返回文章列表

Linux裝置驅動Regmap API應用與ADXL345實作

本文探討在Linux裝置驅動程式中使用Regmap API的技巧,並以ADXL345加速計為例,詳細解析如何透過Regmap進行SPI通訊、中斷處理和資料擷取。文章涵蓋Regmap組態、中斷處理函式、觸發器處理函式以及裝置初始化等關鍵環節,提供開發者實務參考。

嵌入式系統 驅動程式開發

Regmap API 提供簡化的暫存器存取介面,有效提升驅動程式開發效率。本文以 ADXL345 加速計為例,說明如何整合 Regmap API 管理 SPI 通訊與中斷。首先,透過 regmap_config 結構體設定暫存器和數值的位元寬度,並定義讀取旗標遮罩以支援多位元組讀取。在 SPI 探測函式中,使用 devm_regmap_init_spi 初始化 Regmap,並呼叫核心探測函式進行裝置初始化。接著,使用 devm_request_threaded_irq 請求執行緒中斷,以便在單次點選事件觸發時執行 adxl345_event_handler 中斷處理函式。此函式讀取 ACT_TAP_STATUSINT_SOURCE 暫存器,判斷中斷來源並推播事件到使用者空間。同時,驅動程式利用 devm_iio_triggered_buffer_setup 設定 IIO 觸發緩衝區,並在 adxl345_trigger_handler 函式中使用 regmap_bulk_read 讀取感測器資料,最後透過 iio_push_to_buffers_with_timestamp 將資料推播到緩衝區。

在裝置驅動程式中使用Regmap API

概述

本章節將介紹如何在Linux裝置驅動程式中使用Regmap API,以ADXL345加速計為例,展示如何使用Regmap API進行SPI通訊和中斷處理。

初始化Regmap組態

首先,定義一個regmap_config結構體,用於組態Regmap:

static const struct regmap_config adxl345_spi_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    /* 設定第7和第6位元以啟用多位元組讀取 */
    .read_flag_mask = BIT(7) | BIT(6),
};

內容解密:

  • .reg_bits = 8:表示暫存器位址是8位元寬。
  • .val_bits = 8:表示暫存器值是8位元寬。
  • .read_flag_mask = BIT(7) | BIT(6):設定讀取旗標遮罩,以啟用多位元組讀取。

SPI探測函式

在SPI探測函式中,初始化Regmap並呼叫核心探測函式:

static int adxl345_spi_probe(struct spi_device *spi)
{
    struct regmap *regmap;
    /* 從驅動結構體取得ID以使用名稱 */
    const struct spi_device_id *id = spi_get_device_id(spi);
    regmap = devm_regmap_init_spi(spi, &adxl345_spi_regmap_config);
    return adxl345_core_probe(&spi->dev, regmap, id->name);
}

內容解密:

  • devm_regmap_init_spi:初始化SPI Regmap。
  • adxl345_core_probe:核心探測函式,用於初始化裝置和註冊IIO裝置。

請求執行緒中斷

在核心探測函式中,請求執行緒中斷以處理單次點選中斷:

/* 請求執行緒中斷 */
devm_request_threaded_irq(dev, data->irq, NULL, adxl345_event_handler,
                          IRQF_TRIGGER_HIGH | IRQF_ONESHOT, dev_name(dev), indio_dev);

內容解密:

  • devm_request_threaded_irq:請求執行緒中斷,用於處理中斷事件。
  • adxl345_event_handler:中斷處理函式,用於處理單次點選事件。

中斷處理函式

中斷處理函式用於讀取ADXL345裝置的狀態並推播事件到使用者空間:

