T7 平臺總線

1.設備驅動模型

1.1由來

  • 在以前的字符設備驅動編程模型裏面主要有如下幾步

1.首先要實現入口函數xxx_init()和卸載函數xxx_exit()node

2.申請設備號register_chrdevlinux

3.建立設備節點,如class_create,device_createshell

4.硬件部分初始化,如io資源映射ioremap,中斷註冊等express

5.構建file_operation結構編程

6.實現xxx_open,xxx_read,xxx_write等函數ubuntu

  • 這樣寫看似沒毛病,可是可移植性較差.若是在寫完一個驅動的狀況下又要寫另一個驅動,明顯按照這種寫法又得推倒重來
  • 舉個例子,假如要寫手機攝像頭驅動,使用上述方法可寫一套前攝驅動和一套後攝驅動,但實際上咱們發現除了步驟4不同,其餘的步驟先後攝都差很少,所以上述方法在代碼複用性上有待提高,換種說法,就是爲了省事,少加班
  • 所以隆重推出linux設備驅動模型

1.2概念

  • 該模型將驅動劃分爲3個部分:Device(設備對象),Driver(設備驅動對象),Bus(總線對象)
  • 其中能夠把Device想象爲車,Driver想象爲老司機,每輛車的具體參數不同,如功耗,百千米加速,油耗等等.可是對於老司機而言其駕駛方式都是同樣的.其駕駛步驟都差很少
  • 在驅動裏面,Device也就是針對不一樣硬件,其對於的操做地址或者中斷不同,可是對於Driver部分而言都是差很少的,均可以使用ioremap函數映射地址,以後經過readl或者writel函數操做地址
  • 所以在寫驅動代碼的時候就須要將Device與Driver相分類,前者的核心是存異,後者的核心是求同.所以存異的代價是數量繁多,針對不一樣設備就須要不一樣的代碼,可是其代碼量是比較少的.而求同的代價是代碼量多,可是可作到一勞永逸
  • 既然在寫代碼的時候將Device與Driver相分離了,可是到了最後仍是會將兩者組合起來.怎麼組合呢?故引入Bus(總線對象)來將Device與Driver進行組合
  • 咱們將設備信息用Device對象描述好(封裝爲一個節點),再將設備操做方法用Driver對象描述好(封裝爲一個節點),以後將兩者交給Bus總線(設備註冊),Bus總線則根據名稱將Device與Driver相匹配,進而完成老司機開車的壯舉,在內核中經過鏈表管理
  • 若是Device與Driver匹配成功,Bus將調用proke方法

1.3結合實際分析

  • 先看一張圖

/sys下面有總線文件夾,設備文件夾和類文件夾.api

總線文件夾保存總線信息,如i2c總線,usb總線;數組

設備文件夾下面保存設備信息,如usb設備bash

在類文件夾下保存事件信息app

在某一總線下面又包含設備和驅動,驅動即driver,設備即device,其中的設備經過軟連接的方式指向/sys/devices中的設備

  • 咱們進入/sys目錄查看其中的文件,能夠看到其中有bus總線目錄,devices設備目錄(記錄設備信息)
topeet@ubuntu:~$ cd /sys
topeet@ubuntu:/sys$ ls
block  bus  class  dev  devices  firmware  fs  hypervisor  kernel  module  power
topeet@ubuntu:/sys/devices$ cd /sys/devices/
topeet@ubuntu:/sys/devices$ ls
breakpoint  cpu  LNXSYSTM:00  pci0000:00  platform  pnp0  rapidio  software  system  tracepoint  virtual
topeet@ubuntu:/sys/devices$
  • 以後進入/sys/bus目錄,可看到其中有不少總線,以usb總線爲例子,在其內部就有devices設備文件和drivers驅動文件
topeet@ubuntu:/sys/bus$ ls
ac97         cpu           hid           mdio_bus  node         platform  scsi   spi     xen
acpi         event_source  i2c           memory    pci          pnp       sdio   usb     xen-backend
clocksource  gameport      machinecheck  mmc       pci_express  rapidio   serio  virtio
topeet@ubuntu:/sys/bus$ cd usb/
topeet@ubuntu:/sys/bus/usb$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent
  • 以前寫驅動會在/dev目錄下生成相應節點,可是卻不能查看其詳細信息,如輸入設備事件,咱們直接使用ls /dev/input/event*指令查看輸入設備事件節點,可是卻看不到其對於驅動信息,其具體信息保存在/sys/class/input下面
# 查看輸入設備節點
topeet@ubuntu:~$ ls /dev/input/event*
/dev/input/event0  /dev/input/event1  /dev/input/event2  /dev/input/event3  /dev/input/event4
# 查看輸入設備節點信息
topeet@ubuntu:~$ cd /sys/class/input/
topeet@ubuntu:/sys/class/input$ ls
event0  event2  event4  input1  input3  js0   mouse0  mouse2
event1  event3  input0  input2  input4  mice  mouse1
# 查看event0節點設備號
topeet@ubuntu:/sys/class/input$ cd event0
topeet@ubuntu:/sys/class/input/event0$ ls
dev  device  power  subsystem  uevent
topeet@ubuntu:/sys/class/input/event0$ cat uevent 
MAJOR=13
MINOR=64
DEVNAME=input/event0
# 查看event0節點設備號
topeet@ubuntu:/sys/class/input/event0$ ls -l /dev/input/event0
crw-r----- 1 root root 13, 64 Nov 12 02:43 /dev/input/event0
# 查看event0對於驅動名稱
topeet@ubuntu:/sys/class/input/event0$ cd device
topeet@ubuntu:/sys/class/input/event0/device$ ls
capabilities  device  event0  id  modalias  name  phys  power  properties  subsystem  uevent  uniq
topeet@ubuntu:/sys/class/input/event0/device$ cat name 
Power Button
topeet@ubuntu:/sys/class/input/event0/device$ cat uevent 
PRODUCT=19/0/1/0
NAME="Power Button"
PHYS="LNXPWRBN/button/input0"
PROP=0
EV=3
KEY=10000000000000 0
MODALIAS=input:b0019v0000p0001e0000-e0,1,k74,ramlsfw

