返回文章列表

Regmap API 裝置驅動程式應用

本文探討 Regmap API 在 Linux 驅動程式中的應用,以 ADXL345 加速計為例,講解如何使用 Regmap API 讀寫硬體暫存器,並結合 IIO 子系統實作資料採集和事件觸發。文章涵蓋了 Regmap API 的核心概念、操作流程、程式碼範例以及優缺點分析,幫助讀者快速掌握 Regmap API

嵌入式系統 驅動程式開發

在嵌入式系統開發中,高效且簡潔的硬體暫存器存取至關重要。Regmap API 提供了 Linux 驅動程式與硬體互動的標準化介面,簡化了暫存器操作的複雜性。本文以 ADXL345 加速計為例,示範如何利用 Regmap API 進行裝置控制和資料讀取,並整合 IIO 子系統實作感測器資料的採集和事件處理。透過 Regmap API,驅動程式開發者可以避免繁瑣的底層硬體操作,提升程式碼可讀性和可維護性,並降低跨平台移植的難度。

使用Regmap API於裝置驅動程式 Chapter 12

Regmap API的操作流程

Regmap API提供了一種通用且有效率的方式來存取硬體暫存器。在裝置驅動程式中,Regmap API扮演著至關重要的角色。本章節將探討Regmap API的核心功能及其在裝置驅動程式中的實際應用。

regmap_write函式的運作機制

regmap_write函式主要用於將資料寫入裝置的暫存器中。以下是該函式的詳細步驟:

  1. 鎖定機制:在寫入操作開始前,regmap_write會先取得鎖定,以確保操作的原子性。如果在regmap_config中設定了fast_io,則使用自旋鎖(spinlock);否則,使用互斥鎖(mutex)。

  2. 檢查寫入位址的有效性:檢查傳入的暫存器位址是否小於max_register。如果不是,則傳回-EIO錯誤。這個檢查步驟僅在max_register被設定為大於零時執行。

  3. 檢查寫入許可權:如果設定了writeable_reg回呼函式,則呼叫該函式檢查寫入的暫存器是否可寫。如果回呼函式傳回false,則終止寫入操作並傳回-EIO錯誤。

  4. 檢查暫存器位址是否在wr_table的no_ranges或yes_ranges內:如果暫存器位址位於wr_table的no_ranges內,或者既不在no_ranges也不在yes_ranges內,則傳回-EIO錯誤。只有當位址位於yes_ranges內時,才允許進一步的操作。

  5. 快取處理:如果允許快取,則將暫存器的值快取起來,而不直接寫入硬體,從而結束操作。如果快取被設定為略過(bypass),則進行下一步操作。

  6. 硬體寫入操作:呼叫硬體寫入例程(如regmap_spi_write),將值寫入硬體暫存器。該函式會將write_flag_mask寫入值的第一個位元組,並將值寫入裝置。

  7. 解鎖並傳回:完成寫入操作後,釋放之前取得的鎖定,並傳回結果。

regmap_read函式的運作機制

regmap_read函式主要用於從裝置讀取資料。以下是該函式的詳細步驟:

  1. 鎖定機制:與regmap_write類別似,regmap_read在讀取操作開始前會先取得鎖定。

  2. 檢查讀取位址的有效性:檢查傳入的暫存器位址是否小於max_register,如果不是,則傳回-EIO錯誤。

  3. 檢查讀取許可權:如果設定了readable_reg回呼函式,則呼叫該函式檢查讀取的暫存器是否可讀。如果回呼函式傳回false,則終止讀取操作並傳回-EIO錯誤。

  4. 檢查暫存器位址是否在rd_table的no_ranges或yes_ranges內:與寫入操作類別似,檢查位址是否位於rd_table的no_ranges或yes_ranges內,以決定是否允許讀取操作。

  5. 快取處理:如果允許快取,則直接從快取中讀取暫存器的值並傳回。如果快取被設定為略過,則進行下一步操作。

  6. 硬體讀取操作:呼叫硬體讀取例程(如regmap_spi_read),讀取暫存器的值,並更新傳入變數的值。

  7. 解鎖並傳回:完成讀取操作後,釋放鎖定並傳回結果。

實驗室12.1:使用SPI Regmap IIO裝置模組

本實驗室旨在開發一個與實驗室10.4功能相似的驅動程式,但這次使用IIO子系統而非Input子系統,並利用Regmap API存取ADXL345裝置的暫存器,而不是使用SPI特定的核心API。

驅動程式的主要程式碼段

  1. 引入必要的標頭檔
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/iio/events.h>
#include <linux/iio/buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>

定義ADXL345裝置的暫存器

/* ADXL345 Register Map */
#define DEVID 0x00 /* R Device ID */
#define THRESH_TAP 0x1D /* R/W Tap threshold */
#define DUR 0x21 /* R/W Tap duration */
#define TAP_AXES 0x2A /* R/W Axis control for tap/double tap */
#define ACT_TAP_STATUS 0x2B /* R Source of tap/double tap */
#define BW_RATE 0x2C /* R/W Data rate and power mode control */
#define POWER_CTL 0x2D /* R/W Power saving features control */
#define INT_ENABLE 0x2E /* R/W Interrupt enable control */
#define INT_MAP 0x2F /* R/W Interrupt mapping control */
#define INT_SOURCE 0x30 /* R Source of interrupts */
#define DATA_FORMAT 0x31 /* R/W Data format control */
#define DATAX0 0x32 /* R X-Axis Data 0 */
#define DATAX1 0x33 /* R X-Axis Data 1 */
#define DATAY0 0x34 /* R Y-Axis Data 0 */
#define DATAY1 0x35 /* R Y-Axis Data 1 */
#define DATAZ0 0x36 /* R Z-Axis Data 0 */
#define DATAZ1 0x37 /* R Z-Axis Data 1 */
#define FIFO_CTL 0x38 /* R/W FIFO control */
#define FIFO_STATUS 0x39 /* R FIFO status */