static irqreturn_t adxl345_event_handler(int irq, void *handle)
{
    u32 tap_stat, int_stat;
    struct iio_dev *indio_dev = handle;
    struct adxl345_data *data = iio_priv(indio_dev);
    data->timestamp = iio_get_time_ns(indio_dev);
    /* 讀取ACT_TAP_STATUS暫存器 */
    regmap_read(data->regmap, ACT_TAP_STATUS, &tap_stat);
    /* 讀取INT_SOURCE暫存器 */
    regmap_read(data->regmap, INT_SOURCE, &int_stat);
    /* 處理單次點選事件 */
    if (int_stat & (SINGLE_TAP)) {
        dev_info(data->dev, "單次點選中斷已發生\n");
        /* 推播事件到使用者空間 */
        iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, IIO_EV_TYPE_THRESH, 0), data->timestamp);
        /* ... */
    }
    return IRQ_HANDLED;
}

內容解密:

  • regmap_read:讀取ADXL345裝置的暫存器值。
  • iio_push_event:推播事件到使用者空間。

分配IIO觸發緩衝區

在核心探測函式中,分配IIO觸發緩衝區:

devm_iio_triggered_buffer_setup(dev, indio_dev, &iio_pollfunc_store_time, adxl345_trigger_handler, NULL);

內容解密:

  • devm_iio_triggered_buffer_setup:設定IIO觸發緩衝區。
  • adxl345_trigger_handler:觸發處理函式,用於讀取裝置資料。

觸發處理函式

觸發處理函式用於讀取ADXL345裝置的資料並推播到緩衝區:

static irqreturn_t adxl345_trigger_handler(int irq, void *p)
{
    struct iio_poll_func *pf = p;
    struct iio_dev *indio_dev = pf->indio_dev;
    struct adxl345_data *data = iio_priv(indio_dev);
    /* 6位元組軸資料 + 2位元組填充 + 8位元組時間戳記 */
    s16 buf[8];
    int i, ret, j = 0, base = DATAX0;
    s16 sample;
    /* 讀取已啟用的通道資料 */
    for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) {
        ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample), &sample, sizeof(sample));
        if (ret < 0)
            goto done;
        buf[j++] = sample;
    }
    iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp);
done:
    iio_trigger_notify_done(indio_dev->trig);
    return IRQ_HANDLED;
}

內容解密:

  • regmap_bulk_read:批次讀取ADXL345裝置的資料。
  • iio_push_to_buffers_with_timestamp:推播資料到緩衝區。

裝置驅動註冊

最後,註冊裝置驅動到SPI匯流排:

module_spi_driver(adxl345_driver);

內容解密:

  • module_spi_driver:註冊SPI驅動。

使用Regmap API在裝置驅動程式中的實作

ADXL345加速計驅動程式分析

本章節將探討如何在Linux裝置驅動程式中使用Regmap API,以ADXL345加速計為例,展示如何有效地管理和存取裝置暫存器。

ADXL345暫存器對映

ADXL345是一款由Analog Devices生產的三軸加速計,具備多種功能,包括敲擊檢測、中斷控制和資料格式化。以下是驅動程式中定義的ADXL345暫存器對映:

#define DEVID 0x00 /* R 裝置ID */
#define THRESH_TAP 0x1D /* R/W 敲擊閾值 */
#define DUR 0x21 /* R/W 敲擊持續時間 */
#define TAP_AXES 0x2A /* R/W 敲擊/雙擊軸控制 */
#define ACT_TAP_STATUS 0x2B /* R 敲擊/雙擊來源 */
#define BW_RATE 0x2C /* R/W 資料速率和電源模式控制 */
#define POWER_CTL 0x2D /* R/W 省電功能控制 */
#define INT_ENABLE 0x2E /* R/W 中斷啟用控制 */
#define INT_MAP 0x2F /* R/W 中斷對映控制 */
#define INT_SOURCE 0x30 /* R 中斷來源 */
#define DATA_FORMAT 0x31 /* R/W 資料格式控制 */
#define DATAX0 0x32 /* R X軸資料0 */
#define DATAX1 0x33 /* R X軸資料1 */
#define DATAY0 0x34 /* R Y軸資料0 */
#define DATAY1 0x35 /* R Y軸資料1 */
#define DATAZ0 0x36 /* R Z軸資料0 */
#define DATAZ1 0x37 /* R Z軸資料1 */
#define FIFO_CTL 0x38 /* R/W FIFO控制 */
#define FIFO_STATUS 0x39 /* R FIFO狀態 */

