Linux系統SPI驅動學習筆記(一)
- CS 片選訊號
- SCK 時鐘訊號
- MISO 主裝置的資料輸入、從裝置的資料輸出腳
- MOSI 主裝置的資料輸出、從裝置的資料輸入腳
一、硬體結構
通常,負責發出時鐘訊號的裝置我們稱之為主裝置,另一方則作為從裝置,下圖是一個SPI系統的硬體連線示例:
如上圖所示,主裝置對應SOC晶片中的SPI控制器,通常,一個SOC中可能存在多個SPI控制器,像上面的例子所示,SOC晶片中有3個SPI控制器。每個控制器下可以連線多個SPI從裝置,每個從裝置有各自獨立的CS引腳。每個從裝置共享另外3個訊號引腳:SCK、MISO、MOSI。任何時刻,只有一個CS引腳處於有效狀態,與該有效CS引腳連線的裝置此時可以與主裝置(SPI控制器)通訊,其它的從裝置處於等待狀態,並且它們的3個引腳必須處於高阻狀態。
二、工作時序
按照時鐘訊號和資料訊號之間的相位關係,SPI有4種工作時序模式:
- 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 print?- #
- # 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
- # 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 copy print?- struct 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)(structspi_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)(structspi_device *spi,
- structspi_message *mesg); //新增訊息到佇列的方法。這個函式不可睡眠。它的職責是安排發生的傳送並且呼叫註冊的回撥函式complete()。這個不同的控制器要具體實現,傳輸資料最後都要呼叫這個函式
- /* called on release() to free memory provided by spi_master */
- void (*cleanup)(structspi_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)(structspi_master *master,
- structspi_device *spi,
- structspi_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)(structspi_master *master);
- int (*transfer_one_message)(structspi_master *master,
- structspi_message *mesg);
- int (*unprepare_transfer_hardware)(structspi_master *master);
- int (*prepare_message)(structspi_master *master,
- structspi_message *message);
- int (*unprepare_message)(structspi_master *master,
- structspi_message *message);
- /*
- * These hooks are for drivers that use a generic implementation
- * of transfer_one_message() provied by the core.
- */
- void (*set_cs)(structspi_device *spi, bool enable);
- int (*transfer_one)(structspi_master *master, structspi_device *spi,
- structspi_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 copy print?- struct 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 copy print?- struct spi_driver {
- conststructspi_device_id *id_table; //支援的spi_device裝置表
- int (*probe)(structspi_device *spi); //和spi匹配成功之後會呼叫這個方法。因此這個方法需要對裝置和私有資料進行初始化。
- int (*remove)(structspi_device *spi); //解除spi_device和spi_driver的繫結,釋放probe申請的資源。
- void (*shutdown)(structspi_device *spi); //關閉
- int (*suspend)(structspi_device *spi, pm_message_t mesg); //掛起
- int (*resume)(structspi_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 copy print?- struct 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
- */
- constvoid *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來初始化。
- struct 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 copy print?- struct 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];
- constvoid *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 copy print?- struct 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);
- };