1.4構建本身的總線

  • 系統默認建立的總線在/sys/bus/文件夾下
  • 咱們也能夠屬於建立本身的總線,咱們須要在/sys/bus/下面建立咱們本身的總線mybus,以後在mybus內部實現driver驅動和device設備軟連接,其指向/sys/devices/目錄下的mydevice設備,如圖

  • 首先有一個總線對象struct bus_type,用於描述一個總線,管理device和driver,完成兩者匹配
struct bus_type {
	const char		*name;
	const char		*dev_name;
	struct device		*dev_root;
	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);

	int (*num_vf)(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;
};
  • 能夠看到,其中有不少接口,咱們重點關注如下幾個便可
struct bus_type {
	const char		*name;
	int (*match)(struct device *dev, struct device_driver *drv);
};
  • 以後註冊與註銷總線
int bus_register(struct bus_type * bus);	//非0表明失敗
void bus_unregister(struct bus_type * bus);
  • 完整代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


struct bus_type mybus = {
    .name = "mybus",
};


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //註冊總線
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸載總線
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • 現象,當安裝完驅動後咱們在/sys/bus目錄下可看到本身的總線mybus成功被建立,進入其目錄下咱們會發現系統已經自動建立了系列文件,可是drivers和devices文件夾爲空,這部分須要咱們本身去實現
[root@iTOP-4412]# insmod my_bus.ko 
[root@iTOP-4412]# cd /sys/bus/
[root@iTOP-4412]# ls
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# cd mybus/
[root@iTOP-4412]# ls
devices            drivers_autoprobe  uevent
drivers            drivers_probe
[root@iTOP-4412]# cd devices/
[root@iTOP-4412]# cd ..
[root@iTOP-4412]# cd drivers/
[root@iTOP-4412]#

1.5device與driver註冊

1.5.1概述

  • 上述過程實現了總線的建立,可是其內部device與driver爲空,須要咱們本身去建立和註冊
  • 因爲在內核內部會自動幫咱們完成device與driver的匹配,因此兩者的註冊順序不論前後

1.5.2device註冊與卸載

  • 內核裏面有一個device設備對象,用於描述設備信息,包括地址,中斷號,自定義數據等
  • device對象屬性以下
struct device {
	struct device		*parent;

	struct device_private	*p;

	struct kobject kobj;		//全部對象的父類
	const char		*init_name; /* initial name of the device */
	const struct device_type *type;

	struct mutex		mutex;	/* mutex to synchronize calls to
					 * its driver.
					 */

	struct bus_type	*bus;		/* type of bus device is on */
	struct device_driver *driver;	/* which driver has allocated this
					   device */
	void		*platform_data;	/* Platform specific data, device
					   core doesn't touch it */
	void		*driver_data;	/* Driver data, set and get with
					   dev_set/get_drvdata */
	struct dev_links_info	links;
	struct dev_pm_info	power;
	struct dev_pm_domain	*pm_domain;

#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAIN
	struct irq_domain	*msi_domain;
#endif
#ifdef CONFIG_PINCTRL
	struct dev_pin_info	*pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQ
	struct list_head	msi_list;
#endif

#ifdef CONFIG_NUMA
	int		numa_node;	/* NUMA node this device is close to */
#endif
	const struct dma_map_ops *dma_ops;
	u64		*dma_mask;	/* dma mask (if dma'able device) */
	u64		coherent_dma_mask;/* Like dma_mask, but for
					     alloc_coherent mappings as
					     not all hardware supports
					     64 bit addresses for consistent
					     allocations such descriptors. */
	unsigned long	dma_pfn_offset;

	struct device_dma_parameters *dma_parms;

	struct list_head	dma_pools;	/* dma pools (if dma'ble) */

	struct dma_coherent_mem	*dma_mem; /* internal for coherent mem
					     override */
#ifdef CONFIG_DMA_CMA
	struct cma *cma_area;		/* contiguous memory area for dma
					   allocations */
#endif
	/* arch specific additions */
	struct dev_archdata	archdata;

	struct device_node	*of_node; /* associated device tree node */
	struct fwnode_handle	*fwnode; /* firmware device node */

	dev_t			devt;	/* dev_t, creates the sysfs "dev" */
	u32			id;	/* device instance */

	spinlock_t		devres_lock;
	struct list_head	devres_head;

	struct klist_node	knode_class;
	struct class		*class;
	const struct attribute_group **groups;	/* optional groups */

	void	(*release)(struct device *dev);		/*使用platform_device_register函數時候須要實現*/
	struct iommu_group	*iommu_group;
	struct iommu_fwspec	*iommu_fwspec;

	bool			offline_disabled:1;
	bool			offline:1;
	bool			of_node_reused:1;
};
  • 咱們須要注意如下幾個成員