資料結構與函式實作

驅動程式中定義了一個adxl345_data結構體,用於儲存裝置的相關資料,如GPIO描述符、Regmap例項、IIO觸發器等。

struct adxl345_data {
    struct gpio_desc *gpio;
    struct regmap *regmap;
    struct iio_trigger *trig;
    struct device *dev;
    struct axis_triple saved;
    u8 data_range;
    u8 tap_threshold;
    u8 tap_duration;
    u8 tap_axis_control;
    u8 data_rate;
    u8 fifo_mode;
    u8 watermark;
    u8 low_power_mode;
    int irq;
    int ev_enable;
    u32 int_mask;
    s64 timestamp;
};

驅動程式實作了多個函式,用於處理IIO裝置的原始資料讀取、寫入、事件處理等。

static int adxl345_read_raw(struct iio_dev *indio_dev,
                            struct iio_chan_spec const *chan,
                            int *val, int *val2, long mask)
{
    struct adxl345_data *data = iio_priv(indio_dev);
    __le16 regval;
    int ret;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        ret = regmap_bulk_read(data->regmap, chan->address, &regval, sizeof(regval));
        if (ret < 0)
            return ret;
        *val = sign_extend32(le16_to_cpu(regval), 12);
        return IIO_VAL_INT;
    case IIO_CHAN_INFO_SCALE:
        *val = 0;
        *val2 = adxl345_uscale;
        return IIO_VAL_INT_PLUS_MICRO;
    default:
        return -EINVAL;
    }
}

Regmap組態與中斷處理

驅動程式使用Regmap API來管理和存取裝置暫存器。以下是Regmap的組態:

static const struct regmap_config adxl345_spi_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .read_flag_mask = BIT(7) | BIT(6),
};

中斷服務例程(ISR)用於處理來自ADXL345的中斷:

static irqreturn_t adxl345_event_handler(int irq, void *handle)
{
    u32 tap_stat, int_stat;
    int ret;
    struct iio_dev *indio_dev = handle;
    struct adxl345_data *data = iio_priv(indio_dev);

    data->timestamp = iio_get_time_ns(indio_dev);
    // 處理中斷邏輯
}
程式碼重點回顧
  1. 暫存器對映:定義了ADXL345的暫存器地址和功能。
  2. adxl345_data結構體:儲存裝置相關資料。
  3. IIO函式實作:實作了原始資料讀取、寫入和事件處理等功能。
  4. Regmap組態:設定了Regmap的引數,用於管理裝置暫存器。
  5. 中斷處理:實作了中斷服務例程,用於處理來自ADXL345的中斷。

詳細解析

  • 暫存器對映:正確定義暫存器對映是驅動程式開發的基礎,確保能夠正確存取裝置暫存器。
  • 資料結構設計:合理的資料結構設計有助於提高驅動程式的可讀性和可維護性。
  • Regmap API的使用:簡化了裝置暫存器的存取,提高了驅動程式的效率和可靠性。
  • 中斷處理機制:正確的中斷處理機制對於實時系統至關重要,能夠提高系統的回應速度和穩定性。

透過本章節的學習,開發者可以深入瞭解如何在Linux裝置驅動程式中使用Regmap API,從而開發出高效、穩定的裝置驅動程式。

使用Regmap API於裝置驅動程式中 - ADXL345實作詳解

在Linux裝置驅動程式開發中,Regmap API提供了一種統一且高效的方式來存取硬體暫存器。本文將探討如何在ADXL345加速計驅動程式中使用Regmap API,並詳細解析其實作細節。

中斷處理機制

ADXL345支援多種中斷來源,包括單次點選(SINGLE_TAP)事件。驅動程式透過adxl345_event_handler函式處理這些中斷事件。

程式碼解析

