之前寫的字符驅動,安裝驅動之後還需要自己mknod創建文件節點、而且設備和驅動都在一個驅動文件裏,嵌入式的產品那麼多,如果對應的硬件變了,那整個驅動代碼就不可重用了,可移植性很差,下面說說怎麼解決這些問題。
一、自動創建設備文件結點
實際上Linux內核爲我們提供了一組函數,可以用來在模塊加載的時候自動在/dev目錄下創建相應設備節點,並在卸載模塊時刪除該節點,當然前提條件是用戶空間移植了udev,udev根據系統信息創建設備結點、mdev是busybox提供的一個工具,用在嵌入式系統中,相當於簡化版的udev,作用是在系統啓動和熱插拔或動態加載驅動程序時, 自動創建設備節點。文件系統中的/dev目錄下的設備節點都是由mdev創建的。
[wuyujun@wuyujunlocalhost etc]$ cat inittab
# Use mdev to auto generate device nod and auto mount SD card and USB storage
::sysinit:/bin/echo /sbin/mdev > /proc/sys/kernel/hotplug
::sysinit:/sbin/mdev -s
當驅動程序調用device_create()創建設備節點時內核產生hotplug事件,通知應用程序mdev創建設備程序,mdev最後調用make_device來創建節點,而make_device最終也還是會調用mknod函數去創建節點,詳細的分析mdev參考下面博客
參考:https://blog.csdn.net/qq_33160790/article/details/79266306
自動創建文件結點需要用到的函數:
static struct class * ;
struct class *class_create(struct module *owner, const char *name)
class_create - create a struct class structure
@owner: pointer to the module that is to "own" this struct class
@name: pointer to a string for the name of this class.
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
class_destroy(struct class *cls);
device_destroy(struct class *class,dev_t devt);
在驅動初始化的代碼裏調用class_create爲該設備創建一個類,再爲每個設備調用 device_create創建對應的設備。
下面是增加了自動創建設備文件結點init函數
#include <linux/device.h> /*class_create()*/
#include <linux/version.h>//獲取Linux內核版本號
struct class *dev_class ; //定義全局變量,結構體struct class 類型的指針dev_class方便後面使用;
static int __init s3c_led_init(void)
{
int result;
dev_t devno;
char dev_name[16] ;
int i ;
if( 0 != s3c_hw_init() )
{
printk(KERN_ERR "s3c2440 LED hardware initialize failure.\n");
return -ENODEV;
}
/* Alloc the device for driver */
if (0 != dev_major) /* Static */
{
devno = MKDEV(dev_major, 0);
result = register_chrdev_region (devno, dev_count, DEV_NAME);
}
else
{
result = alloc_chrdev_region(&devno, dev_minor, dev_count, DEV_NAME);
dev_major = MAJOR(devno);
}
/* Alloc for device major failure */
if (result < 0)
{
printk(KERN_ERR "S3C %s driver can't use major %d\n", DEV_NAME, dev_major);
return -ENODEV;
}
printk(KERN_DEBUG "S3C %s driver use major %d\n", DEV_NAME, dev_major);
if(NULL == (led_cdev=cdev_alloc()) )
{
printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
unregister_chrdev_region(devno, dev_count);
return -ENOMEM;
}
led_cdev->owner = THIS_MODULE;
cdev_init(led_cdev, &led_fops);
result = cdev_add(led_cdev, devno, dev_count);
if (0 != result)
{
printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
goto ERROR;
}
dev_class = class_create(THIS_MODULE, DEV_NAME) ;
if(IS_ERR(dev_class))
{
printk("%s driver create class failture\n",DEV_NAME);
result = -ENOMEM;
goto ERROR;
}
memset(dev_name,0,sizeof(dev_name)) ;
for(i=0;i<LED_NUM;i++)
{
devno = MKDEV(dev_major, i);
snprintf(dev_name,sizeof(dev_name),"%s%d",DEV_NAME,i) ;
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
device_create(dev_class, NULL, devno, NULL, dev_name);
#else
device_create (dev_class, NULL, devno, dev_name);
#endif
}
printk(KERN_ERR "S3C %s driver[major=%d] version 1.0.0 installed successfully!\n", DEV_NAME, dev_major);
return 0;
ERROR:
printk(KERN_ERR "S3C %s driver installed failure.\n", DEV_NAME);
cdev_del(led_cdev);
unregister_chrdev_region(devno, dev_count);
return result;
}
在static void __exit s3c_led_exit(void)函數退出還要destroy銷燬掉,防止佔用空間
static void __exit s3c_led_exit(void)
{
int i ;
dev_t devno = MKDEV(dev_major, dev_minor);
s3c_hw_term();
cdev_del(led_cdev);
unregister_chrdev_region(devno, dev_count);
for(i=0;i<LED_NUM;i++)
{
devno = MKDEV(dev_major, i);
device_destroy(dev_class, devno);
}
class_destroy(dev_class);
printk(KERN_ERR "S3C %s driver version 1.0.0 removed!\n", DEV_NAME);
return ;
}
二、Platform總線驅動
從Linux 2.6起引入了一套新的驅動管理和註冊機制,platform_device和platform_driver,Linux中大部分的設備驅動都可以使用這套機制。platform是一條虛擬總線。設備用platform_device表示,驅動用platform_driver進行註冊,linux platform driver機制和傳統的device driver機制(通過driver_register進行註冊)相比,一個明顯的優勢在於platform機制將設備本身的資源註冊進內核,由內核統一管理,在驅動中使用這些資源時通過platform device提供的標準結構進行申請並使用。這樣提高了驅動和資源的獨立性,並且具有較好的可移植性和安全性(這些標準接口是安全的)。
先分析一下platform的工作原理,platform先被內核註冊,設備掛接到總線上時,與總線上的所有驅動進行匹配(用bus_type.match進行匹配),如果匹配成功,則調用bus_type.probe或者driver.probe初始化該設備,掛接到總線上,如果匹配失敗,則只是將該設備掛接到總線上。
驅動掛接到總線上時,與總線上的所有設備進行匹配(用bus_type.match進行匹配),如果匹配成功,則調用bus_type.probe或者driver.probe初始化該設備;如果匹配失敗,則只是將該驅動掛接到總線上。需要重點關注的是總線的匹配函數match(),驅動的初始化函數probe()。
platform的註冊在linux-3.0/driver/base/platform.c裏
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus); //註冊platform設備
if (error)
return error;
error = bus_register(&platform_bus_type); //註冊platform總線
if (error)
device_unregister(&platform_bus);
return error;
}
/platform設備聲明
struct device platform_bus = {
.bus_id = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);
//platform總線設備聲明
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, //設備與匹配用到的函數
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};
platform總線下的匹配函數,platform_match函數
static int platform_match(struct device *dev, struct device_driver *drv) //總線下的設備與設備驅動的匹配函數
{
struct platform_device *pdev = to_platform_device(dev); //通過device 變量獲取到 platform_device
struct platform_driver *pdrv = to_platform_driver(drv); //通過driver獲取 platform_driver
/* match against the id table first */
if (pdrv->id_table) //如果pdrv中的id_table 表存在
return platform_match_id(pdrv->id_table, pdev) != NULL; //匹配id_table
/* fall-back to driver name match */ // 第二個就是指直接匹配 pdev->name drv->name 名字是否形同
return (strcmp(pdev->name, drv->name) == 0);
}
static const struct platform_device_id *platform_match_id(
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) { // 循環去比較id_table數組中的各個id名字是否與pdev->name 相同
if (strcmp(pdev->name, id->name) == 0) {
pdev->id_entry = id; // 將id_table數組中的名字匹配上的這個數組項指針賦值給 pdev->id_entry
return id; // 返回這個指針
}
id++;
}
return NULL;
}
platform總線下設備與設備驅動是通過名字進行匹配的,先去匹配platform_driver中的id_table表中的各個名字與platform_device->name,不管是先註冊設備還是先註冊設備驅動都會進行一次設備與設備驅動的匹配過程,匹配成功之後就會調用probe函數,匹配的原理就是去遍歷總線下的相應的鏈表來找到掛接在他下面的設備或者設備驅動
註冊platform之後就將設備掛到總線上
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]); //輪詢的去註冊設備
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
設備註冊函數
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
return platform_device_add(pdev);
}
接着看 platform_device_add()函數
int platform_device_add(struct platform_device *pdev)
{
int i, ret = 0;
if (!pdev)
return -EINVAL;
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus; //設置父設備是platform_bus
pdev->dev.bus = &platform_bus_type; //設置類型是platform_bus_type
if (pdev->id != -1)
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); //設置設備名字
else
dev_set_name(&pdev->dev, "%s", pdev->name);
for (i = 0; i < pdev->num_resources; i++) {
struct resource *p, *r = &pdev->resource[i];
if (r->name == NULL)
r->name = dev_name(&pdev->dev);
p = r->parent;
if (!p) {
if (resource_type(r) == IORESOURCE_MEM)
p = &iomem_resource;
else if (resource_type(r) == IORESOURCE_IO)
p = &ioport_resource;
}
if (p && insert_resource(p, r)) {
printk(KERN_ERR
"%s: failed to claim resource %d\n",
dev_name(&pdev->dev), i);
ret = -EBUSY;
goto failed;
}
}
pr_debug("Registering platform device '%s'. Parent at %s\n",
dev_name(&pdev->dev), dev_name(pdev->dev.parent));
ret = device_add(&pdev->dev);//註冊一個設備到內核
if (ret == 0)
return ret;
failed:
while (--i >= 0) {
struct resource *r = &pdev->resource[i];
unsigned long type = resource_type(r);
if (type == IORESOURCE_MEM || type == IORESOURCE_IO)
release_resource(r);
}
return ret;
}
device_add具體確定該設備的父設備總線等,調用bus_probe_device()...之後的就不再分析了,感興趣的可以自己看源碼
device_add()-->bus_probe_device()-->device_attch()-->__device_attch()-->really_probe()-->probe()。
和上面分析的一樣device_add註冊之後會到總線上面去找相對應的驅動,如果找到了最終調用probe()函數。
註冊驅動函數
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type; //總線類型初始化爲platform_bus_type
if (drv->probe) //註冊probe函數
drv->driver.probe = platform_drv_probe;
if (drv->remove)//註冊remove函數
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
實現driver_register的代碼在/drivers/base/driver.c裏
int driver_register(struct device_driver *drv)
{
int ret;
struct device_driver *other;
BUG_ON(!drv->bus->p);
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus); //查找驅動防止重複註冊
if (other) {
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
ret = bus_add_driver(drv); //根據總線類型添加驅動
if (ret)
return ret;
ret = driver_add_groups(drv, drv->groups);
if (ret) {
bus_remove_driver(drv);
return ret;
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
return ret;
}
bus_add_driver()裏有driver_attach()函數同樣也是查找相對應的設備:
bus_add_driver()-->driver_attch()-->__driver_attach()-->driver_probe_device-->really_probe()-->probe()
找到了相對應的設備最終調用probe()函數。最後來看一下really_probe(),在linux-3.0/drivers/base/dd.c裏有really_probe()函數
static int really_probe(struct device *dev, struct device_driver *drv)
{
int ret = 0;
atomic_inc(&probe_count);
pr_debug("bus: '%s': %s: probing driver %s with device %s\n",
drv->bus->name, __func__, drv->name, dev_name(dev));
WARN_ON(!list_empty(&dev->devres_head));
dev->driver = drv;
if (driver_sysfs_add(dev)) {
printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
__func__, dev_name(dev));
goto probe_failed;
}
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
goto probe_failed;
} else if (drv->probe) {
ret = drv->probe(dev);
if (ret)
goto probe_failed;
}
driver_bound(dev);
ret = 1;
pr_debug("bus: '%s': %s: bound device %s to driver %s\n",
drv->bus->name, __func__, dev_name(dev), drv->name);
goto done;
probe_failed:
devres_release_all(dev);
driver_sysfs_remove(dev);
dev->driver = NULL;
if (ret != -ENODEV && ret != -ENXIO) {
/* driver matched but the probe failed */
printk(KERN_WARNING
"%s: probe of %s failed with error %d\n",
drv->name, dev_name(dev), ret);
}
/*
* Ignore errors returned by ->probe so that the next driver can try
* its luck.
*/
ret = 0;
done:
atomic_dec(&probe_count);
wake_up(&probe_waitqueue);
return ret;
}
如果dev->bus->probe也就是platform總線上有probe函數優先執行dev->bus->probe(dev);,如果沒有就執行驅動下面的probe函數drv->probe(dev);
使用platfrom總線驅動用到的函數:
int platform_driver_register(struct platform_driver *); // 註冊設備驅動
void platform_driver_unregister(struct platform_driver *); // 卸載設備驅動
int platform_device_register(struct platform_device *); // 註冊設備
void platform_device_unregister(struct platform_device *); // 卸載設備
這裏有兩個重要的結構體struct platform_driver和struct platform_device
定義結構體的頭文件platform_device.h在linux-3.0/include/linux/路徑下面
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
其中最重要的成員就是struct resource * resource;
struct resource {
resource_size_t start; /* 這段資源的起始 */
resource_size_t end; /* 這段資源的結束 */
const char *name; /* 這個資源的名字,方便用戶查看 */
unsigned long flags; /* 標記屬於那種資源 */
struct resource *parent, *sibling, *child; /* 作爲樹的節點,鏈入樹中 */
};
驅動struct platform_driver結構體同樣在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;
};
對gpio的操作的函數
爲了更加優化代碼,對gpio的操作可以使用內核給的函數,在linux內核源代碼的/arch/arm/plat_s3c24xx/gpio.c中實現,這些函數的具體內容就不在這裏介紹了,可以通過源代碼進行查看,這裏對這些函數的用法進行解讀:
1.void s3c2410_gpio_cfgpin(unsigned int pin,unsigned int function)
第一個參數pin 是對應的io引腳
第二個引腳是設置該引腳的功能的
(由S3C2410_GPIO_INPUT,S3C2410_GPIO_OUTPUT,S3C2410_GPIO_SFN2,S3C2410_GPIO_SFN3這4個宏進行定義)
例如:s3c2410_gpio_cfgpin(S3C2410_GPB(5),S3C2410_GPIO_INPUT)
設置GPB5引腳爲輸入。
2.unsigned int s3c2410_gpio_gecfg(unsigned int pin)
作用:返回對應的GPIO的配置情況
例如:pin=s3c2410_GPB5返回GPB5的配置情況
3.void s3c2410_gpio_pullup(unsigned int pin,unsigned int to)
作用:設置相應的的GPIO的上拉電阻。
第一個參數:相應的引腳,和1裏面的用法一致。
第二個參數:設置爲1或者0,1表示上拉,0表示不上拉。
4.void s3c2410_gpio_setpin(unsigned int pin,unsigned int to)
作用:將相應的引腳輸出爲1或者0。
第一個參數:相應的引腳宏
第二個參數:1或者0
例子:s3c2410_gpio_setpin(S3C2410_GPB(5),1)將引腳GPB5輸出爲1
5.unsigned int s3c2410_gpin_getpin(unsigned int pin)
功能:獲取輸入值
參數:相應的引腳
6.unsigned int s3c2410_modify_misccr(unsigned int clear ,unsigned int change)
7.int s3c2410_gpio_getirq(unsigned pin)
Platform總線LED驅動代碼:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/version.h>
#include "platform_led.h"
#define DEV_NAME "led"
#define DEV_MAJOR 251
#ifndef DEV_MAJOR
#define DEV_MAJOR 0 /* dynamic major by default */
#endif
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
#define DEV_NUM 1
static int dev_major = DEV_MAJOR;
struct class *dev_class ;
/* LED hardware informtation data*/
static struct s3c_led_info s3c_leds[] = { //在"platform_led.h"頭文件定義的結構體
[0] = {
.num = 1,
.gpio = S3C2410_GPB(5),
.active_level = LOWLEVEL,
.status = OFF,
},
[1] = {
.num = 2,
.gpio = S3C2410_GPB(6),
.active_level = LOWLEVEL,
.status = OFF,
},
[2] = {
.num = 3,
.gpio = S3C2410_GPB(8),
.active_level = LOWLEVEL,
.status = OFF,
},
[3] = {
.num = 4,
.gpio = S3C2410_GPB(10),
.active_level = LOWLEVEL,
.status = OFF,
},
};
/* The LED platform device private data */
static struct s3c_led_platform_data s3c_led_data = {
.leds = s3c_leds,
.nleds = ARRAY_SIZE(s3c_leds),
};
struct led_device
{
struct s3c_led_platform_data *data;
struct cdev cdev;
}led_device;
static void platform_led_release(struct device * dev)//platform設備結構體裏聲明的remove函數
{
int i;
struct s3c_led_platform_data *pdata = dev->platform_data;
for(i=0; i<pdata->nleds; i++)
{
/* Turn all LED off */
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
}
static void print_led_help(void)
{
printk("Follow is the ioctl() command for LED driver:\n");
printk("Turn LED on command : %u\n", LED_ON);
printk("Turn LED off command : %u\n", LED_OFF);
}
static int led_open(struct inode *inode, struct file *file) //file_operations裏定義的open()函數
{
struct led_device *pdev ;
struct s3c_led_platform_data *pdata;
pdev = container_of(inode->i_cdev,struct led_device, cdev);
pdata = pdev->data;
file->private_data = pdata;
return 0;
}
static int led_release(struct inode *inode, struct file *file)//file_operations裏定義release函數,應用層調用closes時調用到它
{
return 0;
}
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)//file_operations裏定義的的ioctl()函數
{
struct s3c_led_platform_data *pdata = file->private_data;
switch (cmd)
{
case LED_OFF:
if(pdata->nleds <= arg) /* 通過傳進來的arg判斷對哪個LED燈控制,
如果傳進來要控制的LED編號大於led的總數,出錯返回*/
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
s3c2410_gpio_setpin(pdata->leds[arg].gpio, ~pdata->leds[arg].active_level); //關燈
pdata->leds[arg].status = OFF;
break;
case LED_ON:
if(pdata->nleds <= arg)
{
printk("LED%ld doesn't exist\n", arg);
return -ENOTTY;
}
pdata->leds[arg].status = ON;
s3c2410_gpio_setpin(pdata->leds[arg].gpio, pdata->leds[arg].active_level); //開燈
break;
default:
printk("%s driver don't support ioctl command=%d\n", DEV_NAME, cmd);
print_led_help();
return -EINVAL;
}
return 0;
}
struct file_operations led_fops = {
.open = led_open ,
.unlocked_ioctl = led_ioctl ,
.release = led_release ,
};
static int s3c_led_probe(struct platform_device *dev)//設備與驅動匹配成功進入probe函數
{
struct s3c_led_platform_data *pdata = dev->dev.platform_data;
int result = 0;
int i;
dev_t devno;
/* Initialize the LED status */
for(i=0; i<pdata->nleds; i++) //初始化LED狀態
{
s3c2410_gpio_cfgpin(pdata->leds[i].gpio, S3C2410_GPIO_OUTPUT);
if(ON == pdata->leds[i].status)
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, pdata->leds[i].active_level);
}
else
{
s3c2410_gpio_setpin(pdata->leds[i].gpio, ~pdata->leds[i].active_level);
}
}
/* Alloc the device for driver */
if (0 != dev_major)
{
devno = MKDEV(dev_major, 0);
result = register_chrdev_region(devno, 1, DEV_NAME); //註冊主次設備號
}
else
{
result = alloc_chrdev_region(&devno, 0, 1, DEV_NAME); //動態註冊主次設備號
dev_major = MAJOR(devno);
}
/* Alloc for device major failure */
if (result < 0)
{
printk("%s driver can't get major %d\n", DEV_NAME, dev_major);
return result;
}
/* Initialize button structure and register cdev*/
memset(&led_device, 0, sizeof(led_device));
led_device.data = dev->dev.platform_data;
cdev_init (&(led_device.cdev), &led_fops);
led_device.cdev.owner = THIS_MODULE;
result = cdev_add(&(led_device.cdev), devno, 1); //綁定主次設備號,並註冊給Linux內核
if (0 != result)
{
printk(KERN_INFO "S3C %s driver can't reigster cdev: result=%d\n", DEV_NAME, result);
goto ERROR;
}
dev_class = class_create(THIS_MODULE, DEV_NAME); //創建類
if(IS_ERR(dev_class))
{
printk("%s driver create class failture\n",DEV_NAME);
result = -ENOMEM;
goto ERROR;
}
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
device_create(dev_class, NULL, devno, NULL, DEV_NAME); //創建設備結點
#else
device_create(dev_class, NULL, devno, DEV_NAME);
#endif
printk("S3C %s driver version 1.0.0 initiliazed.\n", DEV_NAME);
return 0;
ERROR:
printk("S3C %s driver version 1.0.0 install failure.\n", DEV_NAME);
cdev_del(&(led_device.cdev));
unregister_chrdev_region(devno, 1);
return result;
}
static int s3c_led_remove(struct platform_device *dev) //platform驅動結構體聲明的remove函數
{
dev_t devno = MKDEV(dev_major, 0);
cdev_del(&(led_device.cdev));
device_destroy(dev_class, devno);
class_destroy(dev_class);
unregister_chrdev_region(devno, 1);
printk("S3C %s driver removed\n", DEV_NAME);
return 0;
}
struct platform_device s3c_led_platdev = { //platform設備結構體
.name = "plat_led" ,
.id = 1 ,
.dev = {
.platform_data = &s3c_led_data,
.release = platform_led_release,
}
};
static struct platform_driver s3c_led_platdrv = { //platform驅動結構體
.probe = s3c_led_probe,
.remove = s3c_led_remove,
.driver = {
.name = "plat_led",
.owner = THIS_MODULE,
},
};
static int __init s3c_led_init(void)
{
int rv ;
rv = platform_device_register(&s3c_led_platdev); //註冊設備
if(rv)
{
printk(KERN_ERR "%s:%d: Can't register platform device %d\n", __FUNCTION__,__LINE__, rv);
return rv;
}
printk("Regist S3C LED Platform Device successfully.\n");
rv =platform_driver_register(&s3c_led_platdrv) ;//註冊設備驅動
if(rv)
{
printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__,__LINE__, rv);
return rv;
}
printk("Regist S3C LED Platform Driver successfully.\n");
return 0;
}
static void s3c_led_exit(void)
{
dev_t devno = MKDEV(dev_major, 0);
cdev_del(&(led_device.cdev));
device_destroy(dev_class, devno);
class_destroy(dev_class);
unregister_chrdev_region(devno, 1); //卸載字符設備
class_destroy(dev_class);
printk("%s():%d remove LED platform device\n", __FUNCTION__,__LINE__);
platform_device_unregister(&s3c_led_platdev) ; //卸載設備
printk("%s():%d remove LED platform drvier\n", __FUNCTION__,__LINE__);
platform_driver_unregister(&s3c_led_platdrv) ;//卸載設備驅動
}
/* These two functions defined in <linux/init.h> */
module_init(s3c_led_init);
module_exit(s3c_led_exit);
module_param(dev_major, int, S_IRUGO);
MODULE_AUTHOR("WuYujun<[email protected]>");
MODULE_DESCRIPTION("FL2440 LED linux lowlevel platform device");
MODULE_LICENSE("GPL");
platform_led.h頭文件
#ifndef _PLATDEV_LED_H_
#define _PLATDEV_LED_H_
#include <linux/platform_device.h>
#include <linux/version.h>
#include <mach/regs-gpio.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#include <mach/hardware.h>
#include <mach/gpio.h>
#include <asm/irq.h>
#else
#include <asm-arm/irq.h>
#include <asm/arch/gpio.h>
#include <asm/arch/hardware.h>
#endif
#define ENPULLUP 1
#define DISPULLUP 0
#define HIGHLEVEL 1
#define LOWLEVEL 0
#define INPUT 1
#define OUTPUT 0
#define OFF 0
#define ON 1
#define ENABLE 1
#define DISABLE 0
/* LED hardware informtation structure*/
struct s3c_led_info
{
unsigned char num; /* The LED number */
unsigned int gpio; /* Which GPIO the LED used */
unsigned char active_level; /* The GPIO pin level(HIGHLEVEL or LOWLEVEL) to turn on or off */
unsigned char status; /* Current LED status: OFF/ON */
unsigned char blink; /* Blink or not */
};
/* The LED platform device private data structure */
struct s3c_led_platform_data
{
struct s3c_led_info *leds;
int nleds;
};
#endif /* ----- #ifndef _PLATDEV_LED_H_ ----- */
安裝platform驅動
測試驅動代碼:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define DEV_NUM 4
#define PLATDRV_MAGIC 0x60
#define LED_OFF _IO (PLATDRV_MAGIC, 0x18)
#define LED_ON _IO (PLATDRV_MAGIC, 0x19)
int main(int argc, char **argv)
{
int i ;
int led_fd = -1;
char dev_name[16] ;
memset(dev_name,0,sizeof(dev_name)) ;
snprintf(dev_name, sizeof(dev_name), "/dev/led", i);
led_fd = open(dev_name,O_RDWR) ;
if(led_fd < 0)
{
printf("open %s failed:%s\n",dev_name, strerror(errno)) ;
goto clean ;
}
while(1)
{
for(i=0;i<DEV_NUM;i++)
{
ioctl(led_fd, LED_ON,i) ;
sleep(1) ;
ioctl(led_fd,LED_OFF,i) ;
sleep(1) ;
}
}
clean:
if(led_fd>0)
{
close(led_fd) ;
}
return 0;
}
同樣是實現跑馬燈功能