返回文章列表

CY8C9520A 驅動程式中斷與 PWM 控制

本文深入分析 CY8C9520A 驅動程式,解析其中斷處理與 PWM 控制的實作細節。涵蓋中斷處理函式、中斷遮罩與解除遮罩、PWM 設定與控制邏輯,以及初始化流程與暫存器組態。透過程式碼範例與詳細說明,闡明 CY8C9520A 驅動程式如何有效管理 GPIO 中斷及 PWM 訊號輸出。

嵌入式系統 驅動程式開發

CY8C9520A 是一款根據 I2C 介面的 GPIO 與 PWM 擴充套件晶片,其驅動程式在 Linux 核心中的實作至關重要。本文將探討 CY8C9520A 驅動程式如何處理 GPIO 中斷以及如何組態和控制 PWM 輸出。首先,我們會分析中斷處理函式 cy8c9520a_irq_handler 如何讀取中斷狀態、執行對應的 ISR,以及如何使用中斷遮罩機制。接著,我們會探討 PWM 控制的實作,包含週期、佔空比的設定,以及 PWM 啟用和停用。最後,我們會簡述驅動程式的初始化流程,包含 GPIO 和 PWM 的初始設定,以及相關暫存器的組態。

在裝置驅動程式中處理中斷(Handling Interrupts in Device Drivers)

7.3 CY8C9520A GPIO 擴充套件晶片的中斷處理

CY8C9520A 是一款透過 I2C 介面擴充套件 GPIO 的晶片,它支援中斷功能,能夠在 GPIO 狀態變化時通知處理器。本章節將探討如何在裝置驅動程式中處理 CY8C9520A 的中斷。

中斷處理函式(Interrupt Handler)

中斷處理函式是當 GPIO 發生中斷時被呼叫的函式。在 CY8C9520A 的驅動程式中,中斷處理函式為 cy8c9520a_irq_handler

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);
        // 檢查哪些 GPIO 發生了中斷並且未被遮罩
        pending = stat[port] & (~cygpio->irq_mask[port]);
        mutex_unlock(&cygpio->irq_lock);

        // 執行對應的 ISR(Interrupt Service Routine)
        while (pending) {
            ret = IRQ_HANDLED;
            // 省略部分程式碼
        }
    }
    return ret;
}

中斷處理流程

  1. 讀取中斷狀態暫存器:當中斷發生時,首先讀取 CY8C9520A 的中斷狀態暫存器,以瞭解哪些 GPIO 發生了變化。
  2. 檢查未被遮罩的 GPIO:對於每個埠,檢查哪些 GPIO 的中斷未被遮罩,並將結果儲存在 pending 暫存器中。
  3. 執行對應的 ISR:對於每個發生中斷且未被遮罩的 GPIO,執行其對應的 ISR。

#### 內容解密:

  • cy8c9520a_irq_handler 函式是 CY8C9520A 的中斷處理函式,當 GPIO 發生中斷時被呼叫。
  • 該函式首先讀取 CY8C9520A 的中斷狀態暫存器,以瞭解哪些 GPIO 發生了變化。
  • 然後,對於每個埠,檢查哪些 GPIO 的中斷未被遮罩,並將結果儲存在 pending 暫存器中。
  • 最後,對於每個發生中斷且未被遮罩的 GPIO,執行其對應的 ISR。

中斷相關的 Callback 函式

CY8C9520A 的驅動程式定義了多個與中斷相關的 callback 函式,這些函式用於設定和控制中斷。

中斷遮罩(Mask)和解除遮罩(Unmask)

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);
    dev_info(chip->parent, "cy8c9520a_irq_mask is called\n");
    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);
    dev_info(chip->parent, "cy8c9520a_irq_unmask is called\n");
    cygpio->irq_mask[port] &= ~BIT(cypress_get_offs(gpio, port));
}

#### 內容解密:

  • cy8c9520a_irq_mask 函式用於遮罩(停用)指定的 GPIO 中斷。
  • cy8c9520a_irq_unmask 函式用於解除遮罩(啟用)指定的 GPIO 中斷。
  • 這兩個函式透過修改 irq_mask 陣列來實作對中斷的控制。

CY8C9520A 驅動程式中的中斷處理與 PWM 控制實作分析

CY8C9520A 是一款透過 I2C 介面擴充套件 GPIO 及 PWM 功能的晶片,其驅動程式實作涵蓋中斷處理、GPIO 控制及 PWM 訊號組態等功能。本文將探討其驅動程式的關鍵實作細節,包含中斷處理流程、PWM 組態與控制邏輯,以及相關的設計考量。

中斷處理機制實作