static irqreturn_t adxl345_event_handler(int irq, void *p)
{
    struct iio_dev *indio_dev = p;
    struct adxl345_data *data = iio_priv(indio_dev);
    u8 tap_stat, int_stat;
    int ret;

    // 檢查是否啟用了點選偵測功能
    if (data->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN)) {
        ret = regmap_read(data->regmap, ACT_TAP_STATUS, &tap_stat);
        if (ret) {
            dev_err(data->dev, "讀取ACT_TAP_STATUS暫存器錯誤\n");
            return ret;
        }
    } else {
        tap_stat = 0;
    }

    // 讀取INT_SOURCE暫存器以清除中斷
    ret = regmap_read(data->regmap, INT_SOURCE, &int_stat);
    if (ret) {
        dev_err(data->dev, "讀取INT_SOURCE暫存器錯誤\n");
        return ret;
    }

    // 處理單次點選事件
    if (int_stat & SINGLE_TAP) {
        dev_info(data->dev, "發生單次點選中斷\n");
        // 推播事件到IIO框架
        if (tap_stat & TAP_X_EN) {
            iio_push_event(indio_dev, IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, IIO_MOD_X, IIO_EV_TYPE_THRESH, 0), data->timestamp);
        }
        // ... (省略Y軸和Z軸的處理)
    }

    return IRQ_HANDLED;
}

內容解密:

  1. 首先檢查是否啟用了點選偵測功能,如果有,則讀取ACT_TAP_STATUS暫存器以取得點選狀態。
  2. 讀取INT_SOURCE暫存器以清除中斷標誌。
  3. 如果發生了單次點選事件,則根據ACT_TAP_STATUS的內容推播對應的事件到IIO框架。
  4. 使用regmap_read函式進行暫存器讀取,這是Regmap API提供的安全且高效的存取方式。

資料擷取與緩衝區處理

驅動程式使用adxl345_trigger_handler函式處理觸發器的資料擷取。

程式碼解析

static irqreturn_t adxl345_trigger_handler(int irq, void *p)
{
    struct iio_poll_func *pf = p;
    struct iio_dev *indio_dev = pf->indio_dev;
    struct adxl345_data *data = iio_priv(indio_dev);
    s16 buf[8];
    int i, ret, j = 0, base = DATAX0;
    s16 sample;

    // 讀取已啟用的通道資料
    for_each_set_bit(i, indio_dev->active_scan_mask, indio_dev->masklength) {
        ret = regmap_bulk_read(data->regmap, base + i * sizeof(sample), &sample, sizeof(sample));
        if (ret < 0)
            goto done;
        buf[j++] = sample;
    }

    // 將資料推播到IIO緩衝區
    iio_push_to_buffers_with_timestamp(indio_dev, buf, pf->timestamp);

done:
    iio_trigger_notify_done(indio_dev->trig);
    return IRQ_HANDLED;
}

內容解密:

  1. 使用for_each_set_bit遍歷已啟用的通道,並使用regmap_bulk_read批次讀取資料。
  2. 將讀取到的資料存入緩衝區buf中。
  3. 使用iio_push_to_buffers_with_timestamp將資料和時間戳推播到IIO緩衝區。
  4. 最後通知觸發器處理完成。

裝置初始化與註冊

驅動程式的核心初始化函式為adxl345_core_probe

程式碼解析

int adxl345_core_probe(struct device *dev, struct regmap *regmap, const char *name)
{
    // ... (省略部分程式碼)

    // 初始化ADXL345暫存器
    ret = regmap_write(data->regmap, DATA_FORMAT, data->data_range);
    // ... (省略其他暫存器設定)

    // 請求執行緒中斷
    ret = devm_request_threaded_irq(dev, data->irq, NULL, adxl345_event_handler,
                                    IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
                                    dev_name(dev), indio_dev);
    if (ret) {
        dev_err(dev, "請求中斷失敗 %d (%d)", data->irq, ret);
        goto error_standby;
    }

    return 0;
}

內容解密:

  1. 初始化ADXL345的各項引數,包括資料格式、點選偵測門檻等。
  2. 使用regmap_write函式將這些引數寫入對應的暫存器中。
  3. 請求執行緒中斷處理函式adxl345_event_handler
  4. 使用Regmap API進行暫存器存取,簡化了硬體互動的複雜度。