SPI是“Serial Peripheral Interface” 的縮寫,是一種四線制的同步串行通信接口,用來連接微控制器、傳感器、存儲設備,SPI設備分為主設備和從設備兩種,用于通信和控制的四根線分別是:
CS 片選信號
SCK 時鐘信號
MISO 主設備的數據輸入、從設備的數據輸出腳
MOSI 主設備的數據輸出、從設備的數據輸入腳
因為在大多數情況下,CPU或SOC一側通常都是工作在主設備模式,所以,目前的Linux內核版本中,只實現了主模式的驅動框架。
一、硬件結構
通常,負責發出時鐘信號的設備我們稱之為主設備,另一方則作為從設備,下圖是一個SPI系統的硬件連接示例:
如上圖所示,主設備對應SOC芯片中的SPI控制器,通常,一個SOC中可能存在多個SPI控制器,像上面的例子所示,SOC芯片中有3個SPI控制器。每個控制器下可以連接多個SPI從設備,每個從設備有各自獨立的CS引腳。每個從設備共享另外3個信號引腳:SCK、MISO、MOSI。任何時刻,只有一個CS引腳處于有效狀態,與該有效CS引腳連接的設備此時可以與主設備(SPI控制器)通信,其它的從設備處于等待狀態,并且它們的3個引腳必須處于高阻狀態。
二、工作時序
按照時鐘信號和數據信號之間的相位關系,SPI有4種工作時序模式:
我們用CPOL表示時鐘信號的初始電平的狀態,CPOL為0表示時鐘信號初始狀態為低電平,為1表示時鐘信號的初始電平是高電平。另外,我們用CPHA來表示在那個時鐘沿采樣數據,CPHA為0表示在首個時鐘變化沿采樣數據,而CPHA為1則表示要在第二個時鐘變化沿來采樣數據。內核用CPOL和CPHA的組合來表示當前SPI需要的工作模式:
CPOL=0,CPHA=1 模式0
CPOL=0,CPHA=1 模式1
CPOL=1,CPHA=0 模式2
CPOL=1,CPHA=1 模式3
三、確定驅動文件
SPI作為linux里面比較小的一個子系統,其驅動程序位于/drivers/spi/*目錄,首先,我們可以通過Makefile及Kconfig來確定我們需要看的源文件。
[plain] view plain copy#
# Makefile for kernel SPI drivers.
#
# small core, mostly translating board-specific
# config declarations into driver model code
obj-$(CONFIG_SPI_MASTER) += spi.o
obj-$(CONFIG_SPI_SPIDEV) += spidev.o
[plain] view plain copy# SPI master controller drivers (bus)
obj-$(CONFIG_SPI_BITBANG) += spi-bitbang.o
obj-$(CONFIG_SPI_IMX) += spi-imx.o
對應的Kconfig去配置內核
編譯生成的目標文件如下
通過以上分析我們知道,spi驅動由三部分組成,分別是core(spi.c),master controller driver (spi_imx.c)以及SPIprotocol drivers (spidev.c)。
四、數據結構分析
Spi驅動涉及的數據結構主要位于/include/linux/spi.h,其中spi.c,spi-imx.c,spidev.c均用到了spi.h里的結構體。
1.spi_master
spi_master代表一個主機控制器,此處表示imx的SPI控制器。一般不需要自己編寫spi控制器驅動,但是了解這個結構體還是必要的。
[objc] view plain copystruct spi_master {
struct device dev; //設備模型使用
struct list_head list;
/* other than negative (== assign one dynamically), bus_num is fully
* board-specific. usually that simplifies to being SOC-specific.
* example: one SOC has three SPI controllers, numbered 0..2,
* and one board‘s schematics might show it using SPI-2. software
* would normally use bus_num=2 for that controller.
*/
s16 bus_num; //總線(或控制器)編號,imx6q有5個spi控制器,0~4
/* chipselects will be integral to many controllers; some others
* might use board-specific GPIOs.
*/
u16 num_chipselect; //片選數量,決定該控制器下面掛接多少個SPI設備,從設備的片選號不能大于這個數量
/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits; //master支持的設備模式
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_MASTER_HALF_DUPLEX BIT(0) /* can’t do full duplex */
#define SPI_MASTER_NO_RX BIT(1) /* can‘t do buffer read */
#define SPI_MASTER_NO_TX BIT(2) /* can’t do buffer write */
#define SPI_MASTER_MUST_RX BIT(3) /* requires rx */
#define SPI_MASTER_MUST_TX BIT(4) /* requires tx */
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
/* Setup mode and clock, etc (spi driver may call many times)。
*
* IMPORTANT: this may be called when transfers to another
* device are active. DO NOT UPDATE SHARED REGISTERS in ways
* which could break those transfers.
*/
int (*setup)(struct spi_device *spi); //根據spi設備更新硬件配置。設置模式、時鐘等,這個需要我們自己具體實現,主要設置SPI控制器和工作方式
/* bidirectional bulk transfers
*
* + The transfer() method may not sleep; its main role is
* just to add the message to the queue.
* + For now there‘s no remove-from-queue operation, or
* any other request management
* + To a given spi_device, message queueing is pure fifo
*
* + The master’s main job is to process its message queue,
* selecting a chip then transferring data
* + If there are multiple spi_device children, the i/o queue
* arbitration algorithm is unspecified (round robin, fifo,
* priority, reservations, preemption, etc)
*
* + Chipselect stays active during the entire message
* (unless modified by spi_transfer.cs_change != 0)。
* + The message transfers use clock and SPI mode parameters
* previously established by setup() for this device
*/
int (*transfer)(struct spi_device *spi,
struct spi_message *mesg); //添加消息到隊列的方法。這個函數不可睡眠。它的職責是安排發生的傳送并且調用注冊的回調函數complete()。這個不同的控制器要具體實現,傳輸數據最后都要調用這個函數
/* called on release() to free memory provided by spi_master */
void (*cleanup)(struct spi_device *spi); //cleanup函數會在spidev_release函數中被調用,spidev_release被登記為spi dev的release函數。
/*
* Used to enable core support for DMA handling, if can_dma()
* exists and returns true then the transfer will be mapped
* prior to transfer_one() being called. The driver should
* not modify or store xfer and dma_tx and dma_rx must be set
* while the device is prepared.
*/
bool (*can_dma)(struct spi_master *master,
struct spi_device *spi,
struct spi_transfer *xfer);
/*
* These hooks are for drivers that want to use the generic
* master transfer queueing mechanism. If these are used, the
* transfer() function above must NOT be specified by the driver.
* Over time we expect SPI drivers to be phased over to this API.
*/
bool queued;
struct kthread_worker kworker;
struct task_struct *kworker_task;
struct kthread_work pump_messages;
spinlock_t queue_lock;
struct list_head queue;
struct spi_message *cur_msg;
bool busy;
bool running;
bool rt;
bool auto_runtime_pm;
bool cur_msg_prepared;
bool cur_msg_mapped;
struct completion xfer_completion;
size_t max_dma_len;
int (*prepare_transfer_hardware)(struct spi_master *master);
int (*transfer_one_message)(struct spi_master *master,
struct spi_message *mesg);
int (*unprepare_transfer_hardware)(struct spi_master *master);
int (*prepare_message)(struct spi_master *master,
struct spi_message *message);
int (*unprepare_message)(struct spi_master *master,
struct spi_message *message);
/*
* These hooks are for drivers that use a generic implementation
* of transfer_one_message() provied by the core.
*/
void (*set_cs)(struct spi_device *spi, bool enable);
int (*transfer_one)(struct spi_master *master, struct spi_device *spi,
struct spi_transfer *transfer);
/* gpio chip select */
int *cs_gpios;
/* DMA channels for use with core dmaengine helpers */
struct dma_chan *dma_tx;
struct dma_chan *dma_rx;
/* dummy data for full duplex devices */
void *dummy_rx;
void *dummy_tx;
};
2. spi_device
spi_device代表一個外圍spi設備,由master controller driver注冊完成后掃描BSP中注冊設備產生的設備鏈表并向spi_bus注冊產生。在內核中,每個spi_device代表一個物理的spi設備。
[objc] view plain copystruct spi_device {
struct device dev; //設備模型使用
struct spi_master *master; //設備使用的master結構,掛在哪個主控制器下
u32 max_speed_hz; //通訊時鐘最大頻率
u8 chip_select; //片選號,每個master支持多個spi_device
u16 mode; //設備支持的模式,如片選是高or低?
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? 為1時片選的有效信號是高電平*/
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire 發送時低比特在前*/
#define SPI_3WIRE 0x10 /* SI/SO signals shared 輸入輸出信號使用同一根信號線*/
#define SPI_LOOP 0x20 /* loopback mode 回環模式*/
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
#define SPI_TX_DUAL 0x100 /* transmit with 2 wires */
#define SPI_TX_QUAD 0x200 /* transmit with 4 wires */
#define SPI_RX_DUAL 0x400 /* receive with 2 wires */
#define SPI_RX_QUAD 0x800 /* receive with 4 wires */
u8 bits_per_word; //每個字長的比特數,默認是8
int irq; //中斷號
void *controller_state; //控制寄存器狀態
void *controller_data;
char modalias[SPI_NAME_SIZE]; //設備驅動的名字
int cs_gpio; /* chip select gpio */
/*
* likely need more hooks for more protocol options affecting how
* the controller talks to each chip, like:
* - memory packing (12 bit samples into low bits, others zeroed)
* - priority
* - drop chipselect after each word
* - chipselect delays
* - 。。。
*/
};
由于一個SPI總線上可以有多個SPI設備,因此需要片選號來區分它們,SPI控制器根據片選號來選擇不同的片選線,從而實現每次只同一個設備通信。
spi_device的mode成員有兩個比特位含義很重要。SPI_CPHA選擇對數據線采樣的時機,0選擇每個時鐘周期的第一個沿跳變時采樣數據,1選擇第二個時鐘沿采樣數據;SPI_CPOL選擇每個時鐘周期開始的極性,0表示時鐘以低電平開始,1選擇高電平開始。這兩個比特有四種組合,對應SPI_MODE_0~SPI_MODE_3。
另一個比較重要的成員是bits_per_word。這個成員指定每次讀寫的字長,單位是比特。雖然大部分SPI接口的字長是8或者16,仍然會有一些特殊的例子。需要說明的是,如果這個成員為零的話,默認使用8作為字長。
最后一個成員并不是設備的名字,而是需要綁定的驅動的名字。
3.spi_driver
spi_driver代表一個SPI protocol drivers,即外設驅動。
[objc] view plain copystruct spi_driver {
const struct spi_device_id *id_table; //支持的spi_device設備表
int (*probe)(struct spi_device *spi); //和spi匹配成功之后會調用這個方法。因此這個方法需要對設備和私有數據進行初始化。
int (*remove)(struct spi_device *spi); //解除spi_device和spi_driver的綁定,釋放probe申請的資源。
void (*shutdown)(struct spi_device *spi); //關閉
int (*suspend)(struct spi_device *spi, pm_message_t mesg); //掛起
int (*resume)(struct spi_device *spi); //恢復
struct device_driver driver; //設備模型使用
};
通常對于從事Linux驅動工作人員來說,spi設備的驅動主要就是實現這個結構體中的各個接口,并將之注冊到spi子系統中去。
4.spi_transfer
spi_transfer代表一個讀寫緩沖對,包含接收緩沖區及發送緩沖區,其實,spi_transfer的發送是通過構建spi_message實現,通過將spi_transfer中的鏈表transfer_list鏈接到spi_message中的transfers,再以spi_message形勢向底層發送數據。每個spi_transfer都可以對傳輸的一些參數進行設置,使得master controller按照它要求的參數進行數據發送。
[cpp] view plain copystruct spi_transfer {
/* it‘s ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf; //發送緩沖區,要寫入設備的數據(必須是dma_safe),或者為NULL。
void *rx_buf; //接收緩沖區,要讀取的數據緩沖(必須是dma_safe),或者為NULL。
unsigned len; //緩沖區長度,tx和rx的大小(字節數)。這里不是指它的和,而是各自的長度,它們總是相等的。
dma_addr_t tx_dma; //如果spi_message.is_dma_mapped是真,這個是tx的dma地址
dma_addr_t rx_dma; //如果spi_message.is_dma_mapped是真,這個是rx的dma地址
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1; //當前spi_transfer發送完成之后重新片選。影響此次傳輸之后的片選。指示本次transfer結束之后是否要重新片選并調用setup改變設置。這個標志可以減少系統開銷。
u8 tx_nbits;
u8 rx_nbits;
#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; //每個字長的比特數,0代表使用spi_device中的默認值8
u16 delay_usecs; //發送完成一個spi_transfer后延時時間,此次傳輸結束和片選改變之間的延時,之后就會啟動另一個傳輸或者結束整個消息
u32 speed_hz; //通信時鐘。如果是0,使用默認值
struct list_head transfer_list; //用于鏈接到spi_message,用來連接的雙向鏈接節點
};《/span》
5.spi_message
spi_message代表spi消息,由多個spi_transfer段組成。
spi_message用來原子的執行spi_transfer表示的一串數組傳輸請求。
這個傳輸隊列是原子的,這意味著在這個消息完成之前不會有其它消息占用總線。
消息的執行總是按照FIFO的順序。
向底層提交spi_message的代碼要負責管理它的內存空間。未顯示初始化的內存需要使用0來初始化。
[cpp] view plain copystruct spi_message {
struct list_head transfers; //spi_transfer鏈表隊列,此次消息的傳輸段隊列,一個消息可以包含多個傳輸段。
struct spi_device *spi; //傳輸的目的設備
unsigned is_dma_mapped:1; //如果為真,此次調用提供dma和cpu虛擬地址。
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer 。。。 allowing things like “read 16 bit length L”
* immediately followed by “read L bytes”。 Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context); //異步調用完成后的回調函數
void *context; //回調函數的參數
unsigned frame_length;
unsigned actual_length; //《span style=“font-family: Arial, Helvetica, sans-serif;”》實際傳輸的數據長度《/span》
int status; //該消息的發送結果,成功被置0,否則是一個負的錯誤碼。
/* for optional use by whatever driver currently owns the
* spi_message 。。。 between calls to spi_async and then later
* complete(), that’s the spi_master controller driver.
*/
struct list_head queue; //下面兩個成員是給擁有本消息的驅動選用的。spi_master會使用它們。自己最好不要使用。
void *state;
};
控制器驅動會先寫入tx的數據,然后讀取同樣長度的數據。長度指示是len。
如果tx_buff是空指針,填充rx_buff的時候會輸出0(為了產生接收的時鐘),如果rx_buff是NULL,接收到的數據將被丟棄。
只有len長讀的數據會被輸出和接收。
輸出不完整的字長是錯誤的(比如字長為2字節的時候輸出三個字節,最后一個字節湊不成一個整字)。
本地內存中的數據總是使用本地cpu的字節序,無論spi的字節序是大段模式還是小段模式(使用SPI_LSB_FIRS)
當spi_transfer的字長不是8bit的2次冪的整數倍,這些數據字就包含擴展位。在spi通信驅動看來內存中的數據總是剛好對齊的,所以rx中位定義和rx中未使用的比特位總是最高有效位。(比如13bit的字長,每個字占2字節,rx和tx都應該如此存放)
所有的spi傳輸都以使能相關的片選線為開始。一般來說片選線在本消息結束之前保持有效的狀態。驅動可以使用
spi_transfer中的cs_change成員來影響片選:
(i)如果transfer不是message的最后一個,這個標志量可以方便的將片選線置位無效的狀態。
有時需要這種方法來告知芯片一個命令的結束并使芯片完成這一批處理任務。
(ii)當這個trasfer是最后一個時,片選可以一直保持有效知道下一個transfer到來。
在多spi從機的總線上沒有辦法阻止其他設備接收數據,這種方法可以作為一個特別的提示;開始往另一個設備傳輸信息就要先將本芯片的片選置為無效。但在其他情況下,這可以保證正確性。一些設備后面的信息依賴于前面的信息并且在一個處理序列完成后需要禁用片選線。
上面這段是翻譯的,講的不明白。
再說一下:cs_change影響此transfer完成后是否禁用片選線并調用setup改變配置。(這個標志量就是chip select change片選改變的意思)
沒有特殊情況,一個spi_message應該只在最后一個transfer置位該標志量。
6.spi_board_info
spi_device的板信息用spi_board_info結構體描述,該結構體記錄著SPI外設使用的主機控制器序號、片選序號、數據比特率、SPI傳輸模式(即CPOL、CPHA)等。ARM Linux3.x之后的內核在改為設備樹之后,不再需要在arch/arm/mach-xxx中編碼SPI的板級信息了,而傾向于在SPI控制器節點下填寫子節點。
[cpp] view plain copystruct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* “modalias” is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE];
const void *platform_data;
void *controller_data;
int irq;
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz;
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it‘s less than num_chipselect.
*/
u16 bus_num;
u16 chip_select;
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u16 mode;
/* 。。。 may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
7.spi_bitbang
spi_bitbang是具體的負責信息傳輸的數據結構,它維護一個workqueue_struct,每收到一個消息,都會向其中添加一個work_struct,由內核守護進程在將來的某個時間調用該work_struct中的function進行消息發送。
[cpp] view plain copystruct spi_bitbang {
spinlock_t lock;
u8 busy; //忙標志
u8 use_dma;
u8 flags; /* extra spi-》mode support */
struct spi_master *master;
/* setup_transfer() changes clock and/or wordsize to match settings
* for this transfer; zeroes restore defaults from spi_device.
*/
int (*setup_transfer)(struct spi_device *spi,
struct spi_transfer *t); //對數據傳輸進行設置
void (*chipselect)(struct spi_device *spi, int is_on); //控制片選
#define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */
#define BITBANG_CS_INACTIVE 0
/* txrx_bufs() may handle dma mapping for transfers that don’t
* already have one (transfer.{tx,rx}_dma is zero), or use PIO
*/
int (*txrx_bufs)(struct spi_device *spi, struct spi_transfer *t); //實際的數據傳輸函數
/* txrx_word[SPI_MODE_*]() just looks like a shift register */
u32 (*txrx_word[4])(struct spi_device *spi,
unsigned nsecs,
u32 word, u8 bits);
};
評論
查看更多