struct device {
	struct kobject kobj;			//全部對象的父類,相似於繼承父類
	const char		*init_name;		//在總線中/sys/bus/devices中會建立一個該名字命名的文件,用於匹配
	struct bus_type	*bus;			//指向該device對象依附的總線對象
	struct device_driver *driver;	 //描述該device被哪一個driver驅動
	void		*platform_data;		//自定義數據,可指向任何數據類型的數據
};
  • device註冊和註銷的方法
int device_register(struct device * dev);	//小於0表明設備註冊失敗
int device_unregister(struct device * dev);
  • device驅動代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


/*導入外部結構體變量,使得編譯經過*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "mydev",
    .bus = &mybus,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*註冊device到總線中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • 總線驅動代碼,將自定義總線導出,便於驅動之間的相互調用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


struct bus_type mybus = {
    .name = "mybus",
};

/*導出結構體,方便內核之間交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //註冊總線
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸載總線
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • 現象,安裝my_bus.ko驅動後在/sys/bus/下出現本身的總線.安裝my_dev.ko 後在/sys/bus/mybus/devices/出現本身的設備
[root@iTOP-4412]# ls
key_drv.ko  my_bus.ko   my_dev.ko
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          nvmem        serio
clockevents  gpio         mdio_bus     platform     spi
clocksource  hid          mipi-dsi     scsi         usb
container    i2c          mmc          sdio         workqueue
[root@iTOP-4412]# insmod my_bus.ko 
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
mydev
[root@iTOP-4412]#

1.5.3driver註冊與卸載

  • 同device同樣,driver也有一個設備驅動對象,他主要用於描述設備驅動的方法(代碼邏輯,即如何操做地址中斷等)
  • driver設備驅動對象屬性以下
struct device_driver {
	const char		*name;			//在總線中/sys/bus/drivers中會建立一個該名字命名的文件,用於匹配
	struct bus_type		*bus;		//指向該driver對象依附的總線對象

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

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

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

	int (*probe) (struct device *dev);		//device與driver匹配以後,driver要乾的事情
	int (*remove) (struct device *dev);		//device與driver從總線移除後,driver要乾的事情
	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;
};
  • driver註冊和註銷的方法
int driver_register(struct device_driver * drv);	//小於0表明驅動註冊失敗
void driver_unregister(struct device_driver * drv);
  • driver驅動代碼以下
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


int mydrv_probe (struct device *dev)
{
    return 0;
}
int mydrv_remove (struct device *dev)
{
    return 0;
}


/*導入外部結構體變量,使得編譯經過*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "mydrv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*註冊driver到總線中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 同時要修改Makefile,讓其可以同時編譯my_bus.c,my_dev.c,my_drv.c
ROOTFS_DIR = /mnt/hgfs/share_drv/driver_ko

# 應用程序代碼名稱
#APP_NAME = key_test
# 驅動代碼名稱
MODULE_NAME = my_bus
MODULE_NAME1 = my_dev
MODULE_NAME2 = my_drv

CROSS_COMPILE = /usr/local/arm/gcc-4.6.2-glibc-2.13-linaro-multilib-2011.12/fsl-linaro-toolchain/bin/arm-linux-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/topeet/kernel/itop4412_kernel_4_14_2_bsp/linux-4.14.2_iTop-4412_scp
CUR_DIR = $(shell pwd)

all:
        make -C $(KERNEL_DIR) M=$(CUR_DIR) modules
#       $(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
        make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
        rm $(APP_NAME)
install:
        cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)
else
        obj-m += $(MODULE_NAME).o
        obj-m += $(MODULE_NAME1).o
        obj-m += $(MODULE_NAME2).o
endif
  • 執行結果,可見在安裝完dev與drv驅動後在/sys/bus/mybus/devices//sys/bus/mybus/drivers目錄下有對應驅動文件出現
[root@iTOP-4412]# ls
key_drv.ko  my_bus.ko   my_dev.ko   my_drv.ko
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          nvmem        serio
clockevents  gpio         mdio_bus     platform     spi
clocksource  hid          mipi-dsi     scsi         usb
container    i2c          mmc          sdio         workqueue
[root@iTOP-4412]# insmod my_bus.ko 
[  525.038385] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# ls /sys/bus/
amba         cpu          iio          mybus        sdio         workqueue
clockevents  gpio         mdio_bus     nvmem        serio
clocksource  hid          mipi-dsi     platform     spi
container    i2c          mmc          scsi         usb
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/devices/
mydev
[root@iTOP-4412]# ls /sys/bus/mybus/drivers
[root@iTOP-4412]# insmod my_drv.ko 
[root@iTOP-4412]# ls /sys/bus/mybus/drivers
mydrv
[root@iTOP-4412]#

1.5.4手動匹配

  • 1.5.3中說到,device與driver匹配以後,會調用driver中的probe方法,可是在上述代碼中driver的名字爲mydrv(.name = "mydrv",),device的名字爲mydev(.init_name = "mydev",).可見兩者名字不一樣,那麼天然沒法匹配
  • 如何完成匹配,而且調用probe方法呢?此時須要咱們本身在bus中實現匹配的邏輯過程,而且將device與driver中的名字改成同樣,即總線做用之一就是匹配device和driver
  • 咱們能夠經過實現bus對象中的match方法完成匹配
//注意:若是匹配成功,match方法必定要返回1;匹配失敗會返回0
int (*match)(struct device *dev, struct device_driver *drv);

/*demo*/
int mybus_match(struct device *dev, struct device_driver *drv)
{
    /*注意參數2爲kobj對象中的name,而不是dev->init_name*/
    /*匹配成功返回0,取反後爲true*/
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}

