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;
}
中斷處理流程
- 讀取中斷狀態暫存器:當中斷發生時,首先讀取 CY8C9520A 的中斷狀態暫存器,以瞭解哪些 GPIO 發生了變化。
- 檢查未被遮罩的 GPIO:對於每個埠,檢查哪些 GPIO 的中斷未被遮罩,並將結果儲存在
pending暫存器中。 - 執行對應的 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;
}
內容解密:
- 中斷狀態讀取:透過
i2c_smbus_read_byte_data讀取中斷狀態暫存器(REG_INTERRUPT_STATUS),判斷是否有待處理的中斷。 - 迴圈處理中斷源:使用
__ffs函式找出第一個被設定的位元,即第一個觸發中斷的 GPIO。 - 清除中斷標記:清除對應的 pending 位元,避免重複處理。
- 巢狀中斷處理:透過
handle_nested_irq呼叫對應的 GPIO 子驅動程式 ISR,完成中斷處理流程。
PWM 控制實作
CY8C9520A 的 PWM 控制涵蓋週期、佔空比的組態以及使能/停用控制,主要由以下幾個函式實作:
cy8c9520a_pwm_config:設定 PWM 訊號的週期與佔空比(單位為奈秒)。cy8c9520a_pwm_enable:啟用指定的 PWM 輸出。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;
}
內容解密:
- 引數轉換:將奈秒單位的
period_ns和duty_ns轉換為晶片內部時鐘單位。 - 邊界檢查:檢查
period是否超出最大允許值,確保組態的有效性。 - 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;
}
內容解密:
- 初始硬體狀態設定:將所有 PWM 停用,並將 GPIO 設定為輸入模式。
- 輸出暫存器快取:讀取並快取輸出暫存器的初始狀態。
- PWM 時鐘源組態:為每個 PWM 通道設定預設時鐘源。
中斷處理在裝置驅動程式中的應用
在 Linux 核心中,中斷處理是裝置驅動程式的重要組成部分。對於像 cy8c9520a 這樣的 GPIO 擴充套件晶片,中斷處理機制允許系統有效地處理來自外部裝置的中斷請求。本文將探討 cy8c9520a 驅動程式中的中斷處理實作。
cy8c9520a 中斷設定
cy8c9520a_irq_setup 函式負責設定 cy8c9520a 裝置的中斷處理機制。該函式主要執行以下任務:
清除中斷狀態暫存器:透過讀取三個中斷狀態暫存器(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。 - 這一步驟是必要的,因為它確保了中斷狀態被正確清除,避免了誤觸發的中斷。
- 這段程式碼使用
初始化中斷遮罩暫存器:設定中斷遮罩暫存器(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; } }內容解密:
- 首先,使用
memset將irq_mask_cache和irq_mask陣列初始化為0xff,表示所有 GPIO 中斷都被遮罩(停用)。 - 然後,迴圈遍歷每個埠,選擇對應的埠並寫入中斷遮罩暫存器,以停用該埠上的所有 GPIO 中斷。
- 如果寫入失敗,則輸出錯誤訊息並跳轉到錯誤處理。
- 首先,使用
請求中斷:使用
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)。 - 如果請求失敗,則輸出錯誤訊息並傳回錯誤碼。
- 該函式請求一個執行緒化的中斷處理程式
設定巢狀 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,並啟用執行緒化中斷處理。 - 設定
threaded為true表示使用執行緒化的中斷處理機制,這有助於處理耗時的中斷服務常式。
- 這段程式碼初始化了
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_sleep為true表示該 GPIO 控制器可以在睡眠模式下操作。 - 指定了 GPIO 操作的相關函式指標,如
direction_input、direction_output、get和set。 - 將
owner設定為THIS_MODULE表示該 GPIO 控制器隸屬於當前的模組。