在嵌入式系統開發中,高效且簡潔的硬體暫存器存取至關重要。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函式主要用於將資料寫入裝置的暫存器中。以下是該函式的詳細步驟:
鎖定機制:在寫入操作開始前,regmap_write會先取得鎖定,以確保操作的原子性。如果在regmap_config中設定了fast_io,則使用自旋鎖(spinlock);否則,使用互斥鎖(mutex)。
檢查寫入位址的有效性:檢查傳入的暫存器位址是否小於max_register。如果不是,則傳回-EIO錯誤。這個檢查步驟僅在max_register被設定為大於零時執行。
檢查寫入許可權:如果設定了writeable_reg回呼函式,則呼叫該函式檢查寫入的暫存器是否可寫。如果回呼函式傳回false,則終止寫入操作並傳回-EIO錯誤。
檢查暫存器位址是否在wr_table的no_ranges或yes_ranges內:如果暫存器位址位於wr_table的no_ranges內,或者既不在no_ranges也不在yes_ranges內,則傳回-EIO錯誤。只有當位址位於yes_ranges內時,才允許進一步的操作。
快取處理:如果允許快取,則將暫存器的值快取起來,而不直接寫入硬體,從而結束操作。如果快取被設定為略過(bypass),則進行下一步操作。
硬體寫入操作:呼叫硬體寫入例程(如regmap_spi_write),將值寫入硬體暫存器。該函式會將write_flag_mask寫入值的第一個位元組,並將值寫入裝置。
解鎖並傳回:完成寫入操作後,釋放之前取得的鎖定,並傳回結果。
regmap_read函式的運作機制
regmap_read函式主要用於從裝置讀取資料。以下是該函式的詳細步驟:
鎖定機制:與regmap_write類別似,regmap_read在讀取操作開始前會先取得鎖定。
檢查讀取位址的有效性:檢查傳入的暫存器位址是否小於max_register,如果不是,則傳回-EIO錯誤。
檢查讀取許可權:如果設定了readable_reg回呼函式,則呼叫該函式檢查讀取的暫存器是否可讀。如果回呼函式傳回false,則終止讀取操作並傳回-EIO錯誤。
檢查暫存器位址是否在rd_table的no_ranges或yes_ranges內:與寫入操作類別似,檢查位址是否位於rd_table的no_ranges或yes_ranges內,以決定是否允許讀取操作。
快取處理:如果允許快取,則直接從快取中讀取暫存器的值並傳回。如果快取被設定為略過,則進行下一步操作。
硬體讀取操作:呼叫硬體讀取例程(如regmap_spi_read),讀取暫存器的值,並更新傳入變數的值。
解鎖並傳回:完成讀取操作後,釋放鎖定並傳回結果。
實驗室12.1:使用SPI Regmap IIO裝置模組
本實驗室旨在開發一個與實驗室10.4功能相似的驅動程式,但這次使用IIO子系統而非Input子系統,並利用Regmap API存取ADXL345裝置的暫存器,而不是使用SPI特定的核心API。
驅動程式的主要程式碼段
- 引入必要的標頭檔
#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_spec和iio_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_raw、adxl345_write_raw、adxl345_read_event和adxl345_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, ®val, 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屬性時,會呼叫此函式。
- 當
mask為IIO_CHAN_INFO_RAW時,函式使用regmap_bulk_read讀取指定通道的原始資料,並將結果儲存在regval中。然後,使用sign_extend32將資料進行符號擴充套件,並將結果儲存在val中。 - 當
mask為IIO_CHAN_INFO_SCALE時,函式將比例因子的整數部分和微秒部分分別儲存在val和val2中。
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屬性時,會呼叫此函式。
- 當
mask為IIO_CHAN_INFO_SAMP_FREQ時,函式根據輸入值val計算出新的資料速率,並將其儲存在data->data_rate中。然後,使用regmap_write將新的資料速率寫入ADXL345的BW_RATE暫存器中。
SPI暫存器對映組態和初始化
在驅動程式中,需要組態和初始化SPI暫存器對映。
// SPI register map configuration and initialization code
程式碼解析:
這部分程式碼用於組態和初始化SPI暫存器對映,以便驅動程式能夠正確地與ADXL345進行通訊。