Linux 核心中的 USB 裝置驅動程式開發涉及 USB 子系統和核心模組的理解。本文以 LTC3206 Demo Board 為例,講解如何開發 USB 到 I2C 的橋接驅動程式,實作對外部 I2C 裝置的控制。驅動程式核心功能包含 I2C 訊息傳輸處理與 USB 裝置初始化。I2C 訊息傳輸透過 ltc3206_usb_i2c_xfer 函式實作,該函式負責將多個 I2C 訊息透過 USB 介面傳送至 LTC3206。USB 裝置初始化則在 ltc3206_probe 函式中完成,包含組態記憶體、設定 USB 端點、初始化 LTC3206,以及註冊 I2C 介面卡。文章也提供程式碼片段說明 I2C 寫入操作、USB 中斷輸出 URB 的完成回呼函式等關鍵部分,並以 Raspberry Pi 與 PIC32MX470 Curiosity 開發板的實際操作案例,展示如何載入驅動模組、檢查 I2C 介面卡,並透過 I2C 指令控制 LED 亮度,驗證驅動程式的功能。
USB裝置驅動程式與I2C匯流排的整合應用
本章節將探討USB裝置驅動程式的開發,特別是如何將USB與I2C匯流排結合,以實作對外部裝置的有效控制。文中將以LTC3206 DC749A Demo Board為例,詳細說明驅動程式的實作細節。
驅動程式初始化與設定
首先,我們需要開啟I2C驅動程式並取得其控制程式碼,以進行後續的資料傳輸。
appData.drvI2CHandle_Master = DRV_I2C_Open(DRV_I2C_INDEX_0, DRV_IO_INTENT_WRITE);
if (appData.drvI2CHandle_Master == (DRV_HANDLE)NULL)
{
appInitialized = false;
}
內容解密:
DRV_I2C_Open函式用於開啟I2C驅動程式。DRV_I2C_INDEX_0指定了要開啟的I2C驅動程式索引。DRV_IO_INTENT_WRITE表示開啟I2C驅動程式的目的是進行寫入操作。- 若傳回的控制程式碼為
NULL,則表示開啟失敗,將appInitialized設為false。
接著,我們需要開啟USB裝置層,以實作USB裝置的功能。
if (appData.handleUsbDevice == USB_DEVICE_HANDLE_INVALID)
{
appData.handleUsbDevice = USB_DEVICE_Open(USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE);
if(appData.handleUsbDevice != USB_DEVICE_HANDLE_INVALID)
{
appInitialized = true;
}
else
{
appInitialized = false;
}
}
內容解密:
USB_DEVICE_Open函式用於開啟USB裝置。USB_DEVICE_INDEX_0指定了要開啟的USB裝置索引。DRV_IO_INTENT_READWRITE表示開啟USB裝置的目的是進行讀寫操作。- 若傳回的控制程式碼非
USB_DEVICE_HANDLE_INVALID,則表示開啟成功,將appInitialized設為true。
驅動程式核心程式碼解析
- 標頭檔引入與ID表格建立
首先,我們需要引入必要的標頭檔,並建立ID表格以支援熱插拔功能。
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>
#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
{ }
};
MODULE_DEVICE_TABLE(usb, id_table);
內容解密:
引入必要的Linux核心模組開發標頭檔。
定義USB裝置的Vendor ID和Product ID。
建立
usb_device_id表格,以匹配特定的USB裝置。使用
MODULE_DEVICE_TABLE巨集註冊該ID表格。私有資料結構定義
接下來,我們定義一個私有資料結構來儲存驅動程式的相關資料。
struct i2c_ltc3206 {
u8 obuffer[LTC3206_OUTBUF_LEN];
u8 user_data_buffer[LTC3206_I2C_DATA_LEN];
int ep_out;
struct usb_device *usb_dev;
struct usb_interface *interface;
struct i2c_adapter adapter;
wait_queue_head_t usb_urb_completion_wait;
bool ongoing_usb_ll_op;
struct urb *interrupt_out_urb;
};
內容解密:
該結構儲存了與USB和I2C通訊相關的資料。
obuffer和user_data_buffer分別用於儲存USB寫入緩衝區和I2C資料緩衝區。ep_out儲存了輸出端點的位址。usb_dev和interface分別指向對應的USB裝置和介面。adapter用於I2C介面卡的操作。probe()函式實作
當USB裝置被插入時,probe()函式將被呼叫,以初始化相關的資料結構和組態。
static int ltc3206_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_host_interface *hostif = interface->cur_altsetting;
struct i2c_ltc3206 *dev;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->ep_out = hostif->endpoint[1].desc.bEndpointAddress;
dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
dev->interface = interface;
init_waitqueue_head(&dev->usb_urb_completion_wait);
usb_set_intfdata(interface, dev);
dev->adapter.owner = THIS_MODULE;
dev->adapter.class = I2C_CLASS_HWMON;
dev->adapter.algo = <c3206_usb_algorithm;
i2c_set_adapdata(&dev->adapter, dev);
dev->adapter.dev.parent = &dev->interface->dev;
ltc3206_init(dev);
i2c_add_adapter(&dev->adapter);
return 0;
}
內容解密:
- 組態並初始化私有資料結構。
- 設定輸出端點位址、取得USB裝置和介面的參考。
- 初始化等待佇列以處理URB完成的等待事件。
- 設定I2C介面卡的相關屬性,並將其註冊到I2C子系統中。
I2C傳輸方法的實作
我們需要實作I2C傳輸方法,以支援從使用者空間到I2C裝置的資料傳輸。
static const struct i2c_algorithm ltc3206_usb_algorithm = {
.master_xfer = ltc3206_usb_i2c_xfer,
.functionality = ltc3206_usb_func,
};
內容解密:
- 定義了一個
i2c_algorithm結構體,用於描述I2C傳輸的方法。 .master_xfer成員指向了實際執行I2C傳輸的函式ltc3206_usb_i2c_xfer。
USB 裝置驅動程式開發:以 LTC3206 為例
在 Linux 核心中開發 USB 裝置驅動程式是一項複雜的任務,需要深入瞭解 USB 子系統、核心模組以及裝置特定的通訊協定。本文將以 LTC3206 為例,介紹如何開發一個 USB 到 I2C 的橋接驅動程式。
驅動程式初始化
首先,我們需要定義驅動程式的結構和初始化函式。在這個例子中,我們定義了一個 struct i2c_ltc3206 結構來儲存裝置的相關資訊,包括 USB 裝置、介面、I2C 配接器等。
內容解密:
struct i2c_ltc3206 {
u8 obuffer[LTC3206_OUTBUF_LEN]; /* USB 寫入緩衝區 */
u8 user_data_buffer[LTC3206_I2C_DATA_LEN]; /* I2C/SMBus 資料緩衝區 */
int ep_out; /* 輸出端點 */
struct usb_device *usb_dev; /* USB 裝置 */
struct usb_interface *interface; /* USB 介面 */
struct i2c_adapter adapter; /* I2C 配接器 */
wait_queue_head_t usb_urb_completion_wait; /* 等待佇列 */
bool ongoing_usb_ll_op; /* 是否正在進行 USB 低階操作 */
struct urb *interrupt_out_urb; /* 中斷輸出 URB */
};
在 ltc3206_init 函式中,我們初始化了 LTC3206 裝置,包括分配和初始化中斷輸出 URB。
程式碼解析
static int ltc3206_init(struct i2c_ltc3206 *dev)
{
int ret;
/* 初始化 LTC3206 */
dev_info(&dev->interface->dev,
"LTC3206 at USB bus %03d address %03d -- ltc3206_init()\n",
dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
/* 分配中斷輸出 URB */
dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->interrupt_out_urb) {
ret = -ENODEV;
goto init_error;
}
/* 初始化中斷輸出 URB */
usb_fill_int_urb(dev->interrupt_out_urb, dev->usb_dev,
usb_sndintpipe(dev->usb_dev, dev->ep_out),
(void *)&dev->obuffer, LTC3206_OUTBUF_LEN,
ltc3206_usb_cmpl_cbk, dev,
1);
ret = 0;
goto init_no_error;
init_error:
dev_err(&dev->interface->dev, "ltc3206_init: Error = %d\n", ret);
return ret;
init_no_error:
dev_info(&dev->interface->dev, "ltc3206_init: Success\n");
return ret;
}
I2C 寫入操作
在 ltc3206_i2c_write 函式中,我們實作了 I2C 寫入操作,將資料從 I2C 訊息複製到 USB 寫入緩衝區。
程式碼解析
static int ltc3206_i2c_write(struct i2c_ltc3206 *dev, struct i2c_msg *pmsg)
{
u8 ucXferLen;
int rv;
u8 *pSrc, *pDst;
if (pmsg->len > LTC3206_I2C_DATA_LEN) {
pr_info("problem with the length\n");
return -EINVAL;
}
/* I2C 寫入長度 */
ucXferLen = (u8)pmsg->len;
pSrc = &pmsg->buf[0];
pDst = &dev->obuffer[0];
// ...
}
USB 中斷輸出 URB 的完成回呼函式
在 ltc3206_usb_cmpl_cbk 函式中,我們處理了中斷輸出 URB 的完成事件,包括檢查 URB 狀態、重新提交 URB 等。
程式碼解析
static void ltc3206_usb_cmpl_cbk(struct urb *urb)
{
struct i2c_ltc3206 *dev = urb->context;
int status = urb->status;
int retval;
switch (status) {
case 0: /* 成功 */
break;
case -ECONNRESET: /* 連線重設 */
case -ENOENT:
case -ESHUTDOWN:
return;
/* -EPIPE: 應該清除 halt */
default: /* 錯誤 */
goto resubmit;
}
/*
* 喚醒等待函式。
* 修改標誌表示低階狀態
*/
dev->ongoing_usb_ll_op = 0; /* 通訊正常 */
wake_up_interruptible(&dev->usb_urb_completion_wait);
return;
resubmit:
retval = usb_submit_urb(urb, GFP_ATOMIC);
if (retval) {
dev_err(&dev->interface->dev,
"ltc3206(irq): can't resubmit interrupt urb, retval %d\n",
retval);
}
}
USB裝置驅動程式章節13 - LTC3206 USB-I2C橋接器驅動實作與示範
本章節探討LTC3206 USB-I2C橋接器驅動程式的開發與應用,涵蓋從驅動程式設計到實際硬體整合的完整流程。文中詳細介紹了驅動程式的核心功能實作、USB裝置的識別與初始化、I2C匯流排操作,以及如何透過Raspberry Pi與PIC32MX470 Curiosity開發板進行實測驗證。
驅動程式核心功能實作
I2C訊息傳輸處理
驅動程式中的ltc3206_usb_i2c_xfer函式負責處理I2C訊息的傳輸,其主要任務是將多個I2C訊息透過USB介面傳送至LTC3206裝置。此函式的實作邏輯如下:
static int ltc3206_usb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct i2c_ltc3206 *dev = i2c_get_adapdata(adap);
struct i2c_msg *pmsg;
int ret, count;
pr_info("number of i2c msgs is = %d\n", num);
for (count = 0; count < num; count++) {
pmsg = &msgs[count];
ret = ltc3206_i2c_write(dev, pmsg);
if (ret < 0)
goto abort;
}
ret = num;
abort:
return ret;
}
內容解密:
- 此函式接收I2C介面卡(
i2c_adapter)、I2C訊息陣列(i2c_msg)和訊息數量(num)作為輸入引數。 - 透過迴圈遍歷每個I2C訊息,並呼叫
ltc3206_i2c_write函式將訊息寫入LTC3206裝置。 - 若寫入過程中發生錯誤,則立即跳至
abort標籤傳回錯誤碼。 - 若所有訊息均成功傳輸,則傳回傳輸的訊息數量。
USB裝置初始化與設定
在ltc3206_probe函式中,驅動程式會對USB裝置進行初始化和設定:
static int ltc3206_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
struct usb_host_interface *hostif = interface->cur_altsetting;
struct i2c_ltc3206 *dev;
int ret;
// 組態USB裝置並註冊I2C介面卡
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev) {
ret = -ENOMEM;
goto error;
}
// 初始化LTC3206裝置
ret = ltc3206_init(dev);
if (ret < 0) {
dev_err(&interface->dev, "failed to initialize adapter\n");
goto error_init;
}
// 新增I2C介面卡
ret = i2c_add_adapter(&dev->adapter);
if (ret < 0) {
dev_info(&interface->dev, "failed to add I2C adapter\n");
goto error_i2c;
}
return 0;
error_init:
usb_free_urb(dev->interrupt_out_urb);
error_i2c:
usb_set_intfdata(interface, NULL);
ltc3206_free(dev);
error:
return ret;
}
內容解密:
- 此函式在USB裝置被識別時呼叫,負責組態和初始化裝置。
- 組態過程中包括分配記憶體、設定USB端點、初始化LTC3206裝置,以及註冊I2C介面卡。
- 若初始化或註冊過程中發生錯誤,則進行相應的錯誤處理和資源釋放。
示範與驗證
本文展示如何在Raspberry Pi上載入LTC3206驅動模組,並透過I2C指令控制連線的PIC32MX470 Curiosity開發板上的LED亮度。
載入驅動模組:使用
insmod指令載入編譯好的usb_ltc3206.ko模組。root@raspberrypi:/home# insmod usb_ltc3206.ko檢查I2C介面卡:使用
i2cdetect -l指令檢視系統中的I2C介面卡。載入模組後,應能看到新增的I2C介面卡。root@raspberrypi:/home# i2cdetect -l i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter i2c-11 i2c usb-ltc3206 at bus 001 device 004 I2C adapter控制LED亮度:透過
i2cset指令向LTC3206裝置傳送指令,以控制PIC32MX470板上的LED亮度。root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0xf0 0x00 i
內容解密:
- 此指令透過I2C介面卡11(對應LTC3206裝置)向位址0x1b的裝置傳送控制指令。
- 指令引數(0x00、0xf0、0x00)用於設定LTC3206裝置的輸出,從而控制連線的LED亮度。