CY8C9520A 的中斷處理流程主要由 cy8c9520a_irq_handler 函式負責,該函式作為中斷服務常式(ISR)的主要入口。以下是其核心邏輯的詳細分析:

static irqreturn_t cy8c9520a_irq_handler(int irq, void *data)
{
    struct cy8c9520a *cygpio = data;
    struct i2c_client *client = cygpio->client;
    u8 pending;
    int gpio, gpio_irq, port;
    int ret = IRQ_NONE;

    /* 讀取中斷狀態 */
    ret = i2c_smbus_read_byte_data(client, REG_INTERRUPT_STATUS);
    if (ret < 0) {
        dev_err(&client->dev, "無法讀取中斷狀態\n");
        return IRQ_NONE;
    }
    pending = ret;

    while (pending) {
        /* 取得第一個觸發中斷的 GPIO */
        gpio = __ffs(pending);
        /* 清除對應的 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 IRQ_HANDLED;
}

內容解密:

  1. 中斷狀態讀取:透過 i2c_smbus_read_byte_data 讀取中斷狀態暫存器(REG_INTERRUPT_STATUS),判斷是否有待處理的中斷。
  2. 迴圈處理中斷源:使用 __ffs 函式找出第一個被設定的位元,即第一個觸發中斷的 GPIO。
  3. 清除中斷標記:清除對應的 pending 位元,避免重複處理。
  4. 巢狀中斷處理:透過 handle_nested_irq 呼叫對應的 GPIO 子驅動程式 ISR,完成中斷處理流程。

PWM 控制實作

CY8C9520A 的 PWM 控制涵蓋週期、佔空比的組態以及使能/停用控制,主要由以下幾個函式實作:

  1. cy8c9520a_pwm_config:設定 PWM 訊號的週期與佔空比(單位為奈秒)。
  2. cy8c9520a_pwm_enable:啟用指定的 PWM 輸出。
  3. cy8c9520a_pwm_disable:停用指定的 PWM 輸出。

PWM 組態邏輯分析

static int cy8c9520a_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
                                int duty_ns, int period_ns)
{
    int period = period_ns / PWM_TCLK_NS;
    int duty = duty_ns / PWM_TCLK_NS;
    struct cy8c9520a *cygpio = container_of(chip, struct cy8c9520a, pwm_chip);
    struct i2c_client *client = cygpio->client;

    /* 檢查週期上限 */
    if (period > PWM_MAX_PERIOD) {
        dev_err(&client->dev, "週期超出範圍 [0-%d]ns\n", PWM_MAX_PERIOD * PWM_TCLK_NS);
        return -EINVAL;
    }

    mutex_lock(&cygpio->lock);
    /* 設定 PWM 編號、週期與佔空比 */
    i2c_smbus_write_byte_data(client, REG_PWM_SELECT, pwm->pwm);
    i2c_smbus_write_byte_data(client, REG_PWM_PERIOD, period);
    i2c_smbus_write_byte_data(client, REG_PWM_PULSE_W, duty);
    mutex_unlock(&cygpio->lock);

    return 0;
}

內容解密:

  1. 引數轉換:將奈秒單位的 period_nsduty_ns 轉換為晶片內部時鐘單位。
  2. 邊界檢查:檢查 period 是否超出最大允許值,確保組態的有效性。
  3. I2C 設定:依序寫入 PWM 編號、週期與佔空比暫存器,完成組態。

初始化流程與暫存器組態

在驅動程式初始化階段,cy8c9520a_setup 函式負責進行必要的硬體組態:

static int cy8c9520a_setup(struct cy8c9520a *cygpio)
{
    int ret, i;
    struct i2c_client *client = cygpio->client;

    /* 停用所有 PWM,設定 GPIO 為輸入模式 */
    for (i = 0; i < NPORTS; i++) {
        i2c_smbus_write_byte_data(client, REG_PORT_SELECT, i);
        i2c_smbus_write_byte_data(client, REG_SELECT_PWM, 0x00);
        i2c_smbus_write_byte_data(client, REG_PIN_DIR, 0xff);
    }

    /* 快取輸出暫存器狀態 */
    i2c_smbus_read_i2c_block_data(client, REG_OUTPUT_PORT0,
                                  sizeof(cygpio->outreg_cache),
                                  cygpio->outreg_cache);

    /* 設定預設 PWM 時鐘源 */
    for (i = 0; i < NPWM; i++) {
        i2c_smbus_write_byte_data(client, REG_PWM_SELECT, i);
        i2c_smbus_write_byte_data(client, REG_PWM_CLK, PWM_CLK);
    }

    return 0;
}

內容解密:

  1. 初始硬體狀態設定:將所有 PWM 停用,並將 GPIO 設定為輸入模式。
  2. 輸出暫存器快取:讀取並快取輸出暫存器的初始狀態。
  3. PWM 時鐘源組態:為每個 PWM 通道設定預設時鐘源。

中斷處理在裝置驅動程式中的應用

在 Linux 核心中,中斷處理是裝置驅動程式的重要組成部分。對於像 cy8c9520a 這樣的 GPIO 擴充套件晶片,中斷處理機制允許系統有效地處理來自外部裝置的中斷請求。本文將探討 cy8c9520a 驅動程式中的中斷處理實作。

cy8c9520a 中斷設定

cy8c9520a_irq_setup 函式負責設定 cy8c9520a 裝置的中斷處理機制。該函式主要執行以下任務:

  1. 清除中斷狀態暫存器:透過讀取三個中斷狀態暫存器(Interrupt Status Port0、Interrupt Status Port1 和 Interrupt Status Port2),將其值儲存在一個虛擬陣列中,以清除中斷狀態。

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

    內容解密:

    • 這段程式碼使用 i2c_smbus_read_i2c_block_data 函式從 cy8c9520a 裝置讀取中斷狀態暫存器的值,並將其儲存在 dummy 陣列中。
    • 如果讀取失敗,則輸出錯誤訊息並跳轉到錯誤處理標籤 err
    • 這一步驟是必要的,因為它確保了中斷狀態被正確清除,避免了誤觸發的中斷。
  2. 初始化中斷遮罩暫存器:設定中斷遮罩暫存器(Interrupt Mask Port Register),以停用所有 GPIO 線路上的中斷。

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

    內容解密:

    • 首先,使用 memsetirq_mask_cacheirq_mask 陣列初始化為 0xff,表示所有 GPIO 中斷都被遮罩(停用)。
    • 然後,迴圈遍歷每個埠,選擇對應的埠並寫入中斷遮罩暫存器,以停用該埠上的所有 GPIO 中斷。
    • 如果寫入失敗,則輸出錯誤訊息並跳轉到錯誤處理。
  3. 請求中斷:使用 devm_request_threaded_irq 函式請求一個執行緒化的中斷處理程式。

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

    內容解密:

    • 該函式請求一個執行緒化的中斷處理程式 cy8c9520a_irq_handler,並設定中斷觸發條件為高電平觸發(IRQF_TRIGGER_HIGH)和一次性觸發(IRQF_ONESHOT)。
    • 如果請求失敗,則輸出錯誤訊息並傳回錯誤碼。
  4. 設定巢狀 IRQ 處理器:為 gpio_chip 結構體設定巢狀 IRQ 處理器,以允許子驅動程式請求中斷。

    girq = &cygpio->gpio_chip.irq;
    girq->chip = &cy8c9520a_irq_chip;
    girq->parent_handler = NULL;
    girq->num_parents = 0;
    girq->parents = NULL;
    girq->default_type = IRQ_TYPE_NONE;
    girq->handler = handle_simple_irq;
    girq->threaded = true;
    

    內容解密:

    • 這段程式碼初始化了 gpio_chip 的 IRQ 相關欄位,將 IRQ 處理器設定為 handle_simple_irq,並啟用執行緒化中斷處理。
    • 設定 threadedtrue 表示使用執行緒化的中斷處理機制,這有助於處理耗時的中斷服務常式。

GPIO 控制器的初始化

cy8c9520a_gpio_init 函式負責初始化 cy8c9520a 的 GPIO 控制器,並將其註冊到核心中。

static void cy8c9520a_gpio_init(struct cy8c9520a *cygpio)
{
    struct gpio_chip *gpiochip = &cygpio->gpio_chip;
    gpiochip->label = cygpio->client->name;
    gpiochip->base = -1;
    gpiochip->ngpio = NGPIO;
    gpiochip->parent = &cygpio->client->dev;
    gpiochip->of_node = gpiochip->parent->of_node;
    gpiochip->can_sleep = true;
    gpiochip->direction_input = cy8c9520a_gpio_direction_input;
    gpiochip->direction_output = cy8c9520a_gpio_direction_output;
    gpiochip->get = cy8c9520a_gpio_get;
    gpiochip->set = cy8c9520a_gpio_set;
    gpiochip->owner = THIS_MODULE;
}

內容解密:

  • 初始化 gpio_chip 結構體的各個欄位,包括標籤、GPIO 數量、父裝置節點等。
  • 設定 can_sleeptrue 表示該 GPIO 控制器可以在睡眠模式下操作。
  • 指定了 GPIO 操作的相關函式指標,如 direction_inputdirection_outputgetset
  • owner 設定為 THIS_MODULE 表示該 GPIO 控制器隸屬於當前的模組。