Regmap API 提供簡化的暫存器存取介面,有效提升驅動程式開發效率。本文以 ADXL345 加速計為例,說明如何整合 Regmap API 管理 SPI 通訊與中斷。首先,透過 regmap_config 結構體設定暫存器和數值的位元寬度,並定義讀取旗標遮罩以支援多位元組讀取。在 SPI 探測函式中,使用 devm_regmap_init_spi 初始化 Regmap,並呼叫核心探測函式進行裝置初始化。接著,使用 devm_request_threaded_irq 請求執行緒中斷,以便在單次點選事件觸發時執行 adxl345_event_handler 中斷處理函式。此函式讀取 ACT_TAP_STATUS 和 INT_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, ®val, 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);
// 處理中斷邏輯
}
程式碼重點回顧
- 暫存器對映:定義了ADXL345的暫存器地址和功能。
adxl345_data結構體:儲存裝置相關資料。- IIO函式實作:實作了原始資料讀取、寫入和事件處理等功能。
- Regmap組態:設定了Regmap的引數,用於管理裝置暫存器。
- 中斷處理:實作了中斷服務例程,用於處理來自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;
}
內容解密:
- 首先檢查是否啟用了點選偵測功能,如果有,則讀取
ACT_TAP_STATUS暫存器以取得點選狀態。 - 讀取
INT_SOURCE暫存器以清除中斷標誌。 - 如果發生了單次點選事件,則根據
ACT_TAP_STATUS的內容推播對應的事件到IIO框架。 - 使用
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;
}
內容解密:
- 使用
for_each_set_bit遍歷已啟用的通道,並使用regmap_bulk_read批次讀取資料。 - 將讀取到的資料存入緩衝區
buf中。 - 使用
iio_push_to_buffers_with_timestamp將資料和時間戳推播到IIO緩衝區。 - 最後通知觸發器處理完成。
裝置初始化與註冊
驅動程式的核心初始化函式為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;
}
內容解密:
- 初始化ADXL345的各項引數,包括資料格式、點選偵測門檻等。
- 使用
regmap_write函式將這些引數寫入對應的暫存器中。 - 請求執行緒中斷處理函式
adxl345_event_handler。 - 使用Regmap API進行暫存器存取,簡化了硬體互動的複雜度。