CY8C9520A 是一款根據 I2C 介面的 GPIO 擴充套件晶片,其內建的中斷功能對於提升系統反應速度至關重要。本文除了硬體架構的說明外,也探討了驅動程式如何透過 I2C 讀取中斷狀態、使用 mutex 進行同步控制,以及如何透過 handle_nested_irq 呼叫對應的中斷服務程式。此外,文章也詳細說明瞭中斷遮罩的設定方法,以及如何使用 cypress_get_portcypress_get_offs 進行 GPIO 編號與連線埠的對映。最後,文章也涵蓋了 Device Tree 的相關設定,讓讀者能完整理解 CY8C9520A 中斷處理的軟硬體整合流程。

深入解析 CY8C9520A 中斷處理機制

CY8C9520A 是一款根據 I2C 介面的 GPIO 擴充套件晶片,其內部具備豐富的中斷處理功能,能夠有效提升系統的即時回應能力。本文將針對 CY8C9520A 的中斷處理機制進行深入解析,涵蓋其硬體架構、中斷處理流程以及相關的驅動程式實作。

中斷處理架構設計

CY8C9520A 的中斷處理機制建立在堅實的硬體基礎上,主要包含以下關鍵元件:

  1. 中斷暫存器架構

    • REG_INTR_STAT_PORT0~2:用於記錄各個 GPIO 連線埠的中斷狀態
    • REG_INTR_MASK:控制中斷的遮罩設定,用於啟用或停用特定 GPIO 的中斷功能
  2. I2C 介面同步機制

    • 使用 mutex_lock 確保在存取 I2C 匯流排時的執行緒安全
    • 透過 i2c_smbus_read_i2c_block_data 讀取中斷狀態暫存器
  3. 中斷處理流程

    • 當 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;
}

內容解密:

  1. 中斷狀態讀取與清除

    • 使用 i2c_smbus_read_i2c_block_data 一次讀取多個連線埠的中斷狀態
    • 讀取動作同時會清除中斷狀態暫存器,以避免重複觸發中斷
  2. 同步鎖機制

    • 使用 mutex_lock 確保在存取共用資源時的執行緒安全
    • 鎖定範圍僅涵蓋必要的 I2C 存取操作,以提高系統平行處理能力
  3. 中斷來源識別

    • 透過 stat[port] & (~cygpio->irq_mask[port]) 計算實際待處理的中斷請求
    • 使用 __ffs(pending) 高效地找出第一個待處理的 GPIO

中斷組態與控制

CY8C9520A 的中斷組態涉及多個關鍵函式,主要功能包括:

  1. 中斷遮罩控制

    • cy8c9520a_irq_mask:停用指定的 GPIO 中斷
    • cy8c9520a_irq_unmask:啟用指定的 GPIO 中斷
  2. 中斷觸發模式設定

    • 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));
}

內容解密:

  1. 中斷遮罩管理

    • 使用位元操作直接修改 irq_mask 暫存器的內容
    • 確保對遮罩設定的修改能夠即時反映到硬體組態中
  2. 連線埠對映機制

    • 使用 cypress_get_portcypress_get_offs 將 GPIO 編號轉換為連線埠號和位元偏移量
    • 這種對映機制使得程式碼能夠靈活地適應不同的硬體組態

CY8C9520A 驅動程式中的中斷處理機制

CY8C9520A 是一款透過 I2C 介面擴充套件 GPIO 的裝置,其驅動程式實作了中斷處理機制,以提升系統的即時性和效率。本文將探討該驅動程式的中斷處理流程及其相關實作細節。

初始化與設定

cy8c9520a_probe 函式中,驅動程式首先檢查 I2C 介面的功能是否支援 SMBus Byte 和 Block 操作,接著分配私有結構 cy8c9520a 並進行初始化。在完成裝置識別後,驅動程式依序呼叫 cy8c9520a_setupcy8c9520a_gpio_initcy8c9520a_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;
}

內容解密:

  1. 檢查 I2C 功能支援:使用 i2c_check_functionality 檢查介面是否支援必要的 SMBus 操作,以確保裝置的正常運作。
  2. 分配私有結構:使用 devm_kzalloc 分配 cy8c9520a 結構,並初始化相關欄位。
  3. 裝置識別:透過讀取 REG_DEVID_STAT 暫存器來識別裝置 ID,以確認裝置的正確性。
  4. 裝置設定、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;
}

內容解密:

  1. 清除中斷狀態:透過讀取中斷狀態暫存器來清除未處理的中斷,以確保系統的穩定性。
  2. 初始化中斷遮罩暫存器:將中斷遮罩暫存器初始化為預設值,以停用所有 GPIO 線上的中斷。
  3. 新增巢狀 IRQ chip 到 GPIO chip:使用 gpiochip_irqchip_add_nested 將巢狀 IRQ chip 新增到 GPIO chip,以實作中斷處理的層級結構。
  4. 請求外部處理器的 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);
    // ...
}

程式碼解析

  1. 中斷處理常式註冊:在 my_probe() 函式中,我們使用 platform_get_irq() 取得 Linux IRQ 號。
  2. 申請中斷線:透過 devm_request_threaded_irq() 函式申請中斷線,並指定 P0_line0_isr() 為中斷處理常式。
  3. 中斷觸發條件:設定中斷觸發條件為上升沿和下降沿觸發(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 程式碼。

使用者空間處理流程

  1. 開啟 /dev/gpiochip3 字元裝置檔案。
  2. 使用 ioctl() 系統呼叫設定 GPIO 中斷。
  3. 等待中斷發生並進行相應處理。