特別注意:在執行strncmp函數的時候,參數1爲device_driver對象中的name屬性,參數2不能是device對象中的init_name屬性,由於在內核中會將device對象中的init_name值賦給kobject對象中的name,而後將本身置爲NULL,若是使用dev->init_name會出現段錯誤,其中kobject對象以下,其中init_name的值就給了kobject中的name

  • kobject對象
struct kobject {
	const char		*name;
	struct list_head	entry;
	struct kobject		*parent;
	struct kset		*kset;
	struct kobj_type	*ktype;
	struct kernfs_node	*sd; /* sysfs directory entry */
	struct kref		kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
	struct delayed_work	release;
#endif
	unsigned int state_initialized:1;
	unsigned int state_in_sysfs:1;
	unsigned int state_add_uevent_sent:1;
	unsigned int state_remove_uevent_sent:1;
	unsigned int uevent_suppress:1;
};

1.5.5完整總線框架

  • bus總線
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}


struct bus_type mybus = {
    .name = "mybus",
    .match = mybus_match,
};

/*導出結構體,方便內核之間交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //註冊總線
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸載總線
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • device設備
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>


void mydev_release (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
}


/*導入外部結構體變量,使得編譯經過*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*註冊device到總線中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • driver驅動
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mydrv_probe (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int mydrv_remove (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}


/*導入外部結構體變量,使得編譯經過*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "myderv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*註冊driver到總線中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 結果以下,能夠看到,當註冊完device與driver後會調用bus中的match方法匹配,匹配成功後,系統會自動調用driver對象中的probe方法
[root@iTOP-4412]# insmod my_bus.ko 
[   29.385382] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# insmod my_drv.ko 
[   45.094375] match ok
[   45.095171] ------mydrv_probe------
[root@iTOP-4412]#

1.6driver驅動device

1.6.1概述

  • 當driver與device匹配成功後,會調用driver中的probe方法,其中probe方法以下,其參數爲一個device對象.換句話而言,咱們在建立總線後會註冊driver和device,當兩者匹配成功後系統會將device做爲參數告訴driver,以便讓driver進行操做.再進一步而言,咱們在device裏面寫好設備信息而後傳給driver去使用
int (*probe) (struct device *dev);

1.6.2自定義device設備屬性

  • 在1.5.2裏面說到,在device對象中有一個屬性爲自定義數據類型,以下,由於該參數爲一個void類型指針,故能夠接收任何數據類型,包括結構體(對象)
void		*platform_data;		//自定義數據,可指向任何數據類型的數據
  • 爲了方便device建立自定義對象,driver接收自定義對象,在此我建立一個頭文件dev_info.h來聲明一個類,用於保存device設備屬性
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__

/*自定義一個數據類,描述設備特性*/
struct mydev_desc {
    char *name;
    int irqno;
    unsigned long addr;
};

#endif

1.6.3device設備添加自定義屬性

  • 咱們可建立自定義對象,在對象中描述設備屬性,以後將對象做爲platform_data參數傳進device結構體中,以下
struct mydev_desc mydev_info = {
    .name = "test_dev",
    .irqno = 999,
    .addr = 0x30006000,
};
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
    .platform_data = &mydev_info,
};

1.6.4driver接收設備屬性

  • 首先建立一個mydev_desc類型的對象,用於接收probe方法中device參數中的設備屬性,以下
struct mydev_desc *pdesc;
int mydrv_probe (struct device *dev)
{
    unsigned long *paddr;
    printk("------%s------\n", __FUNCTION__);
    /*注意強制類型轉換*/
    pdesc = (struct mydev_desc *)dev->platform_data;
    printk("name = %s\n", pdesc->name);
    printk("irqno = %d\n", pdesc->irqno);
    paddr = ioremap(pdesc->addr, 8);
    return 0;
}

1.6.5完整代碼

  • 描述設備屬性的頭文件
#ifndef __DEV_INFO_H__
#define __DEV_INFO_H__

/*自定義一個數據類,描述設備特性*/
struct mydev_desc {
    char *name;
    int irqno;
    unsigned long addr;
};
#endif
  • 總線代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>

int mybus_match(struct device *dev, struct device_driver *drv)
{
    if(!strncmp(drv->name, dev->kobj.name, strlen(drv->name)))
    {
        printk("match ok\n");
        return 1;
    }
    else
    {
        printk("match failed\n");
        return 0;
    }
}


struct bus_type mybus = {
    .name = "mybus",
    .match = mybus_match,
};

/*導出結構體,方便內核之間交互*/
EXPORT_SYMBOL(mybus);


static int __init mybus_init(void)
{
    int ret;
    ret = bus_register(&mybus);     //註冊總線
    if(ret != 0)
    {
        printk("bus_register error\n");
        return ret;
    }
    return 0;
}

static void __exit mybus_exit(void)
{
    bus_unregister(&mybus);         //卸載總線
}
module_init(mybus_init);
module_exit(mybus_exit);
MODULE_LICENSE("GPL");
  • device設備部分代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include "dev_info.h"


struct mydev_desc mydev_info = {
    .name = "test_dev",
    .irqno = 999,
    .addr = 0x30006000,
};
void mydev_release (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
}


/*導入外部結構體變量,使得編譯經過*/
/*此處填寫設備專有的信息*/
extern struct bus_type mybus;
struct device mydev = {
    .init_name = "myderv",
    .bus = &mybus,
    .release = mydev_release,
    .platform_data = &mydev_info,
};