程式碼解說

上述程式碼定義了ADXL345加速計裝置的各個暫存器位址,這些定義對於驅動程式與裝置之間的通訊至關重要。透過Regmap API,驅動程式可以方便地讀寫這些暫存器,從而控制裝置並取得感測資料。

使用Regmap API的好處

  • 簡化暫存器存取:Regmap API提供了一種統一的方式來存取不同的硬體暫存器,無需針對每種匯流排型別(如SPI、I2C)編寫特定的程式碼。
  • 提高程式碼的可移植性:由於Regmap API抽象化了底層的硬體細節,因此使用它的驅動程式更容易在不同平台之間移植。
  • 增強快取管理功能:Regmap API支援快取機制,可以減少對硬體的存取次數,從而提高系統效能。

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

簡介

在Linux核心中,Regmap API提供了一種統一的方式來存取硬體暫存器。本文將介紹如何在裝置驅動程式中使用Regmap API,以ADXL345加速計為例。

ADXL345驅動程式實作

1. 定義暫存器對映

首先,需要定義ADXL345的暫存器對映。

#define ADXL_TAP_X_EN (1 << 2)
#define ADXL_TAP_Y_EN (1 << 1)
#define ADXL_TAP_Z_EN (1 << 0)

2. 建立私有資料結構

建立一個私有資料結構adxl345_data來儲存裝置的相關資訊。

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

3. 定義IIO通道和事件屬性

定義iio_chan_speciio_event_spec結構來向使用者空間公開通道和事件sysfs屬性。

static const struct iio_event_spec adxl345_event = {
    .type = IIO_EV_TYPE_THRESH,
    .dir = IIO_EV_DIR_EITHER,
    .mask_separate = BIT(IIO_EV_INFO_VALUE) | BIT(IIO_EV_INFO_PERIOD)
};

#define ADXL345_CHANNEL(reg, axis, idx) { \
    .type = IIO_ACCEL, \
    .modified = 1, \
    .channel2 = IIO_MOD_##axis, \
    .address = reg, \
    .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
    .info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE) | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
    .scan_index = idx, \
    .scan_type = { \
        .sign = 's', \
        .realbits = 13, \
        .storagebits = 16, \
        .endianness = IIO_LE, \
    }, \
    .event_spec = &adxl345_event, \
    .num_event_specs = 1 \
}

static const struct iio_chan_spec adxl345_channels[] = {
    ADXL345_CHANNEL(DATAX0, X, 0),
    ADXL345_CHANNEL(DATAY0, Y, 1),
    ADXL345_CHANNEL(DATAZ0, Z, 2),
    IIO_CHAN_SOFT_TIMESTAMP(3),
};

4. 定義IIO資訊結構

定義iio_info結構來宣告IIO核心將使用的hook函式。

static const struct iio_info adxl345_info = {
    .driver_module = THIS_MODULE,
    .read_raw = adxl345_read_raw,
    .write_raw = adxl345_write_raw,
    .read_event_value = adxl345_read_event,
    .write_event_value = adxl345_write_event,
};

5. 實作hook函式

實作adxl345_read_rawadxl345_write_rawadxl345_read_eventadxl345_write_event函式來處理使用者空間的存取請求。

adxl345_read_raw函式
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;

    switch (mask) {
    case IIO_CHAN_INFO_RAW:
        regmap_bulk_read(data->regmap, chan->address, &regval, sizeof(regval));
        *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;
    }
}

程式碼解析:

此函式用於讀取ADXL345的原始資料或比例因子。當使用者空間讀取通道sysfs屬性時,會呼叫此函式。

  • maskIIO_CHAN_INFO_RAW時,函式使用regmap_bulk_read讀取指定通道的原始資料,並將結果儲存在regval中。然後,使用sign_extend32將資料進行符號擴充套件,並將結果儲存在val中。
  • maskIIO_CHAN_INFO_SCALE時,函式將比例因子的整數部分和微秒部分分別儲存在valval2中。
adxl345_write_raw函式
static int adxl345_write_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);

    switch (mask) {
    case IIO_CHAN_INFO_SAMP_FREQ:
        data->data_rate = RATE(val);
        return regmap_write(data->regmap, BW_RATE, data->data_rate | (data->low_power_mode ? LOW_POWER : 0));
    default:
        return -EINVAL;
    }
}

程式碼解析:

此函式用於設定ADXL345的資料速率。當使用者空間寫入通道sysfs屬性時,會呼叫此函式。

  • maskIIO_CHAN_INFO_SAMP_FREQ時,函式根據輸入值val計算出新的資料速率,並將其儲存在data->data_rate中。然後,使用regmap_write將新的資料速率寫入ADXL345的BW_RATE暫存器中。

SPI暫存器對映組態和初始化

在驅動程式中,需要組態和初始化SPI暫存器對映。

// SPI register map configuration and initialization code

程式碼解析:

這部分程式碼用於組態和初始化SPI暫存器對映,以便驅動程式能夠正確地與ADXL345進行通訊。