大家好,今天分享一篇Linux驅動軟件設計思想的文章。由于文章較長,可以先收藏后再慢慢看。
一、Linux驅動的軟件架構
1.1 出發點
為適應多種體系架構的硬件,增強系統的可重用和跨平臺能力。
1.2 分離思想
為達到一個驅動最好一行都不改就可以適用任何硬件平臺的目的,將驅動與設備分離開來,驅動只管驅動,設備只管設備,而驅動以某種通用的標準途徑去拿板級信息,從而降低驅動與設備的耦合程度。
1.3 分層思想
對于同類設備,其基本框架都是一樣的,那么提煉出一個中間層,例如:對于 Input 設備(按鍵、鍵盤、觸摸屏、鼠標)來說,盡管 file_operation、I/O模型不可或缺,但基本框架都是一樣的,因此可提煉出一個?Input?核心層,把跟 Linux 接口以及整個一套?input?事件的匯報機制都在這里面實現。
二、platform設備驅動
platform:linux中的一種虛擬總線。一個現實的linux設備和驅動通常都需要掛接在一種總線上(方便管理),例如PCI、USB、I2C、SPI等,但是對于在Soc系統中集成的獨立外設控制器、掛接在Soc內存空間的外設等卻不能依附于上述總線,這時候linux就發明了一種虛擬總線,來管理這一類的設備(沒有實體的硬件總線電路)。 ? ? ?
platform設備驅動模型中,分為設備、驅動、總線3個實體,分別稱為?platform_device、paltform_driver?和?platform總線,總線負責將設備和驅動進行綁定。在系統每注冊一個設備時,會尋找與之匹配的驅動;相反的,在系統每注冊一個驅動時,會尋找與之匹配的設備,而匹配的過程則由總線完成。
2.1 platform設備
platform設備:由?platform_device?結構體構成,負責管理外設的資源,例如?I/O資源、內存資源、中斷資源等等。 原型:linux/platform_device.h中
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; };
?
2.1.1 resource 結構體
resource?結構體,描述了?platform_device?的資源:
struct?resource?{ ????resource_size_t?start;????????//資源的開始 ????resource_size_t?end;????????//資源的結束 ????const?char?*name; ????unsigned?long?flags;????????//資源的類型 ????struct?resource?*parent,?*sibling,?*child; };參數?flags?常用類型?IORESOURCE_IO、IORESOURCE_MEM、IORESOURCE_IRQ、IORESOURCE_DMA等。參數?start?和?end?的含義會隨著?flags?的不同有所變化。
1)flags?為?IORESOURCE_MEM,start、end?分別表示該platform_device占據的內存的開始與結束地址;
2)flags?為?IORESOURCE_IRQ,start、end?分別表示該platform_device 使用的中斷號的開始值與結束值,如果使用 1個中斷號,開始與結束值相同; 同類型的資源可以有多份,例如某設備占據了多個內存區域,則可以定義多個?IORESOURCE_MEM。 例如在?arch/arm/mach-at91/board-sam9261ek.c?板文件中為 DM9000 網卡定義的 resource:
static?struct?resource?dm9000_resource[]?=?{ ????[0]?=?{ ????????.start????=?AT91_CHIPSELECT_2, ????????.end????=?AT91_CHIPSELECT_2?+?3, ????????.flags????=?IORESOURCE_MEM ????}, ????[1]?=?{ ????????.start????=?AT91_CHIPSELECT_2?+?0x44, ????????.end????=?AT91_CHIPSELECT_2?+?0xFF, ????????.flags????=?IORESOURCE_MEM ????}, ????[2]?=?{ ????????.start????=?AT91_PIN_PC11, ????????.end????=?AT91_PIN_PC11, ????????.flags????=?IORESOURCE_IRQ ????????|?IORESOURCE_IRQ_LOWEDGE?|?IORESOURCE_IRQ_HIGHEDGE, ????} };
?
2.1.2 device 結構體中的 platform_data 資源
設備除了可在 BSP 中定義資源以外,還可以附加一些數據信息,因為對設備的硬件描述除了中斷、內存等標準資源以外,可能還會有一些配置信息,而這些配置信息也依賴于板,不適宜直接放在設備驅動上。 因此,platform_device?提供可供每個設備驅動自定義的?platform_data?形式以支持添加一些數據信息,即 Linux 內核不對這塊的數據做要求。
device?結構體:
/** ?*?struct?device?-?The?basic?device?structure ...... ?*?@platform_data:?Platform?data?specific?to?the?device. ?*?????????Example:?For?devices?on?custom?boards,?as?typical?of?embedded ?*?????????and?SOC?based?hardware,?Linux?often?uses?platform_data?to?point ?*?????????to?board-specific?structures?describing?devices?and?how?they ?*?????????are?wired.??That?can?include?what?ports?are?available,?chip ?*?????????variants,?which?GPIO?pins?act?in?what?additional?roles,?and?so ?*?????????on.??This?shrinks?the?"Board?Support?Packages"?(BSPs)?and ?*?????????minimizes?board-specific?#ifdefs?in?drivers. ...... ?*/ struct?device?{ ????...... ????void?*platform_data;????/*?Platform?specific?data,?device ???????????????????????????core?doesn't?touch?it?*/ ????...... };
?
例如在?arch/arm/mach-at91/board-sam9261ek.c?板文件中,將?platform_data?定義了?dm9000_plat_data?結構體,完成定義后,將MAC地址、總線寬度、板上有無EEPROM信息等放入:
?
static?struct?dm9000_plat_data?dm9000_platdata?=?{ ????.flags?=?DM9000_PLATF_16BITONLY?|?DM9000_PLATF_NO_EEPROM, }; static?struct?platform_device?dm9000_device?=?{ ????.name?=?"dm9000", ????.id????=?0, ????.num_resources?=?ARRAY_SIZE(dm9000_resource), ????.resource?=?dm9000_resource, ????.dev?=?{ ????????.platform_data????=?&dm9000_platdata, ????} };
?
2.1.3 platform_device 的注冊
對于Linux 2.6 ARM 平臺而言,對?platform_device?的定義通常在 BSP 的板文件中實現,在板文件中,將?platform_device?歸納為一個數組,隨著板文件的加載,最終通過?platform_add_devices()?函數統一注冊。 platform_add_devices()?函數可以將平臺設備添加到系統中,這個函數的原型為:
int?platform_add_devices(struct?platform_device?**devs,?int?num)第一個參數為平臺設備數組的指針,第二個參數為平臺設備的數量,函數的內部是調用?platform_device_register()?函數逐一注冊平臺設備。 如果注冊順利,可在?sys/devices/platform?目錄下看到相應名字的子目錄。 Linux 3.x 之后,ARM Linux 不太以編碼的形式去填寫?platform_device?和注冊,更傾向于根據設備樹中的內容自動展開platform_device。
?
2.2 platform驅動
platform驅動:由?platform_driver?結構體構成,負責驅動的操作實現,例如加載、卸載、關閉、懸掛、恢復等。原型位于?linux/platform_driver.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; }; ?/*?@probe:????Called?to?query?the?existence?of?a?specific?device, ?*????????whether?this?driver?can?work?with?it,?and?bind?the?driver ?*????????to?a?specific?device. ?*?@remove:????Called?when?the?device?is?removed?from?the?system?to ?*????????unbind?a?device?from?this?driver. ?*?@shutdown:????Called?at?shut-down?time?to?quiesce?the?device. ?*?@suspend:????Called?to?put?the?device?to?sleep?mode.?Usually?to?a ?*????????low?power?state. ?*?@resume:????Called?to?bring?a?device?from?sleep?mode. ?*/probe()?和?remove()?分別對應驅動在加載、卸載時執行的操作。 而直接填充?platform_driver?的?suspend()、resume()?做電源管理回調的方法目前已經過時,較好的做法是實現?platfrom_driver?的?device_driver?中?dev_pm_ops?結構體成員。
?
2.2.1 device_driver 結構體
?
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; ???? ????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; };
?
與?platform_driver?地位對等的?i2c_driver、spi_driver、usb_driver、pci_driver中都包含了?device_driver結構體實例成員。它其實描述了各種 xxx_driver(xxx是總線名)在驅動意義上的一些共性。
2.2.2 驅動中獲取板的資源
獲取設備中?resource?資源:drivers/net/dm9000.c?中的?dm9000_probe()函數
????db->addr_res?=?platform_get_resource(pdev,?IORESOURCE_MEM,?0); ????db->data_res?=?platform_get_resource(pdev,?IORESOURCE_MEM,?1); ????db->irq_res??=?platform_get_resource(pdev,?IORESOURCE_IRQ,?0);或者:
db->irq_wake?=?platform_get_irq(pdev,?1);實際上是調用了?platform_get_resource(dev, IORESOURCE_IRQ, num); 獲取設備中?platform_data?資源:drivers/net/dm9000.c?中的?dm9000_probe()函數
struct?dm9000_plat_data?*pdata?=?pdev->dev.platform_data;
?
2.2.3 platform_driver 的注冊
通過?platform_driver_register()、platform_driver_unregister()?進行?platform_driver?的注冊于注銷。
?
static?int?__init dm9000_init(void) { ????printk(KERN_INFO?"%s?Ethernet?Driver,?V%s ",?CARDNAME,?DRV_VERSION); ???? ????return?platform_driver_register(&dm9000_driver); } static?void?__exit dm9000_cleanup(void) { ????platform_driver_unregister(&dm9000_driver); } module_init(dm9000_init); module_exit(dm9000_cleanup);而原本的字符設備(或其它設備)的注冊和注銷工作移交到?platform_driver?的?probe()?和?remove()?成員函數中。以這樣的形式對字符設備驅動進行注冊,只是套了一層?platform_driver?的外殼,并沒有改變是字符設備的本質。 例如在?drivers/net/dm9000.c?中,還是將其定義為網絡設備,只是將網絡設備驅動的注冊流程放在?probe()?中:
static?const?struct?net_device_ops?dm9000_netdev_ops?=?{ ????.ndo_open?=?dm9000_open, ????.ndo_stop?=?dm9000_stop, ????.ndo_start_xmit?=?dm9000_start_xmit, ????.ndo_tx_timeout?=?dm9000_timeout, ????.ndo_set_multicast_list?=?dm9000_hash_table, ????.ndo_do_ioctl?=?dm9000_ioctl, ????.ndo_change_mtu?=?eth_change_mtu, ????.ndo_set_features?=?dm9000_set_features, ????.ndo_validate_addr?=?eth_validate_addr, ????.ndo_set_mac_address?=?eth_mac_addr, #ifdef?CONFIG_NET_POLL_CONTROLLER ????.ndo_poll_controller?=?dm9000_poll_controller, #endif };
?
2.3 platform總線
platform總線:負責管理外設與驅動之間的匹配。
系統為 platfrom總線 定義了一個?bus_type?的實例?platform_bus_type,其定義位于?drivers/base/platform.c下:
?
struct?bus_type?platform_bus_type?=?{ ????.name?=?"platform", ????.dev_attrs?=?platform_dev_attrs, ????.match?=?platform_match, ????.uevent?=?platform_uevent, ????.pm????=?&platform_dev_pm_ops, };
?
2.3.1 .match 成員函數
重點關注其?match()?成員函數,此成員函數確定了?platform_device?和?platform_driver?之間是如何進行匹配的。
?
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); ????/*?Attempt?an?OF?style?match?first?*/ ????if?(of_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); }可以看出?platform_device?和?platform_driver?之間匹配有 3 種可能性:
?
基于設備樹風格的匹配;
匹配 ID 表(即 platform_device 設備名是否出現在 platform_driver 的 ID 表內);
匹配 platform_device 設備名和驅動的名字。
2.3.2 platform總線的注冊
?
start_kernel() ????rest_init() ????????kernel_init() ????????????do_basic_setup() ????????????????driver_init() ????????????????????platform_bus_init() ???? int?__init?platform_bus_init(void) { ????int?error; ????early_platform_cleanup();????????????????//早期的平臺清理 ????error?=?device_register(&platform_bus);??//注冊設備?(在/sys/devices/目錄下建立?platform目錄對應的設備對象??/sys/devices/platform/)? ????if?(error) ????????return?error; ????error?=??bus_register(&platform_bus_type);//總線注冊 ????if?(error) ????????device_unregister(&platform_bus); ????return?error; }
?
2.3.3 platform總線自動匹配
?
platform_device_register() ????platform_device_add() ????????device_add() ????????????bus_probe_device() ????????????????device_attach() ????????????????????bus_for_each_drv() ????????????????????????---------- platform_driver_register() ????driver_register() ????????bus_add_driver() ????????????driver_attach() ????????????????bus_for_each_dev()??????? ????????????????????---------無論是先注冊設備還是先注冊設備驅動,都會進行一次設備與設備驅動的匹配過程,匹配成功之后就會將其進行綁定,匹配的原理就是去遍歷總線下設備或者設備驅動的鏈表。
?
2.4 platform 的優點
使得設備被掛接在一個總線上,符合 Linux 2.6 以后內核的設備模型。其結果是使配套的 sysfs 節點、設備電源管理都成為可能。
將 BSP 和 驅動隔離。在 BSP 中定義 platform 設備和設備使用的資源、設備的具體配置信息,而在驅動中,只需要通過通用 API 去獲取資源和數據,做到了板相關代碼和驅動代碼的分離,使得驅動具有更好的可擴展性和跨平臺性。
讓一個驅動支持多個設備實例。譬如 DM9000 的驅動只有一份,但是我們可以在板級添加多份 DM9000 的 platform_device,他們都可以與唯一的驅動匹配。
在 Linux 3.x之后的內核中,DM9000 驅動可通過設備樹的方法被枚舉,添加的動作只需要簡單的修改 dts 文件。(詳細的后續再貼鏈接)
三、設備驅動的分層思想
在面向對象的程序設計中,可以為某一類相似的事物定義一個基類,而具體的事物可以繼承這個基類中的函數。如果對于繼承的這個事物而言,某成員函數的實現與基類一致,那它就可以直接繼承基類的函數;相反,它也可以重寫(Overriding),對父類的函數進行重新定義。 ? ? ? ?若子類中的方法與父類中的某方法具有相同的方法名、返回類型和參數表,則新方法將覆蓋原有的方法。這樣可以極大的提高代碼的可重用能力。 雖然 Linux 內核完全是由 C 和 匯編寫的,但卻頻繁用到了面向對象的設計思想。在設備驅動方面,往往為同類的設備設計一個框架,而框架中的核心層則實現了該設備通用的一些功能。同樣的,如果具體的設備不想使用核心層的函數,也可以重寫。
例1:
?
return_type?core_funca(xxx_device?*?bottom_dev,?param1_type?param1,?param1_type?param2) { ????if?(bottom_dev->funca) ????return?bottom_dev->funca(param1,?param2); ????/*?核心層通用的funca代碼?*/ ????... }在 core_funca() 函數的實現中,會檢查底層設備是否重載了 core_funca()。如果重載了,就調用底層的代碼,否則,直接使用通用層的。 這樣做的好處是,核心層的代碼可以處理絕大多數該類設備的 core_funca() 對應的功能,只有少數特殊設備需要重新實現 core_funca()。
?
例2:
?
return_type?core_funca(xxx_device?*?bottom_dev,?param1_type?param1,?param1_type?param2) { ????/*?通用的步驟代碼A?*/ ????typea_dev_commonA(); ????... ???? ????/*?底層操作?ops1?*/ ????bottom_dev->funca_ops1(); ???? ????/*?通用的步驟代碼B?*/ ????typea_dev_commonB(); ????... ????/*?底層操作?ops2?*/???? ????bottom_dev->funca_ops2(); ???? ????/*?通用的步驟代碼C?*/ ????typea_dev_commonC(); ????... ???? ????/*?底層操作?ops3?*/ ????bottom_dev->funca_ops3(); }上述代碼假定為了實現funca(),對于同類設備而言,操作流程一致,都要經過“通用代碼A、底層ops1、通用代碼B、底層ops2、通用代碼C、底層ops3”這幾步,分層設計明顯帶來的好處是,對于通用代碼A、B、C,具體的底層驅動不需要再實現(抽離出來,放到核心層實現),而僅僅只關心其底層的操作ops1、ops2、ops3。 下圖明確反映了設備驅動的核心層與具體設備驅動的關系,實際上,這種分層可能只有2層,也可能是多層。 這樣的分層設計在 Linux 的 Input、RTC、MTD、I2C、SPI、tty、USB等諸多類型設備驅動中都存在。
?
3.1 輸入設備驅動
輸入設備(如按鍵、鍵盤、觸摸屏、鼠標等)是典型的字符設備,其一般的工作機理是底層在按鍵、觸摸等動作發送時產生一個中斷(或驅動通過 Timer 定時查詢),然后CPU通過SPI、I2C 或外部存儲器總線讀取鍵值、坐標等數據,放入1個緩沖區,字符設備驅動管理該緩沖區,而驅動的 read() 接口讓用戶可以讀取鍵值、坐標等數據。 顯然,在這些工作中,只有中斷、讀值是設備相關的,而輸入事件的緩沖區管理以及字符設備驅動的 file_operations 接口則對輸入設備是通用的。基于此,內核設計了輸入子系統,由核心層處理公共的工作。
3.1.1 輸入核心提供了底層輸入設備驅動程序所需的API
如分配/釋放一個輸入設備:
?
struct?input_dev?*input_allocate_device(void);????//返回的結構體用于表征1個輸入設備。 void?input_free_device(struct?input_dev?*dev);
?
注冊/注銷輸入設備用的如下接口:
?
int?__must_check?input_register_device(struct?input_dev?*); void?input_unregister_device(struct?input_dev?*);
?
報告輸入事件用的如下接口:
?
/*?報告指定type、code的輸入事件?*/ void?input_event(struct?input_dev?*dev,?unsigned?int?type,?unsigned?int?code,?int?value); /*?報告鍵值?*/ void?input_report_key(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報告相對坐標?*/ void?input_report_rel(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報告絕對坐標?*/ void?input_report_abs(struct?input_dev?*dev,?unsigned?int?code,?int?value); /*?報告同步事件?*/ void?input_sync(struct?input_dev?*dev);
?
而所有的輸入事件,內核都用統一的數據結構來描述,這個數據結構是input_event:
?
struct?input_event?{ ????struct?timeval?time; ????__u16?type; ????__u16?code; ????__s32?value; };
?
3.1.2 案例:gpio按鍵驅動
drivers/input/keyboard/gpio_keys.c?是基于 input 架構實現的一個通用的 GPIO 按鍵驅動。該驅動基于 platform_driver架構,名為 “gpio-keys”。它將硬件相關的信息(如使用的GPIO號,電平等)屏蔽在板文件 platform_device 的 platform_data 中,因此該驅動可應用于各個處理器,具有良好的跨平臺性。
該驅動的?probe()?函數:
?
static?int?__devinit?gpio_keys_probe(struct?platform_device?*pdev) { ????...... ????input?=?input_allocate_device();????//分配一個輸入設備 ????......???????????????????????????????????? ????input->name?=?pdata->name???:?pdev->name;????//初始化該?input_dev?的一些屬性 ????input->phys?=?"gpio-keys/input0"; ????input->dev.parent?=?&pdev->dev; ????input->open?=?gpio_keys_open; ????input->close?=?gpio_keys_close; ???? ????input->id.bustype?=?BUS_HOST; ????input->id.vendor?=?0x0001; ????input->id.product?=?0x0001; ????input->id.version?=?0x0100;???????????????? ????...... ????for?(i?=?0;?i?nbuttons;?i++)?{????????//初始化所用到的?GPIO ????????struct?gpio_keys_button?*button?=?&pdata->buttons[i]; ????????struct?gpio_button_data?*bdata?=?&ddata->data[i]; ????????unsigned?int?type?=?button->type??:?EV_KEY; ???????? ????????bdata->input?=?input; ????????bdata->button?=?button; ???????? ????????error?=?gpio_keys_setup_key(pdev,?bdata,?button); ????????if?(error) ????????????goto?fail2; ???????? ????????if?(button->wakeup) ????????????wakeup?=?1; ???????? ????????input_set_capability(input,?type,?button->code); ????} ????...... ????error?=?input_register_device(input);????????//注冊輸入設備 ????...... }在注冊輸入設備后,底層輸入設備驅動的核心工作只剩下在按鍵、觸摸等人為動作發生時報告事件。在中斷服務函數中,GPIO 按鍵驅動通過?input_event()、input_sync()?這樣的函數來匯報按鍵事件以及同步事件。 從底層的 GPIO 按鍵驅動可以看出,該驅動中沒有任何 file_operation 的動作,也沒有各種 I/O 模型,注冊進入系統也用的是?input_register_device()?這樣與 input 相關的 API。 這是由于與 Linux VFS 接口的這一部分代碼全部都在?drivers/input/evdev.c?中實現了: input?核心層的 file_operations 和 read() 函數:
static?ssize_t?evdev_read(struct?file?*file,?char?__user?*buffer, ??????????????size_t?count,?loff_t?*ppos) { ????struct?evdev_client?*client?=?file->private_data; ????struct?evdev?*evdev?=?client->evdev; ????struct?input_event?event; ????int?retval; ????if?(count?f_flags?&?O_NONBLOCK))?{????????????????//檢查是否是非阻塞訪問 ????????????retval?=?wait_event_interruptible(evdev->wait, ????????????????client->packet_head?!=?client->tail?||?!evdev->exist); ????????if?(retval) ????????????return?retval; ????} ???? ????if?(!evdev->exist) ????????return?-ENODEV; ????while?(retval?+?input_event_size()?<=?count?&&????????//處理了阻塞的睡眠情況 ????????????evdev_fetch_next_event(client,?&event))?{ ???? ????????if?(input_event_to_user(buffer?+?retval,?&event)) ????????????return?-EFAULT; ???? ????????retval?+=?input_event_size(); ????} ????if?(retval?==?0?&&?file->f_flags?&?O_NONBLOCK) ????????retval?=?-EAGAIN; ????return?retval; }
?
3.2 RTC 設備驅動
RTC (實時時鐘)借助電池供電,在系統掉電的情況下依然可以正常計時。通常還具有產生周期性中斷以及鬧鐘中斷的能力,是一種典型的字符設備。 作為一種字符設備驅動,RTC 需要實現 file_operations 中的接口函數,例如 open()、read()等等。而 RTC 典型的 IOCTL 包括?RTC_SET_TIME、RTC_ALM_READ、RTC_ALM_SET、RTC_IRQP_SET、RTC_IRQP_READ等,這些對于 RTC 來說是通用的,那么這些通用的就放在 RTC 的核心層,而與設備相關的具體實現則放在底層。
與 RTC 核心有關的文件有: /drivers/rtc/class.c????????//該文件向linux設備模型核心注冊了一個類RTC,然后向驅動程序提供了注冊/注銷接口 /drivers/rtc/rtc-dev.c??????//該文件定義了基本的設備文件操作函數,如:open,read等 /drivers/rtc/interface.c????//該文件主要提供用戶程序與RTC驅動的接口函數,用戶程序一般通過ioctl與RTC??????????????????????????????//驅動交互,這里定義了每個ioctl命令需要調用的函數 /drivers/rtc/rtc-sysfs.c????//與sysfs有關 /drivers/rtc/rtc-proc.c?????//與proc文件系統有關 /include/linux/rtc.h????????//定義了與RTC有關的數據結構
?
RTC 驅動模型如下圖:
下面主要了解 RTC 核心 的以下幾點:
實現 file_operations 的成員函數以及一些通用的關于 RTC 的控制代碼;
向底層導出?rtc_device_register()、?rtc_device_unregister()以注冊和注銷 RTC;
導出 rtc_class_ops 結構體以描述底層的 RTC 硬件操作。
在這樣的驅動模型下,底層的 RTC 驅動不再需要關心 RTC 作為字符設備驅動的具體實現,也無需關心一些通用的 RTC 控制邏輯。關系如下:
以S3C6410 的 RTC驅動為例:
RTC 核心:
1. 在文件?drivers/rtc/rtc-dev.c?中:實現 file_operations 相關成員函數
?
static?const?struct?file_operations?rtc_dev_fops?=?{ ????.owner????????=?THIS_MODULE, ????.llseek????????=?no_llseek, ????.read????????=?rtc_dev_read, ????.poll????????=?rtc_dev_poll, ????.unlocked_ioctl????=?rtc_dev_ioctl, ????.open????????=?rtc_dev_open, ????.release????????????=?rtc_dev_release, ????.fasync????????=?rtc_dev_fasync, };
?
2. 在文件?drivers/rtc/class.c中:向底層提供注冊/注銷接口
?
struct?rtc_device?*rtc_device_register(const?char?*name,?struct?device?*dev, ????????????????????const?struct?rtc_class_ops?*ops, ????????????????????struct?module?*owner) void?rtc_device_unregister(struct?rtc_device?*rtc)
?
3. 在文件?drivers/rtc/class.h中:導出 rtc_class_ops 結構體
?
struct?rtc_class_ops?{ ????int?(*open)(struct?device?*); ????void?(*release)(struct?device?*); ????int?(*ioctl)(struct?device?*,?unsigned?int,?unsigned?long); ????int?(*read_time)(struct?device?*,?struct?rtc_time?*); ????int?(*set_time)(struct?device?*,?struct?rtc_time?*); ????int?(*read_alarm)(struct?device?*,?struct?rtc_wkalrm?*); ????int?(*set_alarm)(struct?device?*,?struct?rtc_wkalrm?*); ????int?(*proc)(struct?device?*,?struct?seq_file?*); ????int?(*set_mmss)(struct?device?*,?unsigned?long?secs); ????int?(*read_callback)(struct?device?*,?int?data); ????int?(*alarm_irq_enable)(struct?device?*,?unsigned?int?enabled); };
?
S3C6410底層:在drivers/rtc/rtc-s3c.c?文件中
其注冊 RTC 以及綁定 rtc_class_ops:
?
static?const?struct?rtc_class_ops?s3c_rtcops?=?{ ????.read_time????=?s3c_rtc_gettime, ????.set_time????=?s3c_rtc_settime, ????.read_alarm????=?s3c_rtc_getalarm, ????.set_alarm????=?s3c_rtc_setalarm, ????.alarm_irq_enable?=?s3c_rtc_setaie, }; static?int?__devinit?s3c_rtc_probe(struct?platform_device?*pdev) { ????...... ????/*?register?RTC?and?exit?*/ ????rtc?=?rtc_device_register("s3c",?&pdev->dev,?&s3c_rtcops, ??????????????????THIS_MODULE); ????...... }drivers/rtc/rtc-dev.c?以及其調用的drivers/rtc/interface.c?等 RTC 核心層相當于把 file_operations 中的 open()、release()、讀取和設置時間等,都間接 “轉發” 給了底層的實例。如下摘取部分 RTC 核心層調用具體底層驅動 callback 的過程:
?
1)open:
?
/*?文件 drivers/rtc/rtc-dev.c 中:?*/ static?int?rtc_dev_open(struct?inode?*inode,?struct?file?*file) { ????const?struct?rtc_class_ops?*ops?=?rtc->ops; ????...... ????err?=?ops->open???ops->open(rtc->dev.parent)?:?0; ????...... }
?
2)IOCTL的 命令:
?
/*?文件?drivers/rtc/rtc-dev.c?中?*/ static?long?rtc_dev_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg) { ????...... ????switch?(cmd)?{ ????case?RTC_ALM_READ: ????????......???? ????????err?=?rtc_read_alarm(rtc,?&alarm); ????????...... ????case?RTC_ALM_SET: ????????...... ????case?RTC_SET_TIME: ????????......???????? ????????return?rtc_set_time(rtc,?&tm); ????...... ????} ????...... } /*?文件?drivers/rtc/interface.c?中?*/ static?int?__rtc_read_time(struct?rtc_device?*rtc,?struct?rtc_time?*tm) { ????int?err; ????if?(!rtc->ops) ????????err?=?-ENODEV; ????else?if?(!rtc->ops->read_time)????????//回調 ????????err?=?-EINVAL; ????...... } ???? int?rtc_read_time(struct?rtc_device?*rtc,?struct?rtc_time?*tm) { ????...... ????err?=?__rtc_read_time(rtc,?tm); ????...... }
?
3.3 Framebuffer 設備驅動
未深入,參考《Linux設備驅動開發詳解:基于最新的Linux 4.0內核》
3.4 終端設備驅動
在 Linux 系統中,終端是一種字符型設備,它有多種類型,通常使用 tty (Teletype)來簡稱各種類型的終端設備。在嵌入式系統中,最常用的是 UART 串行端口。
3.4.1 內核中 tty 的層次結構
圖中包含三個層次:
tty_io.c:tty 核心;
n_tty.c:tty 線路規程;
xxx_tty.c:tty 驅動實例。
3.4.1.1 tty_io.c
tty_io.c?本身是一個標準的字符設備驅動,因此,它對上有字符設備的職責,需實現?file_operations?結構體成員函數。 但 tty 核心層對下又定義了?tty_driver?的架構,因此 tty 設備驅動的主體工作就變成了填充?tty_driver?結構體中的成員,實現其成員?tty_operations?結構體的成員函數,而不再是去實現?file_operations?結構體成員函數這一級的工作。
struct?tty_driver?{ ????...... ????/* ????*?Driver?methods ????*/ ???? ????const?struct?tty_operations?*ops; ????struct?list_head?tty_drivers; }; struct?tty_operations?{ ????struct?tty_struct?*?(*lookup)(struct?tty_driver?*driver, ????struct?inode?*inode,?int?idx); ????int??(*install)(struct?tty_driver?*driver,?struct?tty_struct?*tty); ????void?(*remove)(struct?tty_driver?*driver,?struct?tty_struct?*tty); ????int??(*open)(struct?tty_struct?*?tty,?struct?file?*?filp); ????void?(*close)(struct?tty_struct?*?tty,?struct?file?*?filp); ????void?(*shutdown)(struct?tty_struct?*tty); ????void?(*cleanup)(struct?tty_struct?*tty); ????int??(*write)(struct?tty_struct?*?tty, ????????const?unsigned?char?*buf,?int?count); ????...... ????const?struct?file_operations?*proc_fops; };
?
3.4.1.2 n_tty.c
n_tty.c:tty 線路規程的工作是以特殊的方式格式化從一個用戶或者硬件收到的數據,這種格式化常常采用一個協議轉換的形式。
3.4.2 tty 設備的發送/接收流程
發送流程:tty 核心從一個用戶獲取將要發送給一個 tty 設備的數據,tty 核心將數據傳遞給 tty 線路規程驅動,接著數據被傳遞到 tty 驅動,tty 驅動將數據轉換為可以發送給硬件的格式。 從?tty_driver?操作集?tty_operations?的成員函數 write() 函數接收3個參數:tty_struct、發送數據指針和發送的字節數。該函數是被?file_operations?的 write() 成員函數間接觸發調用的。 接收流程:從 tty 硬件接收到的數據向上交給 tty 驅動,接著進入 tty 線路規程驅動,再進入 tty 核心,在這里它被一個用戶獲取。 tty 驅動一般收到字符后會通過?tty_flip_buffer_push()?將接收緩沖區推到線路規程。
3.4.3 串口核心層
盡管一個特定的底層 UART 設備驅動完全可以遵循上述?tty_driver?的方法來設計,即定義tty_driver 并實現?tty_operations?中的成員函數,但是鑒于串口之間的共性,Linux 考慮在文件?drivers/tty/serial/serial_core.c?中實現 UART 設備的通用 tty 驅動層(稱為串口核心層)。這樣,UART 驅動的主要任務就進一步演變成了實現 文件?serial_core.c中定義的一組 uart_xxx 接口,而不是 tty_xxx 接口。 按照面向對象的思想,可認為 tty_driver 是字符設備的泛化、serial_core 是 tty_driver 的泛化,而具體的串口驅動又是 serial_core 的泛化。 在串口核心層又定義新的?uart_driver?結構體和其操作集?uart_ops。一個底層的 UART 驅動需要創建和通過?uart_register_driver()?注冊一個?uart_driver?而不是?tty_driver。
struct?uart_driver?{ ????struct?module???????*owner; ????const?char????????*driver_name; ????const?char????????*dev_name; ????int?????????????major; ????int?????????????minor; ????int?????????????nr; ????struct?console?????*cons; ???? ????/* ????*?these?are?private;?the?low?level?driver?should?not ????*?touch?these;?they?should?be?initialised?to?NULL ????*/ ????struct?uart_state????*state; ????struct?tty_driver????*tty_driver; }; int?uart_register_driver(struct?uart_driver?*drv); void?uart_unregister_driver(struct?uart_driver?*drv);uart_driver 結構體在本質上是派生自 tty_driver 結構體,因此,uart_driver 結構體中包含 tty_dirver 結構體成員。 tty_operations?在UART 這個層面上也被進一步泛化為?uart_ops:
struct?uart_ops?{ ????unsigned?int????(*tx_empty)(struct?uart_port?*); ????void????(*set_mctrl)(struct?uart_port?*,?unsigned?int?mctrl); ????unsigned?int????(*get_mctrl)(struct?uart_port?*); ????void????(*stop_tx)(struct?uart_port?*); ????void????(*start_tx)(struct?uart_port?*); ????void????(*send_xchar)(struct?uart_port?*,?char?ch); ????void????(*stop_rx)(struct?uart_port?*); ????void????(*enable_ms)(struct?uart_port?*); ????void????(*break_ctl)(struct?uart_port?*,?int?ctl); ????int?????(*startup)(struct?uart_port?*); ????void????(*shutdown)(struct?uart_port?*); ????void????(*flush_buffer)(struct?uart_port?*); ????void????(*set_termios)(struct?uart_port?*,?struct?ktermios?*new, ???????????struct?ktermios?*old); ????void????(*set_ldisc)(struct?uart_port?*,?int?new); ????void????(*pm)(struct?uart_port?*,?unsigned?int?state, ?????????unsigned?int?oldstate); ????int?????(*set_wake)(struct?uart_port?*,?unsigned?int?state); ????void????(*wake_peer)(struct?uart_port?*); ???? ????/* ????*?Return?a?string?describing?the?type?of?the?port ????*/ ????const?char?*(*type)(struct?uart_port?*); ???? ????/* ????*?Release?IO?and?memory?resources?used?by?the?port. ????*?This?includes?iounmap?if?necessary. ????*/ ????void????(*release_port)(struct?uart_port?*); ???? ????/* ????*?Request?IO?and?memory?resources?used?by?the?port. ????*?This?includes?iomapping?the?port?if?necessary. ????*/ ????int?????(*request_port)(struct?uart_port?*); ????void????(*config_port)(struct?uart_port?*,?int); ????int?????(*verify_port)(struct?uart_port?*,?struct?serial_struct?*); ????int?????(*ioctl)(struct?uart_port?*,?unsigned?int,?unsigned?long); #ifdef?CONFIG_CONSOLE_POLL ????void????(*poll_put_char)(struct?uart_port?*,?unsigned?char); ????int?????(*poll_get_char)(struct?uart_port?*); #endif };由于?driver/tty/serial/serial_core.c?是一個?tty_driver?,因此在 serial_core.c 中,存在一個 tty_operations 的實例,這個實例的成員函數會進一步調用 struct uart_ops 的成員函數,這樣就把 file_operaions 里的成員函數、tty_operations 的成員函數和 uart_ops 的成員函數串起來。
?
3.5 misc 設備驅動
......
3.6 驅動核心層
核心層的 3 大職責:
對上提供接口。file_operations 的讀、寫、ioctl 都被中間層搞定,各種 I/O 模型也被處理掉了。
中間層實現通用邏輯。可以被底層各種實例共享的代碼都被中間層搞定,避免底層重復實現。
對下定義框架。底層的驅動不再需要關心 Linux 內核 VFS 的接口和各種可能的 I/O 模型,而只需處理與具體硬件相關的訪問。
這種分層有時候還不是兩層,可以有更多層,在軟件上呈現為面向對象里類繼承和多態的狀態。
四、主機驅動與外設驅動分離的設計思想
4.1 主機驅動與外設驅動分離
Linux 中的 SPI、I2C、USB 等子系統都是典型的利用主機驅動和外設驅動分離的思想。 讓主機端只負責產生總線上的傳輸波形,而外設端只是通過標準的 API 來讓主機端以適當的波形訪問自身。涉及 4 個軟件模塊: 1. 主機端的驅動。根據具體的 SPI、I2C、USB 等控制器的硬件手冊,操作具體的控制器,產生總線的各種波形。 2. 連接主機和外設的紐帶。外設不直接調用主機端的驅動來產生波形,而是調用一個標準的 API。由這個標準的 API 把這個波形的傳輸請求間接 “轉發” 給具體的主機端驅動。最好在這里把關于波形的描述也以某種數據結構標準化。 3. 外設端的驅動。外設接在 SPI、I2C、USB 這樣的總線上,但是它們本身可以是觸摸屏、網卡、聲卡或任意一種類型的設備。當這些外設要求 SPI 、I2C、USB等去訪問它的時候,它調用 “連接主機和外設的紐帶” 模塊的標準 API。 4. 板級邏輯。用來描述主機和外設是如何互聯的,它相當于一個 “路由表”。假設板子上有多個 SPI 控制器和多個 SPI 外設,那究竟誰接在誰上面?管理互聯關系,既不是主機端的責任,也不是外設端的責任,這屬于板級邏輯的責任。 linux 通過上述設計方法,劃分為 4 個輕量級的小模塊,各個模塊各司其職。
4.2 Linux SPI 主機和設備驅動
4.2.1 SPI 主機驅動
在 Linux 中,通過?spi_master?結構體來描述一個 SPI 主動控制器驅動其主要成員由主機控制器的序號、片選數量、SPI 模式、時鐘設置相關函數 和 數據傳輸相關函數。 文件spi/spi.h?中
struct?spi_master?{ ????struct?device?dev; ???? ????struct?list_head?list; ???? ????/*?other?than?negative?(==?assign?one?dynamically),?bus_num?is?fully ????*?board-specific.??usually?that?simplifies?to?being?SOC-specific. ????*?example:??one?SOC?has?three?SPI?controllers,?numbered?0..2, ????*?and?one?board's?schematics?might?show?it?using?SPI-2.??software ????*?would?normally?use?bus_num=2?for?that?controller. ????*/ ????s16????????????bus_num; ???? ????/*?chipselects?will?be?integral?to?many?controllers;?some?others ????*?might?use?board-specific?GPIOs. ????*/ ????u16????????????num_chipselect; ???? ????/*?some?SPI?controllers?pose?alignment?requirements?on?DMAable ????*?buffers;?let?protocol?drivers?know?about?these?requirements. ????*/ ????u16????????????dma_alignment; ???? ????/*?spi_device.mode?flags?understood?by?this?controller?driver?*/ ????u16????????????mode_bits; ???? ????/*?other?constraints?relevant?to?this?driver?*/ ????u16????????????flags; ????#define?SPI_MASTER_HALF_DUPLEX????BIT(0)????????/*?can't?do?full?duplex?*/ ????#define?SPI_MASTER_NO_RX????BIT(1)????????/*?can't?do?buffer?read?*/ ????#define?SPI_MASTER_NO_TX????BIT(2)????????/*?can't?do?buffer?write?*/ ???? ????/*?lock?and?mutex?for?SPI?bus?locking?*/ ????spinlock_t?bus_lock_spinlock; ????struct?mutex?bus_lock_mutex; ???? ????/*?flag?indicating?that?the?SPI?bus?is?locked?for?exclusive?use?*/ ????bool?bus_lock_flag; ???? ????/*?Setup?mode?and?clock,?etc?(spi?driver?may?call?many?times). ????* ????*?IMPORTANT:??this?may?be?called?when?transfers?to?another ????*?device?are?active.??DO?NOT?UPDATE?SHARED?REGISTERS?in?ways ????*?which?could?break?those?transfers. ????*/ ????int?(*setup)(struct?spi_device?*spi); ???? ????/*?bidirectional?bulk?transfers ????* ????*?+?The?transfer()?method?may?not?sleep;?its?main?role?is ????*???just?to?add?the?message?to?the?queue. ????*?+?For?now?there's?no?remove-from-queue?operation,?or ????*???any?other?request?management ????*?+?To?a?given?spi_device,?message?queueing?is?pure?fifo ????* ????*?+?The?master's?main?job?is?to?process?its?message?queue, ????*???selecting?a?chip?then?transferring?data ????*?+?If?there?are?multiple?spi_device?children,?the?i/o?queue ????*???arbitration?algorithm?is?unspecified?(round?robin,?fifo, ????*???priority,?reservations,?preemption,?etc) ????* ????*?+?Chipselect?stays?active?during?the?entire?message ????*???(unless?modified?by?spi_transfer.cs_change?!=?0). ????*?+?The?message?transfers?use?clock?and?SPI?mode?parameters ????*???previously?established?by?setup()?for?this?device ????*/ ????int?(*transfer)(struct?spi_device?*spi, ????????????struct?spi_message?*mesg); ???? ????/*?called?on?release()?to?free?memory?provided?by?spi_master?*/ ????void?(*cleanup)(struct?spi_device?*spi); };分配、注冊和注銷 SPI 主機的 API 由 SPI 核心提供:文件?drivers/spi/spi.c
struct?spi_master?*spi_alloc_master(struct?device?*dev,?unsigned?size); int?spi_register_master(struct?spi_master?*master); void?spi_unregister_master(struct?spi_master?*master);SPI 主機控制器驅動主體是實現了?spi_master?的 transfer()、setup() 這樣的成員函數。也可能實現?spi_bitbang?的 txrx_buf()、setup_transfer()、chipselect() 這樣的成員函數。
?
例如在文件?driver/spi/spi_s3c24xx.c?中:
?
static?int?__init?s3c24xx_spi_probe(struct?platform_device?*pdev) { ????struct?s3c2410_spi_info?*pdata; ????struct?s3c24xx_spi?*hw; ????struct?spi_master?*master; ????struct?resource?*res; ????...... ????/*?initialise?fiq?handler?*/ ???? ????s3c24xx_spi_initfiq(hw); ???? ????/*?setup?the?master?state.?*/ ???? ????/*?the?spi->mode?bits?understood?by?this?driver:?*/ ????master->mode_bits?=?SPI_CPOL?|?SPI_CPHA?|?SPI_CS_HIGH;????//設置模式 ???? ????master->num_chipselect?=?hw->pdata->num_cs;???????????????//設置片選序號 ????master->bus_num?=?pdata->bus_num;??????????????????????//主機控制器的序號 ???? ????/*?setup?the?state?for?the?bitbang?driver?*/ ???? ????hw->bitbang.master?????????=?hw->master; ????hw->bitbang.setup_transfer?=?s3c24xx_spi_setupxfer; ????hw->bitbang.chipselect?????=?s3c24xx_spi_chipsel; ????hw->bitbang.txrx_bufs??????=?s3c24xx_spi_txrx; ???? ????hw->master->setup??=?s3c24xx_spi_setup; ????hw->master->cleanup?=?s3c24xx_spi_cleanup; ????...... }
?
4.2.2 紐帶
......
4.2.3 SPI 外設驅動
在 Linux 中,通過 spi_driver 結構體來描述一個 SPI 外設驅動,這個外設驅動可以認為是 spi_mater 的客戶端驅動。SPI 只是一種總線,spi_driver 的作用只是將 SPI 外設掛接在該總線上,因此在 spi_driver 的 probe() 成員函數中,將注冊 SPI 外設本身所屬設備驅動的類型。 文件?spi/spi.h?中:
struct?spi_driver?{ ????const?struct?spi_device_id?*id_table; ????int????(*probe)(struct?spi_device?*spi); ????int????(*remove)(struct?spi_device?*spi); ????void?(*shutdown)(struct?spi_device?*spi); ????int????(*suspend)(struct?spi_device?*spi,?pm_message_t?mesg); ????int????(*resume)(struct?spi_device?*spi); ????struct?device_driver?driver; }; static?int?spi_drv_probe(struct?device?*dev) { ????const?struct?spi_driver?*sdrv?=?to_spi_driver(dev->driver); ????return?sdrv->probe(to_spi_device(dev)); } int?spi_register_driver(struct?spi_driver?*sdrv) { ????sdrv->driver.bus?=?&spi_bus_type; ????if?(sdrv->probe) ????????sdrv->driver.probe?=?spi_drv_probe; ????if?(sdrv->remove) ????????sdrv->driver.remove?=?spi_drv_remove; ????if?(sdrv->shutdown) ????????sdrv->driver.shutdown?=?spi_drv_shutdown; ????return?driver_register(&sdrv->driver); }可看出,spi_driver 結構體 和 platform_driver 結構體有極大的相似性,都有 prob()、remove()、suspend()、resume()這樣的接口和 device_driver 的實例。(這幾乎是一切客戶端驅動的常用模板)
?
在SPI 外設驅動中(文件?spi/spi.h?與?driver/spi/spi.c?):
1.?spi_tansfer?結構體:通過 SPI 總線進行數據傳輸的接口。 2.?spi_message?結構體:組織一個或多個spi_transfer,從而完成一次完整的 SPI 傳輸流程。 3. 初始化?spi_message:
static?inline?void?spi_message_init(struct?spi_message?*m);4. 將?spi_transfer?添加到?spi_message?隊列:
spi_message_add_tail(struct?spi_transfer?*t,?struct?spi_message?*m);5.?spi_message?的同步傳輸 API,阻塞等待這個消息被處理完:
spi_sync(struct?spi_device?*spi,?struct?spi_message?*message);6.?spi_message?的異步傳輸 API,不會阻塞等待這個消息被處理完,但可在 spi_message 的?complete?字段掛接一個回調函數,當消息被處理完成后,該函數會被調用:
spi_async(struct?spi_device?*spi,?struct?spi_message?*message);7. 初始化?spi_transfer、spi_message?并進行 SPI 數據傳輸的例子,同時?spi_write()?、?spi_read()?也是SPI 核心層的兩個通用API,在外設驅動中可直接調用進行簡單的純寫、純讀操作:
static?inline?int spi_write(struct?spi_device?*spi,?const?void?*buf,?size_t?len) { ????struct?spi_transfer?t?=?{ ????????????.tx_buf?=?buf, ????????????.len????=?len, ????????}; ????struct?spi_message?m; ???? ????spi_message_init(&m); ????spi_message_add_tail(&t,?&m); ????return?spi_sync(spi,?&m); } static?inline?int spi_read(struct?spi_device?*spi,?void?*buf,?size_t?len) { ????struct?spi_transfer????t?=?{ ????????.rx_buf????=?buf, ????????.len????=?len, ????}; ????struct?spi_message????m; ???? ????spi_message_init(&m); ????spi_message_add_tail(&t,?&m); ????return?spi_sync(spi,?&m); }
?
4.2.4 SPI 板級邏輯
通 platform_driver 對應著一個platform_device一樣,spi_driver 也對應著一個 spi_device;platform_device 需要在 BSP 的板文件中添加板信息數據,同樣的 spi_device 也需要。 spi_device 的板信息用?spi_board_info?結構體描述,該結構體記錄著 SPI 外設使用的主機控制器序號、片選序號、數據比特率、SPI 傳輸模式等。?? 兩種方式添加板級信息: 1. 與 platfrom_add_devices 添加 platform_device 類似,通過?spi_register_board_info()?在 Linux 啟動過程中的 機器?init_machine()?函數中進行注冊: 在文件?arch/arm/mach-exynos/mach-itop4412.c?中:
static?struct?spi_board_info?spi_board_info[]?__initdata?=?{ ????{ ????????.modalias????=?"lms501kf03", ????????.platform_data????=?NULL, ????????.max_speed_hz????=?1200000, ????????.bus_num????=?LCD_BUS_NUM, ????????.chip_select????=?0, ????????.mode????????=?SPI_MODE_3, ????????.controller_data?=?(void?*)DISPLAY_CS, ????} }; spi_register_board_info(spi_board_info,?ARRAY_SIZE(spi_board_info));2. 在 ARM Linux 3.x 之后的內核在改為設備樹后,不再需要正在?arch/arm/mach-xxx?中編碼 SPI 的板級信息了,而傾向于在 SPI 控制器節點下填寫子節點。
?
審核編輯:湯梓紅
評論
查看更多