static int __init mydev_init(void)
{
    int dr_ret;
    /*註冊device到總線中去*/
    dr_ret = device_register(&mydev);
    if(dr_ret < 0)
    {
        printk("device_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydev_exit(void)
{
    device_unregister(&mydev);
}

module_init(mydev_init);
module_exit(mydev_exit);
MODULE_LICENSE("GPL");
  • driver驅動部分代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/io.h>
#include "dev_info.h"


struct mydev_desc *pdesc;

/*設備與驅動匹配成功就提取設備信息*/
int mydrv_probe (struct device *dev)
{
    unsigned long *paddr;
    printk("------%s------\n", __FUNCTION__);
    /*此處注意強轉*/
    pdesc = (struct mydev_desc *)dev->platform_data;
    printk("name = %s\n", pdesc->name);
    printk("irqno = %d\n", pdesc->irqno);
    paddr = ioremap(pdesc->addr, 8);
    return 0;
}
int mydrv_remove (struct device *dev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}


/*導入外部結構體變量,使得編譯經過*/
extern struct bus_type mybus;
struct device_driver mydrv = {
    .name = "myderv",
    .bus = &mybus,
    .probe = mydrv_probe,
    .remove = mydrv_remove,
};

static int __init mydrv_init(void)
{
    int dr_ret;
    /*註冊driver到總線中去*/
    dr_ret = driver_register(&mydrv);
    if(dr_ret < 0)
    {
        printk("driver_register error\n");
        return dr_ret;
    }
    return 0;
}

static void __exit mydrv_exit(void)
{
    driver_unregister(&mydrv);
}

module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
  • 最終實現效果,當匹配成功後,driver驅動成功拿到了device設備信息
[root@iTOP-4412]# insmod my_bus.ko 
[  207.194598] my_bus: loading out-of-tree module taints kernel.
[root@iTOP-4412]# insmod my_dev.ko 
[root@iTOP-4412]# insmod my_drv.ko 
[  220.166648] match ok
[  220.167439] ------mydrv_probe------
[  220.176020] name = test_dev
[  220.177356] irqno = 999
[root@iTOP-4412]#

2.平臺總線模型

2.1由來

  • 平臺總線模型用於平臺升級
  • 假設某個硬件平臺升級了,例如三星,由2440升級到4412,很明顯後者處理能力要比前者強.可是後者的誕生並非徹底顛覆前者.而是繼承了前者的一些特性,例如串口,IIC,GPIO等外設的控制方式等.在控制層面兩者基本相同.可是後者因爲內存容量的提高,那麼其尋址範圍要比前者更大,即寄存器地址不同
  • 若是不用平臺總線的話,對於soc升級的時候,對於類似的設備驅動,須要編寫不少重複代碼.所以引入平臺總線模型,將設備與驅動分離,故在升級的時候只須要更改設備信息便可
  • 最終實現一個驅動驅動多個平臺的設備

2.2概念

  • 平臺總線(platform bus)三元素:bus(總線),driver(驅動),device(設備)

2.2.1bus

  • 在平臺總線裏面,bus不須要本身建立,在開機的時候由系統建立.其會在/sys/bus/下面生成一個platform總線
topeet@ubuntu:~$ cd /sys/bus/
topeet@ubuntu:/sys/bus$ ls
ac97  clocksource  event_source  hid  machinecheck  memory  node  pci_express  pnp      scsi  serio  usb     xen
acpi  cpu          gameport      i2c  mdio_bus      mmc     pci   platform     rapidio  sdio  spi    virtio  xen-backend
# 系統生成的platform總線
topeet@ubuntu:/sys/bus$ cd platform/
topeet@ubuntu:/sys/bus/platform$ ls
devices  drivers  drivers_autoprobe  drivers_probe  uevent
topeet@ubuntu:/sys/bus/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,
};
  • bus匹配規則

1.優先匹配driver中的id_table,在其中包含了支持不一樣的平臺的名字

2.直接匹配driver中的名字和device中的名字,與1.5相似

2.2.2device

  • 平臺設備對象描述以下,直接繼承了device類,可是又增長了一些私有屬性,整體偏向於記錄設備屬性
struct platform_device {
	const char	*name;			//與driver作匹配使用
	int		id;				   //一半直接爲-1
	bool		id_auto;
	struct device	dev;	    //繼承了1.5中的device父類
	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;
};

2.2.3driver

  • 平臺驅動對象描述以下,繼承了driver父類並增長了私有方法,整體偏向於實現某種方法.
struct platform_driver {
	int (*probe)(struct platform_device *);			//platform_device與platform_driver匹配以後,platform_driver要乾的事情
	int (*remove)(struct platform_device *);		//platform_device與platform_driver從總線移除後,platform_driver要乾的事情
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;				   //繼承driver父類
	const struct platform_device_id *id_table;		//記錄該platform_driver所支持的平臺
	bool prevent_deferred_probe;
};

2.3平臺總線匹配原理

  • 咱們能夠研究如下平臺設備與平臺驅動之間是如何匹配的
  • 由2.2部分可看出,platform_device與platform_driver類均繼承了本身的父類,分別爲device與driver.而且實際註冊到平臺總線中的是其父類(device與driver)
  • 那麼在註冊的時候是註冊的父類,可是在比較的時候倒是子類,此處就須要經過父類來推測子類
  • 在linux內核中可經過container_of宏來獲取父類指針,即若是一個結構體內部(子類)包含一個結構體(父類),咱們就能夠根據被包含的結構體地址去獲取包含的結構體的地址,即由父類地址去獲取子類地址
#define to_platform_device(x) container_of((x), struct platform_device, dev)


static const struct platform_device_id *platform_match_id(
			const struct platform_device_id *id,
			struct platform_device *pdev)
{
	while (id->name[0]) {
		if (strcmp(pdev->name, id->name) == 0) {
			pdev->id_entry = id;
			return id;
		}
		id++;
	}
	return NULL;
}



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;
	
     /*若是platform_driver中有id_table,則優先匹配pdrv中的設備,不然匹配drvice中的名字*/
	/* 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);
}

2.4編寫platform_device

  • 實操:此處使用平臺總線完成點燈操做
  • 咱們須要註冊一個platform_device對象並定義好相關的資源:地址,中斷...
  • 註冊API爲platform_device_register或者platform_device_add
/*註冊設備,須要實現platform_device->dev->release方法*/
int platform_device_register(struct platform_device *pdev);

/*註冊設備*/
int platform_device_add(struct platform_device * pdev);

/*卸載設備*/
void platform_device_unregister(struct platform_device *pdev);
  • 在platform_device對象中咱們須要填寫硬件資源信息,其保存在resource結構體內,注意:通常某個設備可能包含多個資源,所以能夠經過使用結構體數組來描述多個資源信息
struct resource {
    resource_size_t start;			//起始地址
    resource_size_t end;			//結束地址
    const char      *name;			//自定義名字
    unsigned long   flags;			//標誌,表示當前資源是描述內存(IORESOURCE_MEM)仍是中斷(IORESOURCE_IRQ)
    struct resource *parent, *sibling, *child;
};
  • 知道大概原理後咱們查看開發板原理圖,其中有2個LED燈可控,如圖.其中咱們能夠發現其使用了2個引腳控制,一個爲KP_COL0,一個爲VDD50_EN,此可咱們須要跳轉到核心板原理圖去尋找兩個引腳

  • LED2與LED3分別對應芯片的GPL2_0GPK1_1引腳,以後再去查找芯片數據手冊

  • 首先查找GPL2組寄存器,發現其對應配置寄存器GPL2CON描述以下,能夠看出其基地址爲0x1100 0000(#define GPL_BASE 0x11000000),偏移地址爲0x0100,因此描述資源的起始地址可設置爲0x1100 0100(#define GPL2_CON GPL_BASE + 0x0100),而資源的終止地址則爲起始地址加上GPL2系列地址的總長度(24Byte)

  • GPL2系列寄存器以下,能夠看到在GPL2系列中一共有6個寄存器,其中每一個寄存器大小爲4字節,所以GPL2系列大小爲24字節(#define GPL2_SIZE 24),所以結束地址爲GPL2_CON + GPL2_SIZE - 1.特別注意:最後要減去1,假設GPL2CON編號爲1,那麼GPL2PUDPDN編號爲6,即1 + 6 - 1 = 6,因此要減去1.

  • 對於LED3的GPK1系列就沒必要多說了,最終填寫的資源描述以下
#define GPL_BASE 0x11000000
#define GPL2_CON GPL_BASE + 0x0100
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON GPK_BASE + 0x0060
#define GPK1_SIZE 24

/*定義爲一個數組,由於一個設備中可能有多個資源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意減去1個偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};
  • 再說明一下對中斷的描述,中斷資源不一樣於內存資源,中斷的描述是經過中斷號來體現,所以其開始與結束都是同一個地址,例如
#define IRQ_EINT(x)    (((x) >= 4) ? (IRQ_EINT4 + (x) - 4) : (IRQ_EINT0 + (x)))

struct resource led_res[] = {
    /*中斷資源,此處描述4號中斷*/
    [0] = {
        .start = IRQ_EINT(4),
        .end = IRQ_EINT(4),
        .flags = IORESOURCE_IRQ,
    },
};
  • 最後在platform_device結構體中指定資源地址和大小便可
struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用做匹配
    .id = -1,                               //通常取-1,以後會詳解
    .num_resources = ARRAY_SIZE(led_res),   //資源個數,此處爲2
    .resource = led_res,                    //資源數組
};
  • 完整platform_device代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>


#define GPL_BASE 0x11000000
#define GPL2_CON GPL_BASE + 0x0100
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON GPK_BASE + 0x0060
#define GPK1_SIZE 24

/*定義爲一個數組,由於一個設備中可能有多個資源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意減去1個偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};

/*定義該方法,防止在卸載dev模塊時候報警告*/
static void platform_device_test_release(struct device *dev)
{
    
}

struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用做匹配,系統會在/sys/bus/platform/devices/下建立該名稱文件夾
    .id = -1,                               //通常取-1,以後會詳解
    .num_resources = ARRAY_SIZE(led_res),   //資源個數,此處爲2
    .resource = led_res,                    //資源數組
    .dev = {
        .release = platform_device_test_release,
    },
};


static int __init plat_led_pdev_init(void)
{
    /*註冊platform_device*/
    return platform_device_register(&led_pdev);
    //return platform_device_add(&led_pdev);
}

static void __exit plat_led_pdev_exit(void)
{
    platform_device_unregister(&led_pdev);
}

module_init(plat_led_pdev_init);
module_exit(plat_led_pdev_exit);
MODULE_LICENSE("GPL");
  • 執行結果
# 沒有安裝模塊的時候沒有exynos4412_led設備
[root@iTOP-4412]# ls /sys/bus/platform/devices/
...
[root@iTOP-4412]# insmod plat_led_pdev.ko
# 安裝模塊後出現exynos4412_led設備
[root@iTOP-4412]# ls /sys/bus/platform/devices/
11800000.fimc                    exynos4412_led
11840000.jpeg-codec              gpio-keys

2.5編寫platform_driver

  • 咱們須要註冊一個platform_driver對象並實現相關操做代碼
/*註冊平臺驅動*/
int platform_driver_register(struct platform_driver *drv);
/*註銷平臺驅動*/
void platform_driver_unregister(struct platform_driver *drv);
  • 若是該驅動與設備匹配成功則會調用驅動中的probe方法,所以咱們會在probe方法裏面對硬件進行操做,例如註冊設備號,註冊file_operation爲用戶提供設備標識,同時提供文件操做的接口,如read,write...
  • 填寫platform_driver對象中的方法,此處須要注意id_table的填寫,若是platform_driver中有id_table,則優先匹配platform_driver中的設備,不然匹配drvice中的名字(samsung led_drv),詳見2.3
/*平臺列表,表示本驅動能夠支持的平臺*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};

struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //若是沒有定義id_table的話能夠用於匹配device
    },
    .id_table = led_id_table,
};
  • platform_driver完整代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>

int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

int led_pdrv_remove(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

/*平臺列表,表示本驅動能夠支持的平臺*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};

struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //若是沒有定義id_table的話能夠用於匹配device
    },
    .id_table = led_id_table,
};

static int __init plat_led_pdrv_init(void)
{
    /*註冊一個平臺驅動*/
    platform_driver_register(&led_pdrv);
    return 0;
}

