CY8C9520A 是一款根據 I2C 介面的 GPIO 擴充套件晶片,其內建的中斷功能對於提升系統反應速度至關重要。本文除了硬體架構的說明外,也探討了驅動程式如何透過 I2C 讀取中斷狀態、使用 mutex 進行同步控制,以及如何透過 handle_nested_irq 呼叫對應的中斷服務程式。此外,文章也詳細說明瞭中斷遮罩的設定方法,以及如何使用 cypress_get_port 和 cypress_get_offs 進行 GPIO 編號與連線埠的對映。最後,文章也涵蓋了 Device Tree 的相關設定,讓讀者能完整理解 CY8C9520A 中斷處理的軟硬體整合流程。
深入解析 CY8C9520A 中斷處理機制
CY8C9520A 是一款根據 I2C 介面的 GPIO 擴充套件晶片,其內部具備豐富的中斷處理功能,能夠有效提升系統的即時回應能力。本文將針對 CY8C9520A 的中斷處理機制進行深入解析,涵蓋其硬體架構、中斷處理流程以及相關的驅動程式實作。
中斷處理架構設計
CY8C9520A 的中斷處理機制建立在堅實的硬體基礎上,主要包含以下關鍵元件:
中斷暫存器架構
- REG_INTR_STAT_PORT0~2:用於記錄各個 GPIO 連線埠的中斷狀態
- REG_INTR_MASK:控制中斷的遮罩設定,用於啟用或停用特定 GPIO 的中斷功能
I2C 介面同步機制
- 使用
mutex_lock確保在存取 I2C 匯流排時的執行緒安全 - 透過
i2c_smbus_read_i2c_block_data讀取中斷狀態暫存器
- 使用
中斷處理流程
- 當 GPIO 狀態發生變化時,晶片會產生中斷訊號通知主控器
- 中斷服務程式(ISR)讀取中斷狀態暫存器以判斷觸發中斷的 GPIO
程式碼實作解析
static irqreturn_t cy8c9520a_irq_handler(int irq, void *devid)
{
struct cy8c9520a *cygpio = devid;
u8 stat[NPORTS], pending;
unsigned port, gpio, gpio_irq;
int ret;
pr_info("the interrupt ISR has been entered\n");
// 讀取並清除中斷狀態暫存器
ret = i2c_smbus_read_i2c_block_data(cygpio->client, REG_INTR_STAT_PORT0, NPORTS, stat);
if (ret < 0) {
memset(stat, 0, sizeof(stat));
}
ret = IRQ_NONE;
for (port = 0; port < NPORTS; port++) {
mutex_lock(&cygpio->irq_lock);
// 計算實際待處理的中斷
pending = stat[port] & (~cygpio->irq_mask[port]);
mutex_unlock(&cygpio->irq_lock);
// 處理該連線埠上的所有待處理中斷
while (pending) {
ret = IRQ_HANDLED;
gpio = __ffs(pending);
pending &= ~BIT(gpio);
gpio_irq = cy8c9520a_port_offs[port] + gpio;
// 呼叫對應的下層中斷處理程式
handle_nested_irq(irq_find_mapping(cygpio->gpio_chip.irq.domain, gpio_irq));
}
}
return ret;
}
內容解密:
中斷狀態讀取與清除
- 使用
i2c_smbus_read_i2c_block_data一次讀取多個連線埠的中斷狀態 - 讀取動作同時會清除中斷狀態暫存器,以避免重複觸發中斷
- 使用
同步鎖機制
- 使用
mutex_lock確保在存取共用資源時的執行緒安全 - 鎖定範圍僅涵蓋必要的 I2C 存取操作,以提高系統平行處理能力
- 使用
中斷來源識別
- 透過
stat[port] & (~cygpio->irq_mask[port])計算實際待處理的中斷請求 - 使用
__ffs(pending)高效地找出第一個待處理的 GPIO
- 透過
中斷組態與控制
CY8C9520A 的中斷組態涉及多個關鍵函式,主要功能包括:
中斷遮罩控制
cy8c9520a_irq_mask:停用指定的 GPIO 中斷cy8c9520a_irq_unmask:啟用指定的 GPIO 中斷
中斷觸發模式設定
cy8c9520a_irq_set_type:設定中斷的觸發模式(例如邊緣觸發)
相關程式碼實作
static void cy8c9520a_irq_mask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
cygpio->irq_mask[port] |= BIT(cypress_get_offs(gpio, port));
}
static void cy8c9520a_irq_unmask(struct irq_data *d)
{
u8 port;
struct gpio_chip *chip = irq_data_get_irq_chip_data(d);
struct cy8c9520a *cygpio = gpiochip_get_data(chip);
unsigned gpio = d->hwirq;
port = cypress_get_port(gpio);
cygpio->irq_mask[port] &= ~BIT(cypress_get_offs(gpio, port));
}
內容解密:
中斷遮罩管理
- 使用位元操作直接修改
irq_mask暫存器的內容 - 確保對遮罩設定的修改能夠即時反映到硬體組態中
- 使用位元操作直接修改
連線埠對映機制
- 使用
cypress_get_port和cypress_get_offs將 GPIO 編號轉換為連線埠號和位元偏移量 - 這種對映機制使得程式碼能夠靈活地適應不同的硬體組態
- 使用
CY8C9520A 驅動程式中的中斷處理機制
CY8C9520A 是一款透過 I2C 介面擴充套件 GPIO 的裝置,其驅動程式實作了中斷處理機制,以提升系統的即時性和效率。本文將探討該驅動程式的中斷處理流程及其相關實作細節。
初始化與設定
在 cy8c9520a_probe 函式中,驅動程式首先檢查 I2C 介面的功能是否支援 SMBus Byte 和 Block 操作,接著分配私有結構 cy8c9520a 並進行初始化。在完成裝置識別後,驅動程式依序呼叫 cy8c9520a_setup、cy8c9520a_gpio_init 和 cy8c9520a_irq_setup 函式,分別進行裝置設定、GPIO 初始化和中斷設定。
程式碼範例:裝置初始化
static int cy8c9520a_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
struct cy8c9520a *cygpio;
int ret;
unsigned int dev_id;
// 檢查 I2C 功能支援
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK | I2C_FUNC_SMBUS_BYTE_DATA)) {
dev_err(&client->dev, "SMBUS Byte/Block unsupported\n");
return -EIO;
}
// 分配私有結構並初始化
cygpio = devm_kzalloc(&client->dev, sizeof(*cygpio), GFP_KERNEL);
if (!cygpio) {
dev_err(&client->dev, "failed to alloc memory\n");
return -ENOMEM;
}
cygpio->client = client;
mutex_init(&cygpio->lock);
// 裝置識別
dev_id = i2c_smbus_read_byte_data(client, REG_DEVID_STAT);
if (dev_id < 0) {
dev_err(&client->dev, "can't read device ID\n");
ret = dev_id;
goto err;
}
dev_info(&client->dev, "dev_id=0x%x\n", dev_id & 0xff);
// 裝置設定、GPIO 初始化和中斷設定
ret = cy8c9520a_setup(cygpio);
if (ret < 0) {
goto err;
}
ret = cy8c9520a_gpio_init(cygpio);
if (ret) {
goto err;
}
ret = cy8c9520a_irq_setup(cygpio);
if (ret) {
goto err;
}
// 將 I2C 裝置與 cygpio 裝置連結
i2c_set_clientdata(client, cygpio);
return 0;
err:
mutex_destroy(&cygpio->lock);
return ret;
}
內容解密:
- 檢查 I2C 功能支援:使用
i2c_check_functionality檢查介面是否支援必要的 SMBus 操作,以確保裝置的正常運作。 - 分配私有結構:使用
devm_kzalloc分配cy8c9520a結構,並初始化相關欄位。 - 裝置識別:透過讀取
REG_DEVID_STAT暫存器來識別裝置 ID,以確認裝置的正確性。 - 裝置設定、GPIO 初始化和中斷設定:依序呼叫相關函式完成裝置的初始化和中斷處理的設定。
中斷處理設定
cy8c9520a_irq_setup 函式負責設定中斷處理機制,主要步驟包括清除中斷狀態、初始化中斷遮罩暫存器、新增巢狀 IRQ chip 到 GPIO chip,以及請求外部處理器的 GPIO pin 中斷。
程式碼範例:中斷處理設定
static int cy8c9520a_irq_setup(struct cy8c9520a *cygpio)
{
struct i2c_client *client = cygpio->client;
struct gpio_chip *chip = &cygpio->gpio_chip;
u8 dummy[NPORTS];
int ret, i;
// 清除中斷狀態
ret = i2c_smbus_read_i2c_block_data(client, REG_INTR_STAT_PORT0, NPORTS, dummy);
if (ret < 0) {
dev_err(&client->dev, "couldn't clear int status\n");
goto err;
}
// 初始化中斷遮罩暫存器
memset(cygpio->irq_mask_cache, 0xff, sizeof(cygpio->irq_mask_cache));
memset(cygpio->irq_mask, 0xff, sizeof(cygpio->irq_mask));
for (i = 0; i < NPORTS; i++) {
ret = i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
if (ret < 0) {
dev_err(&client->dev, "can't select port %u\n", i);
goto err;
}
ret = i2c_smbus_write_byte_data(client, REG_INTR_MASK, cygpio->irq_mask[i]);
if (ret < 0) {
dev_err(&client->dev, "can't write int mask on port %u\n", i);
goto err;
}
}
// 新增巢狀 IRQ chip 到 GPIO chip
ret = gpiochip_irqchip_add_nested(chip, &cy8c9520a_irq_chip, 0, handle_simple_irq, IRQ_TYPE_NONE);
if (ret) {
dev_err(&client->dev, "could not connect irqchip to gpiochip\n");
return ret;
}
// 請求外部處理器的 GPIO pin 中斷
ret = devm_request_threaded_irq(&client->dev, client->irq, NULL, cy8c9520a_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_HIGH, dev_name(&client->dev), cygpio);
if (ret) {
dev_err(&client->dev, "failed to request irq %d\n", cygpio->irq);
return ret;
}
gpiochip_set_nested_irqchip(chip, &cy8c9520a_irq_chip, client->irq);
return 0;
err:
mutex_destroy(&cygpio->irq_lock);
return ret;
}
內容解密:
- 清除中斷狀態:透過讀取中斷狀態暫存器來清除未處理的中斷,以確保系統的穩定性。
- 初始化中斷遮罩暫存器:將中斷遮罩暫存器初始化為預設值,以停用所有 GPIO 線上的中斷。
- 新增巢狀 IRQ chip 到 GPIO chip:使用
gpiochip_irqchip_add_nested將巢狀 IRQ chip 新增到 GPIO chip,以實作中斷處理的層級結構。 - 請求外部處理器的 GPIO pin 中斷:使用
devm_request_threaded_irq請求外部處理器的 GPIO pin 中斷,以實作中斷驅動的 GPIO 操作。
深入理解裝置驅動程式中的中斷處理(GPIO 中斷驅動開發實務)
在 Linux 驅動程式開發中,中斷處理是一項至關重要的技術。本文將探討如何在裝置驅動程式中處理中斷,特別是在 GPIO 擴充套件晶片 CY8C9520A 的應用情境下。
GPIO 中斷驅動程式架構
在 LAB 7.4 中,我們開發了一個名為 int_rpi3_gpio 的 GPIO 子驅動程式,用於向 CY8C9520A GPIO 控制器請求中斷。當 CY8C9520A 的 P0 連線埠第一個輸入線路發生電平變化時,會觸發中斷輸出(INT),進而呼叫中斷處理常式 cy8c9520a_irq_handler()。該處理常式會進一步呼叫 handle_nested_irq(),最終執行我們 GPIO 子驅動程式中的中斷處理常式 P0_line0_isr()。
程式碼實作
// int_rpi3_gpio.c 中的關鍵程式碼片段
static irqreturn_t P0_line0_isr(int irq, void *data)
{
struct device *dev = data;
dev_info(dev, "interrupt received. key: %s\n", INT_NAME);
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
// ...
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "irq is not available\n");
return -EINVAL;
}
// ...
ret_val = devm_request_threaded_irq(dev, irq, NULL, P0_line0_isr,
IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
INT_NAME, dev);
// ...
}
程式碼解析
- 中斷處理常式註冊:在
my_probe()函式中,我們使用platform_get_irq()取得 Linux IRQ 號。 - 申請中斷線:透過
devm_request_threaded_irq()函式申請中斷線,並指定P0_line0_isr()為中斷處理常式。 - 中斷觸發條件:設定中斷觸發條件為上升沿和下降沿觸發(
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING)。
Device Tree 設定
為了使驅動程式正常運作,我們需要在 Device Tree 中新增相關節點。以下是在 bcm2710-rpi-3-b.dts 中新增的 int_gpio 節點範例:
int_gpio {
compatible = "arrow,int_gpio_expand";
pinctrl-names = "default";
interrupt-parent = <&cy8c9520a>;
interrupts = <0 IRQ_TYPE_EDGE_BOTH>;
};
設定解析
interrupt-parent:指定中斷父裝置為cy8c9520a節點,即我們的 CY8C9520A GPIO 控制器驅動程式。interrupts:指定中斷線號為 0,對應 CY8C9520A P0 連線埠的第一個輸入線路。
使用者空間中斷處理應用
除了在驅動程式層級處理中斷外,我們還可以在使用者空間透過 GPIOlib API 處理中斷。具體實作請參考 Listing 7-6 中的 gpio_int.c 程式碼。
使用者空間處理流程
- 開啟
/dev/gpiochip3字元裝置檔案。 - 使用
ioctl()系統呼叫設定 GPIO 中斷。 - 等待中斷發生並進行相應處理。