SPI 驅動程式開發涉及與硬體和核心互動,需要仔細設定才能確保裝置正常運作。首先,驅動程式必須正確註冊到 SPI 子系統,並與裝置樹中的硬體描述匹配。透過相容性字串,核心可以找到正確的驅動程式來控制硬體。在裝置樹中,必須定義 SPI 控制器和連線的裝置,包含必要的引數,例如時脈速度、中斷設定和晶片選擇。驅動程式碼本身需要處理 SPI 交易、讀寫裝置暫存器,以及管理中斷。以 ADXL345 加速度計為例,驅動程式需要讀取加速度資料並將其轉換為輸入事件,以便應用程式使用。
SPI 客戶端驅動程式開發
在這一章中,我們將探討如何開發 SPI 客戶端驅動程式,以控制加速計和混合訊號 I/O 裝置。首先,我們將介紹設定 SPI 客戶端驅動程式的主要步驟。
SPI 客戶端驅動程式註冊
SPI 子系統定義了一個 spi_driver 結構,該結構繼承自 device_driver 結構。每個 SPI 客戶端驅動程式都必須例項化和註冊到 SPI 匯流排核心。通常,您將實作一個單一的驅動程式結構,並從中例項化所有客戶端。驅動程式結構包含一般的存取例程,應初始化為零,除了您提供的欄位。
範例:ADXL345 加速計裝置的 spi_driver 定義
static struct spi_driver adxl345_driver = {
.driver = {
.name = "adxl345",
.owner = THIS_MODULE,
.of_match_table = adxl345_dt_ids,
},
.probe = adxl345_spi_probe,
.remove = adxl345_spi_remove,
.id_table = adxl345_id,
};
module_spi_driver(adxl345_driver);
module_spi_driver() 巨集用於註冊/登出驅動程式。
相容性字串定義
在您的 Linux SPI 驅動程式中,您將建立一個 of_device_id 結構陣列,指定相容性字串,該字串與裝置樹(DT)裝置節點的 compatible 屬性值相同。
static const struct of_device_id adxl345_dt_ids[] = {
{ .compatible = "arrow,adxl345", },
{ }
};
MODULE_DEVICE_TABLE(of, adxl345_dt_ids);
驅動程式的 probe() 函式在 of_device_id 條目中的 compatible 欄位與 DT 裝置節點的 compatible 屬性匹配時被呼叫。probe() 函式負責使用從匹配的 DT 裝置節點取得的組態值初始化裝置,並將裝置註冊到適當的核心框架。
在裝置樹中宣告 SPI 裝置
在裝置樹中,SPI 控制器通常在描述處理器的 .dtsi 檔案中宣告。SPI 控制器的狀態通常設定為 "disabled"。
BCM2837 SPI 控制器的宣告範例
spi: spi@7e204000 {
compatible = "brcm,bcm2835-spi";
reg = <0x7e204000 0x200>;
interrupts = <2 22>;
clocks = <&clocks BCM2835_CLOCK_VPU>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
所需的屬性包括:
compatible:與spi-bcm2835.c驅動程式的相容值匹配。reg:裝置的暫存器集偏移和長度。interrupts:中斷訊號的列表。clocks:輸入時鐘的列表。
SPI 裝置的宣告
SPI 裝置在板級/平台級別(arch/arm/boot/dts/bcm2710-rpi-3-b.dts)宣告為 SPI 主控制器的子節點。所需的和可選的屬性包括:
reg:裝置的晶片選擇位址。compatible:SPI 裝置的名稱,應與驅動程式的of_device_id相容字串之一匹配。spi-max-frequency:裝置的最大 SPI 時脈速度(Hz)。
範例:SPI 裝置宣告
&spi {
cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
status = "okay";
adxl345@0 {
compatible = "arrow,adxl345";
reg = <0>;
spi-max-frequency = <1000000>;
};
};
程式碼解析
static const struct spi_device_id adxl345_id[] = {
{ .name = "adxl345", },
{ }
};
MODULE_DEVICE_TABLE(spi, adxl345_id);
此程式碼定義了一個名為 adxl345_id 的陣列,用於儲存 SPI 裝置的 ID。
內容解密:
此段程式碼的作用是定義 SPI 裝置的 ID,並將其註冊到核心中。透過定義相容性字串,驅動程式可以正確地匹配和初始化裝置。
SPI 加速度計輸入裝置驅動程式開發
在本次實驗中,我們將開發第一個SPI裝置的驅動程式,用於管理連線到SPI匯流排的加速度計裝置。我們將使用與前一個實驗相同的ADXL345 Accel click mikroBUS™配件板。
硬體連線描述
以下是連線描述:
- 將Raspberry Pi GPIO8(CE0)連線到ADXL345 CS(CS)
- 將Raspberry Pi SCLK連線到ADXL345 SCL(SCK)
- 將Raspberry Pi MISO連線到ADXL345 SDO(MISO)
- 將Raspberry Pi MOSI連線到ADXL345 SDI(MOSI)
- 將Raspberry Pi GPIO23連線到ADXL345 INT1(INT)
裝置樹描述
我們需要在bcm2710-rpi-3-b.dts裝置樹檔案中新增adxl345@0子節點,在spi0控制器主節點下。pinctrl-0屬性指向accel_int_pin引腳組態節點,其中GPIO23被複用為GPIO訊號。int-gpios屬性使GPIO23可供驅動程式使用,以便設定引腳方向為輸入並取得與該引腳相關聯的Linux IRQ編號。reg屬性提供CS編號。
&spi0 {
pinctrl-names = "default";
pinctrl-0 = <&spi0_pins &spi0_cs_pins>;
cs-gpios = <&gpio 8 1>, <&gpio 7 1>;
Accel: ADXL345@0 {
compatible = "arrow,adxl345";
spi-max-frequency = <5000000>;
spi-cpol;
spi-cpha;
reg = <0>;
pinctrl-0 = <&accel_int_pin>;
int-gpios = <&gpio 23 0>;
interrupts = <23 1>;
interrupt-parent = <&gpio>;
};
};
accel_int_pin: accel_int_pin {
brcm,pins = <23>;
brcm,function = <0>; /* Input */
brcm,pull = <0>; /* none */
};
內容解密:
此段裝置樹組態定義了SPI介面下的ADXL345加速度計裝置。其中:
compatible = "arrow,adxl345"指定了裝置的相容性,使其與驅動程式匹配。spi-max-frequency = <5000000>設定SPI介面的最大頻率為5 MHz。spi-cpol和spi-cpha設定SPI的時脈極性和相位。reg = <0>指定了裝置的CS編號為0。int-gpios = <&gpio 23 0>設定GPIO23為中斷輸入引腳。interrupts = <23 1>和interrupt-parent = <&gpio>定義了中斷組態。
驅動程式碼描述
以下是驅動程式的主要程式碼段:
標頭檔引入
#include <linux/module.h>
#include <linux/input.h>
#include <linux/spi/spi.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
SPI交易指令定義
#define ADXL345_CMD_MULTB (1 << 6)
#define ADXL345_CMD_READ (1 << 7)
#define ADXL345_WRITECMD(reg) (reg & 0x3F)
#define ADXL345_READCMD(reg) (ADXL345_CMD_READ | (reg & 0x3F))
#define ADXL345_READMB_CMD(reg) (ADXL345_CMD_READ | ADXL345_CMD_MULTB | (reg & 0x3F))
內容解密:
這些巨集定義了用於與ADXL345裝置進行SPI交易的命令位元組。其中:
ADXL345_CMD_MULTB設定多位元組讀取模式。ADXL345_CMD_READ設定讀取模式。ADXL345_WRITECMD(reg)、ADXL345_READCMD(reg)和ADXL345_READMB_CMD(reg)分別用於生成寫入、讀取和多位元組讀取命令。
ADXL345暫存器定義
#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 */
內容解密:
這些巨集定義了ADXL345裝置的暫存器位址,用於讀取和寫入裝置的設定和資料。其中包括裝置ID、點選閾值、資料率控制、中斷控制等。
ADXL345 加速度計驅動程式開發
前言
本章節將探討如何開發 ADXL345 加速度計的 Linux 驅動程式。ADXL345 是一款高解析度的加速度計,廣泛應用於各種裝置中,如手機、遊戲控制器和工業裝置等。為了使該裝置正常運作,我們需要編寫一個 Linux 驅動程式來與其進行互動。
驅動程式結構設計
1. 定義必要的巨集和資料結構
首先,我們需要定義一些巨集和資料結構來描述 ADXL345 的暫存器和操作。
#define FULL_RES (1 << 3)
/* FIFO_CTL Bits */
#define FIFO_MODE(x) (((x) & 0x3) << 6)
#define FIFO_BYPASS 0
#define FIFO_FIFO 1
#define FIFO_STREAM 2
#define SAMPLES(x) ((x) & 0x1F)
/* 定義一個結構來儲存 SPI 匯流排操作 */
struct adxl345_bus_ops {
u16 bustype;
int (*read)(struct device *, unsigned char);
int (*read_block)(struct device *, unsigned char, int, void *);
int (*write)(struct device *, unsigned char, unsigned char);
};
struct axis_triple {
int x;
int y;
int z;
};
/* 定義一個結構來儲存特定驅動程式的資訊 */
struct adxl345_platform_data {
u8 low_power_mode;
u8 tap_threshold;
u8 tap_duration;
u8 tap_axis_control;
u8 data_rate;
u8 data_range;
u32 ev_code_tap[3];
u8 fifo_mode;
u8 watermark;
};
2. 初始化 adxl345_bus_ops 結構
接下來,我們需要初始化 adxl345_bus_ops 結構,並將其傳遞給 adxl345_probe() 函式作為引數。
static const struct adxl345_bus_ops adxl345_spi_bops = {
.bustype = BUS_SPI,
.write = adxl345_spi_write,
.read = adxl345_spi_read,
.read_block = adxl345_spi_read_block,
};
static int adxl345_spi_probe(struct spi_device *spi)
{
/* 建立一個私有結構 */
struct adxl345 *ac;
/* 初始化驅動程式並傳回初始化的私有結構 */
ac = adxl345_probe(&spi->dev, &adxl345_spi_bops);
/* 將 SPI 裝置附加到私有結構 */
spi_set_drvdata(spi, ac);
return 0;
}
#### 內容解密:
此段程式碼主要完成了以下工作:
- 定義必要的巨集:定義了與 ADXL345 相關的巨集,例如
FULL_RES、FIFO_MODE等,用於控制暫存器。 - 定義資料結構:定義了
adxl345_bus_ops和adxl345_platform_data結構,分別用於描述 SPI 匯流排操作和特定驅動程式的資訊。 - 初始化 adxl345_bus_ops 結構:初始化了
adxl345_spi_bops結構,並將其傳遞給adxl345_probe()函式。
驅動程式初始化流程
1. adxl345_probe() 函式
adxl345_probe() 函式是驅動程式初始化的核心部分,它負責建立輸入裝置、設定初始暫存器值和註冊中斷處理程式。
struct adxl345 *adxl345_probe(struct device *dev, const struct adxl345_bus_ops *bops)
{
/* 宣告私有結構 */
struct adxl345 *ac;
/* 建立輸入裝置 */
struct input_dev *input_dev;
/* 組態輸入裝置 */
input_dev->name = "ADXL345 accelerometer";
input_dev->phys = ac->phys;
input_dev->dev.parent = dev;
/* 設定事件型別和事件程式碼 */
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
/* 請求 threaded 中斷 */
devm_request_threaded_irq(input_dev->dev.parent, ac->irq, NULL, adxl345_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, dev_name(dev), ac);
/* 初始化 ADXL345 暫存器 */
AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold);
AC_WRITE(ac, DUR, pdata->tap_duration);
return ac;
}
#### 內容解密:
此段程式碼主要完成了以下工作:
- 建立輸入裝置:使用
devm_input_allocate_device()建立了一個輸入裝置,並對其進行了組態。 - 設定事件型別和事件程式碼:設定了輸入裝置支援的事件型別和事件程式碼。
- 請求 threaded 中斷:使用
devm_request_threaded_irq()請求了一個 threaded 中斷,用於處理單次點選中斷。 - 初始化 ADXL345 暫存器:使用
AC_WRITE()對 ADXL345 的暫存器進行了初始化,設定了點選閾值和持續時間等引數。
中斷處理程式
1. 編寫 threaded 中斷處理程式
threaded 中斷處理程式用於處理單次點選中斷。
static irqreturn_t adxl345_irq(int irq, void *dev_id)
{
/* 處理中斷 */
return IRQ_HANDLED;
}
#### 內容解密:
此段程式碼主要完成了以下工作:
- 處理中斷:threaded 中斷處理程式用於處理單次點選中斷,並傳回
IRQ_HANDLED表示中斷已被處理。