static void __exit plat_led_pdrv_exit(void)
{
    platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
  • 執行結果,能夠看到platform_device與platform_driver匹配成功,進而執行了led_pdrv_probe方法
[root@iTOP-4412]# insmod plat_led_pdrv.ko 
[ 2071.656696] ------led_pdrv_probe------

2.6實現probe方法

  • 上述代碼成功註冊設備並與驅動綁定成功,那麼接下來就須要在probe中實現具體的操做邏輯,其操做步驟與以前編寫字符設備驅動步驟相似:動態建立主設備號,建立設備節點,硬件初始化(將物理地址映射爲虛擬地址),以後再獲取platform_device資源便可
  • 獲取platform_device資源可經過platform_get_resource函數便可,此處須要注意獲取方式,獲取方式參考如下描述
/*
功能:獲取設備中的資源
參數:
參數1:從哪一個設備獲取資源
參數2:獲取的資源類型(內存資源或者中斷資源)
參數3:獲取同種類型的資源的第幾個(注意:同種類型資源)

*/
struct resource *platform_get_resource(struct platform_device * dev, unsigned int type, unsigned int num);
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意減去1個偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
    [2] = {
        .start = IRQ_EINT(4),
        .end = IRQ_EINT(4),
        .flags = IORESOURCE_IRQ,
    },
};

