1つ:SPIドライバーフレームワークの概要
SPIドライバーフレームワークはI2Cに似ており、ホストコントローラードライバーとデバイスドライバーに分かれています。
1.SPIホストドライバー
SPIホストドライバーはSOCのSPIコントローラードライバーです。Linuxカーネルはspi_masterを使用してSPIホストドライバーを表します。
struct spi_master {
struct device dev;
struct list_head list;
s16 bus_num;
u16 num_chipselect;
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
... ...
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
... ...
/* other constraints relevant to this driver */
u16 flags;
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
bool bus_lock_flag;
int (*setup)(struct spi_device *spi);
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg);
... ...
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
... ...
};
伝達関数は、コントローラーデータ伝達関数としてのi2cのmaster_xfer関数と同じです。transfer_one_message関数は、SPIデータ送信にも使用されます。spi_messageの送信に使用されます。SPIデータはspi_messageにパッケージ化されてから、キューに送信されます。
SPIホストドライバーのコアは、spi_masterを申請し、spi_masterを初期化し、最後にspi_masterをカーネルに登録することです。
(1)spi_masterのアプリケーションとリリース
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
static inline void spi_master_put(struct spi_master *master)
(2)spi_masterの登録とキャンセル
int spi_register_master(struct spi_master *master)
void spi_unregister_master(struct spi_master *master)
2.SPIデバイスドライバー
Linuxカーネルは、spi_driver構造体を使用してspiデバイスドライバーを表します。これには、以下を実装する必要があります。
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
struct device_driver driver;
};
spiデバイスとドライバが正常に一致すると、プローブ機能が実行されることがわかります。
spi_driverが初期化された後、Linuxカーネルに登録する必要があります。
int spi_register_driver(struct spi_driver *sdrv)
ログアウト機能:
void spi_unregister_driver(struct spi_driver *sdrv)
spi_driver登録の例:
/* probe 函数 */
static int xxx_probe(struct spi_device *spi)
{
/* 具体函数内容 */
return 0;
}
/* remove 函数 */
static int xxx_remove(struct spi_device *spi)
{
/* 具体函数内容 */
return 0;
}
/* 传统匹配方式 ID 列表 */
static const struct spi_device_id xxx_id[] = {
{"xxx", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
{ .compatible = "xxx" },
{ /* Sentinel */ }
};
/* SPI 驱动结构体 */
static struct spi_driver xxx_driver = {
.probe = xxx_probe,
.remove = xxx_remove,
.driver = {
.owner = THIS_MODULE,
.name = "xxx",
.of_match_table = xxx_of_match,
},
.id_table = xxx_id,
};
/* 驱动入口函数 */
static int __init xxx_init(void)
{
return spi_register_driver(&xxx_driver);
}
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
spi_unregister_driver(&xxx_driver);
}
module_init(xxx_init);
module_exit(xxx_exit);
3.SPIデバイスとドライバーのマッチングプロセス
spiデバイスとドライバーのマッチングプロセスは、spiバスによって完了します。
struct bus_type spi_bus_type = {
.name = "spi",
.dev_groups = spi_dev_groups,
.match = spi_match_device,
.uevent = spi_uevent,
};
spiのマッチング関数:
static int spi_match_device(struct device *dev, struct device_driver *drv)
{
const struct spi_device *spi = to_spi_device(dev);
const struct spi_driver *sdrv = to_spi_driver(drv);
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then try ACPI */
if (acpi_driver_match_device(dev, drv))
return 1;
if (sdrv->id_table)
return !!spi_match_id(sdrv->id_table, spi);
return strcmp(spi->modalias, drv->name) == 0;
}
2:SPIホストドライバー分析
ハードウェア情報によると、デバイスはimx6ullシリーズ開発ボードのspi3インターフェイスに接続され、共通デバイスツリーファイルでspi3ノードにクエリを実行します。
ecspi3: ecspi@02010000 {
#address-cells = <1>;
#size-cells = <0>;
compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
reg = <0x02010000 0x4000>;
interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_ECSPI3>,
<&clks IMX6UL_CLK_ECSPI3>;
clock-names = "ipg", "per";
dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
dma-names = "rx", "tx";
status = "disabled";
};
互換性のある属性によると、LinuxカーネルでSPI3ホストコントローラードライバーを見つけることができます。
static struct platform_device_id spi_imx_devtype[] = {
... ...
{
.name = "imx51-ecspi",
.driver_data = (kernel_ulong_t) &imx51_ecspi_devtype_data,
}, {
.name = "imx6ul-ecspi",
.driver_data = (kernel_ulong_t) &imx6ul_ecspi_devtype_data,
}, {
/* sentinel */
}
};
static const struct of_device_id spi_imx_dt_ids[] = {
... ...
{ .compatible = "fsl,imx51-ecspi", .data = &imx51_ecspi_devtype_data, },
{ .compatible = "fsl,imx6ul-ecspi", .data = &imx6ul_ecspi_devtype_data, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, spi_imx_dt_ids);
static struct platform_driver spi_imx_driver = {
.driver = {
.name = DRIVER_NAME,
.of_match_table = spi_imx_dt_ids,
.pm = IMX_SPI_PM,
},
.id_table = spi_imx_devtype,
.probe = spi_imx_probe,
.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);
その中で、spi_imx_devtypeデータはSPIデバイスツリーマッチングテーブルではなく、spi_imx_dt_idsはSPIデバイスツリーマッチングテーブルであり、前の章のI2Cについても同じことが言えます。
spi_imx_probe関数は、デバイスツリーから対応するノード属性値を読み取り、spi_masterを適用して初期化し、最後にspi_bitbang_start(登録関数はspi_register_master)関数を呼び出してspi_masterをLinuxカーネルに登録します。
-->>static int spi_imx_probe(struct platform_device *pdev)
-->>spi_bitbang_start(&spi_imx->bitbang);
-->>spi_register_master(spi_master_get(master));
spi_imx_probe関数には2つの重要な設定があります。1つはSPIホストの最終的なデータ送受信関数spi_imx_transferであり、もう1つは送受信関数のポイントを設定する関数spi_imx_setupxferです。
spi_imx_probe:
spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
spi_imx->bitbang.txrx_bufs = spi_imx_transfer;
-->>spi_imx_transfer
-->>spi_imx_pio_transfer
-->>spi_imx_push
-->>spi_imx->tx(spi_imx);
-->>spi_imx_setupxfer
-->>
/* Initialize the functions for transfer */
if (config.bpw <= 8) {
spi_imx->rx = spi_imx_buf_rx_u8;
spi_imx->tx = spi_imx_buf_tx_u8;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;
} else if (config.bpw <= 16) {
spi_imx->rx = spi_imx_buf_rx_u16;
spi_imx->tx = spi_imx_buf_tx_u16;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
} else {
spi_imx->rx = spi_imx_buf_rx_u32;
spi_imx->tx = spi_imx_buf_tx_u32;
spi_imx->tx_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
spi_imx->rx_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
}
#define MXC_SPI_BUF_TX(type) \
static void spi_imx_buf_tx_##type(struct spi_imx_data *spi_imx) \
{ \
type val = 0; \
\
if (spi_imx->tx_buf) { \
val = *(type *)spi_imx->tx_buf; \
spi_imx->tx_buf += sizeof(type); \
} \
\
spi_imx->count -= sizeof(type); \
\
writel(val, spi_imx->base + MXC_CSPITXDATA); \
}
MXC_SPI_BUF_RX(u8)
MXC_SPI_BUF_TX(u8)
MXC_SPI_BUF_RX(u16)
MXC_SPI_BUF_TX(u16)
MXC_SPI_BUF_RX(u32)
MXC_SPI_BUF_TX(u32)
3:SPIデバイスドライバー分析
1.ドライブデータを初期化します
前述のように、spi_driverを登録すると、デバイスの読み取りと書き込みができるようになります。次の2つの構造に精通する必要があります。
spi_transfer:tx_bufは送信されるデータを保持し、rx_bufは受信されたデータを保持し、lenは転送されるデータの長さです。
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3;
unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
struct list_head transfer_list;
};
spi_message:使用する前にspi_messageを初期化する必要があります。初期化関数はspi_message_initです。
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
struct list_head queue;
void *state;
};
void spi_message_init(struct spi_message *m)
初期化が完了したら、spi_transferをspi_messageキューに追加する必要があります:spi_message_add_tail
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
2.データの送受信
spiデータの準備ができたら、データ送信を行うことができます。データ送信は、同期送信と非同期送信に分けられます。
同期送信:SPIデータ送信の完了待ちをブロックします
int spi_sync(struct spi_device *spi, struct spi_message *message)
非同期送信:ブロックされません。spi_messageで完全なメンバー変数を設定する必要があります。これはコールバック関数です。この関数は、SPI非同期送信が完了すると呼び出されます。
int spi_async(struct spi_device *spi, struct spi_message *message)
3.読み取りおよび書き込み操作の例
/* SPI 多字节发送 */
static int spi_send(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &m); /* 同步传输 */
return ret;
}
/* SPI 多字节接收 */
static int spi_receive(struct spi_device *spi, u8 *buf, int len)
{
int ret;
struct spi_message m;
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
spi_message_init(&m); /* 初始化 spi_message */
spi_message_add_tail(t, &m);/* 将 spi_transfer 添加到 spi_message 队列 */
ret = spi_sync(spi, &m); /* 同步传输 */
return ret;
}
4:例
1.デバイスツリーを変更します
(1)ICM20608デバイスのIOピンを追加します
pinctrl_ecspi3: icm20608 {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x10b0
MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK 0x10b1
MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO 0x10b1
MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI 0x10b1
>;
};
(2)icm20608子ノードをecspi3ノードに追加します
&ecspi3 {
fsl,spi-num-chipselects = <1>; /* 设置当前片选数量为1 */
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* 选用自定义“cs-gpio”属性,而非系统片选属性*/
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>; /* 设置IO要使用的pinctrl子节点 */
status = "okay";
spidev: icm20608@0 { /* 设备连接在ecspi3的第0个通道上 */
compatible = "alientek, icm20608";
spi-max-frequency = <8000000>; /* SPI最大时钟频率为8MHz */
reg = <0>;
};
};
2.ドライブ
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "icm20608reg.h"
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int major;
void *private_data;
int cs_gpio;
signed int gyro_x_adc;
signed int gyro_y_adc;
signed int gyro_z_adc;
signed int accel_x_adc;
signed int accel_y_adc;
signed int accel_z_adc;
signed int temp_adc;
};
static struct icm20608_dev icm20608dev;
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{
int ret;
unsigned char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
gpio_set_value(dev->cs_gpio, 0); /* 片选拉低,选中ICM20608 */
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg | 0x80; /* 写数据的时候寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
/* 第2次,读取数据 */
txdata[0] = 0xff; /* 随便一个值,此处无意义 */
t->rx_buf = buf; /* 读取到的数据 */
t->len = len; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1); /* 片选拉高,释放ICM20608 */
return ret;
}
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
uint8_t char txdata[len];
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
gpio_set_value(dev->cs_gpio, 0);
/* 第1次,发送要读取的寄存地址 */
txdata[0] = reg & ~0x80;
t->tx_buf = txdata;
t->led = 1;
spi_message_init(&m);
spi_message_add_tail(t, &m);
ret = spi_sync(spi, &m);
/* 第2次,发送要写入的数据 */
t->tx_buf = buf; /* 要写入的数据 */
t->len = len; /* 写入的字节数 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
kfree(t); /* 释放内存 */
gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放ICM20608 */
return ret;
}
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 1);
}
void icm20608_readdata(struct icm20608_dev *dev)
{
uint8_t data[14];
icm20608_read_onereg(dev, ICM20_ACCEL_XOUT_H, data, 14);
dev->accel_x_adc = (signed short)((data[0] << 8) | data[1]);
dev->accel_y_adc = (signed short)((data[2] << 8) | data[3]);
dev->accel_z_adc = (signed short)((data[4] << 8) | data[5]);
dev->temp_adc = (signed short)((data[6] << 8) | data[7]);
dev->gyro_x_adc = (signed short)((data[8] << 8) | data[9]);
dev->gyro_y_adc = (signed short)((data[10] << 8) | data[11]);
dev->gyro_z_adc = (signed short)((data[12] << 8) | data[13]);
}
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev;
return 0;
}
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
int16_t data[7];
long err = 0;
struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
icm20608_readdata(dev);
data[0] = dev->gyro_x_adc;
data[1] = dev->gyro_y_adc;
data[2] = dev->gyro_z_adc;
data[3] = dev->accel_x_adc;
data[4] = dev->accel_y_adc;
data[5] = dev->accel_z_adc;
data[6] = dev->temp_adc;
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int icm20608_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations icm20608_fops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
void icm20608_reginit(void)
{
uint8_t value = 0;
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x80);
mdelay(50);
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_1, 0x01);
mdelay(50);
value = icm20608_read_onereg(&icm20608dev, ICM20_WHO_AM_I);
printk("ICM20608 ID = %#X\r\n", value);
icm20608_write_onereg(&icm20608dev, ICM20_SMPLRT_DIV, 0x00); /* 输出速率是内部采样率 */
icm20608_write_onereg(&icm20608dev, ICM20_GYRO_CONFIG, 0x18); /* 陀螺仪±2000dps量程 */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG, 0x18); /* 加速度计±16G量程 */
icm20608_write_onereg(&icm20608dev, ICM20_CONFIG, 0x04); /* 陀螺仪低通滤波BW=20Hz */
icm20608_write_onereg(&icm20608dev, ICM20_ACCEL_CONFIG2, 0x04); /* 加速度计低通滤波BW=21.2Hz */
icm20608_write_onereg(&icm20608dev, ICM20_PWR_MGMT_2, 0x00); /* 打开加速度计和陀螺仪所有轴 */
icm20608_write_onereg(&icm20608dev, ICM20_LP_MODE_CFG, 0x00); /* 关闭低功耗 */
icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 关闭FIFO */
}
static int icm20608_probe(struct spi_device *spi)
{
int ret = 0;
if(icm20608dev.major)
{
icm20608dev.devid = MKDEV(icm20608dev.major, 0);
register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
}
else
{
alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
icm20608dev.major = MAJOR(icm20608dev.devid);
}
cdev_init(&icm20608dev.cdev, &icm20608_fops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.device, NULL, ICM20608_NAME);
icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0){
printk("can't set gpio!\r\n");
}
spi->mode = SPI_MODE_0;
spi_setup(spi);
icm20608dev.private_data = spi;
icm20608_reginit();
return 0;
}
static int icm20608_remove(struct spi_device *spi)
{
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
return 0;
}
static const struct spi_device_id icm20608_id[] = {
{ "alientek, icm20608", 0 },
{}
};
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek, icm20608" },
{}
};
static struct spi_driver icm20608_driver = {
.probe = icm20608_probe,
.remove = icm20608_remove,
.driver = {
.owner = THIS_MODULE,
.name = "icm20608",
.of_match_table = icm20608_of_match,
},
.id_table = icm20608_id,
};
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");