/*獲取內存資源的第一個,即GPL2_CON*/
platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*獲取內存資源的第二個,即GPK1_CON*/
platform_get_resource(pdev, IORESOURCE_MEM, 1);
/*獲取中斷資源的第一個,即IRQ_EINT(4)*/
platform_get_resource(pdev, IORESOURCE_IRQ, 0);
platform_get_irq(pdev, 0);
  • 最終咱們實現的probe方法以下
int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL);
    if(samsung_led == NULL)
    {
        printk("kmalloc error\n");
        return -ENOMEM;
    }
    /*動態註冊設備號*/
    samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops);
    /*建立設備節點*/
    samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
    samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), 
                                    NULL, "led0");
    /*獲取設備中寄存器資源*/
    samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    /*內存映射,硬件初始化,下述2種方法相似*/
    //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1);
    samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
    return 0;
}
  • 以後當咱們調用open函數的時候就將GPL2_0配置爲輸出模式,參考數據手冊,咱們須要將GPL2CON(32bit)寄存器的低4位(bit0~3)設置爲0x1便可,設置代碼以下,即先將低4位置0,再置1便可

int led_pdrv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    /*配置GPL2_0爲輸出*/
    writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base);
    return 0;
}
  • 以後在根據應用層傳入的參數判斷是否開關燈,此時能夠看GPL2DAT寄存器,其只有8位,對於8個GPIO,對於位寫0則輸出0,寫1則輸出1.此處爲GPL2_0,對應第0位,配置代碼以下,須要注意的是GPL2DAT寄存器相較於基地址(GPL2CON)寄存器地址偏移了4字節,因此須要手動偏移4字節(samsung_led->reg_base + 4)

ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
    int val, ret;
    ret = copy_from_user(&val, buf, count);
    if(ret > 0)
    {
        printk("copy_from_user error\n");
        return -EFAULT;
    }
    if(val)
    {
        printk("on\n");
        /*開燈1*/
        writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4);
        /*開燈2*/
        //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4);
    }
    else
    {
        printk("off\n");
        /*關燈1*/
        writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4);
        /*關燈2*/
        //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4);
    }
    return 0;
}

2.7完整所有代碼

  • platform_device代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>


#define GPL_BASE 0x11000000
#define GPL2_CON (GPL_BASE + 0x0100)
#define GPL2_SIZE 24

#define GPK_BASE 0x11000000
#define GPK1_CON (GPK_BASE + 0x0060)
#define GPK1_SIZE 24

/*定義爲一個數組,由於一個設備中可能有多個資源*/
struct resource led_res[] = {
    [0] = {
        .start = GPL2_CON,
        .end = GPL2_CON + GPL2_SIZE - 1,        //注意減去1個偏移量
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = GPK1_CON,
        .end = GPK1_CON + GPK1_SIZE - 1,
        .flags = IORESOURCE_MEM,
    },
};

static void platform_device_test_release(struct device *dev)
{
    
}

struct platform_device led_pdev = {
    .name = "exynos4412_led",               //用做匹配,系統會在/sys/bus/platform/devices/下建立該名稱文件夾
    .id = -1,                               //通常取-1,以後會詳解
    .num_resources = ARRAY_SIZE(led_res),   //資源個數,此處爲2
    .resource = led_res,                    //資源數組
    .dev = {
        .release = platform_device_test_release,
    },
};


static int __init plat_led_pdev_init(void)
{
    /*註冊platform_device*/
    return platform_device_register(&led_pdev);
    //return platform_device_add(&led_pdev);
}

static void __exit plat_led_pdev_exit(void)
{
    platform_device_unregister(&led_pdev);
}

module_init(plat_led_pdev_init);
module_exit(plat_led_pdev_exit);
MODULE_LICENSE("GPL");
  • platform_driver代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/ioport.h>

/*設計全局設備對象,方便後續調用*/
struct led_dev{
    int dev_major;          //主設備號
    struct class *cls;
    struct device *dev;
    struct resource *res;   //獲取到的內存資源
    void *reg_base;         //保存轉換後的虛擬地址
};
struct led_dev *samsung_led;

ssize_t led_pdrv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fops)
{
    int val, ret;
    ret = copy_from_user(&val, buf, count);
    if(ret > 0)
    {
        printk("copy_from_user error\n");
        return -EFAULT;
    }
    if(val)
    {
        printk("on\n");
        /*開燈1*/
        writel(readl(samsung_led->reg_base + 4) | (0x1<<0), samsung_led->reg_base + 4);
        /*開燈2*/
        //writel(readl(samsung_led->reg_base + 4) | (0x1<<1), samsung_led->reg_base + 4);
    }
    else
    {
        printk("off\n");
        /*關燈1*/
        writel(readl(samsung_led->reg_base + 4) & ~(0x1<<0), samsung_led->reg_base + 4);
        /*關燈2*/
        //writel(readl(samsung_led->reg_base + 4) & ~(0x1<<1), samsung_led->reg_base + 4);
    }
    return 0;
}

int led_pdrv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    /*配置寄存器爲輸出*/
    writel((readl(samsung_led->reg_base) & (~((0xf)<<0))) | (0x1<<0), samsung_led->reg_base);
    return 0;
}

int led_pdrv_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

struct file_operations led_fops = {
    .open = led_pdrv_open,
    .release = led_pdrv_close,
    .write = led_pdrv_write,
};

int led_pdrv_probe(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    samsung_led = (struct led_dev *)kmalloc(sizeof(struct led_dev), GFP_KERNEL);
    if(samsung_led == NULL)
    {
        printk("kmalloc error\n");
        return -ENOMEM;
    }
    /*動態註冊設備號*/
    samsung_led->dev_major = register_chrdev(0, "led_drv", &led_fops);
    /*建立設備節點*/
    samsung_led->cls = class_create(THIS_MODULE, "led_new_cls");
    samsung_led->dev = device_create(samsung_led->cls, NULL, MKDEV(samsung_led->dev_major, 0), 
                                    NULL, "led0");
    /*獲取設備中寄存器資源*/
    samsung_led->res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
    /*內存映射,硬件初始化*/
    //ioremap(samsung_led->res->start, (samsung_led->res->end) - (samsung_led->res->start) + 1);
    samsung_led->reg_base = ioremap(samsung_led->res->start, resource_size(samsung_led->res));
    return 0;
}

int led_pdrv_remove(struct platform_device *pdev)
{
    printk("------%s------\n", __FUNCTION__);
    iounmap(samsung_led->reg_base);
    device_destroy(samsung_led->cls, MKDEV(samsung_led->dev_major, 0));
    class_destroy(samsung_led->cls);
    unregister_chrdev(samsung_led->dev_major, "led_drv");
    kfree(samsung_led);
    return 0;
}


/*平臺列表,表示本驅動能夠支持的平臺*/
struct platform_device_id led_id_table[] = {
    {"exynos4412_led", 0x1111},
    {"s5pv210_led", 0x2222},
    {"s3c2410_led", 0x3333},
};


struct platform_driver led_pdrv = {
    .probe = led_pdrv_probe,
    .remove = led_pdrv_remove,
    .driver = {
        .name = "samsung led_drv",          //若是沒有定義id_table的話能夠用於匹配device
    },
    .id_table = led_id_table,
};

static int __init plat_led_pdrv_init(void)
{
    /*註冊一個平臺驅動*/
    platform_driver_register(&led_pdrv);
    return 0;
}

static void __exit plat_led_pdrv_exit(void)
{
    platform_driver_unregister(&led_pdrv);
}

module_init(plat_led_pdrv_init);
module_exit(plat_led_pdrv_exit);
MODULE_LICENSE("GPL");
  • 測試代碼
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
    int fd;
    int led_state = 0;
    fd = open("/dev/led0", O_RDWR);
    if(fd < 0)
    {
        printf("open error\n");
        return -1;
    }
    while(1)
    {
        led_state = 1;
        write(fd, &led_state, sizeof(led_state));
        sleep(1);

        led_state = 0;
        write(fd, &led_state, sizeof(led_state));
        sleep(1);
    }
    return 0;
}
  • 現象,在安裝對應模塊以後,發現燈會持續亮滅
相關文章
相關標籤/搜索