Linux下SPI驅(qū)動詳解(干貨)
本文由嵌入式大牛:蒙工投稿!
1. SPI總線
1.1. SPI總線概述
SPI,是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設(shè)備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應(yīng)用在 EEPROM,F(xiàn)LASH,實時時鐘,AD轉(zhuǎn)換器,還有數(shù)字信號處理器和數(shù)字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節(jié)約了芯片的管腳,同時為PCB的布局上節(jié)省空間,提供方便,正是出于這種簡單易用的特性,現(xiàn)在越來越多的芯片集成了這種通信協(xié)議。SPI總線的構(gòu)成及信號類型如圖1-1所示:
MOSI – 主設(shè)備數(shù)據(jù)輸出,從設(shè)備數(shù)據(jù)輸入 對應(yīng)MOSI master output slave input MISO – 主設(shè)備數(shù)據(jù)輸入,從設(shè)備數(shù)據(jù)輸出 對應(yīng)MISO master input slave output CLK – 時鐘信號,由主設(shè)備產(chǎn)生 nCS – 從設(shè)備使能信號,由主設(shè)備控制

1.2. SPI總線時序
SPI接口在Master控制下產(chǎn)生的從設(shè)備使能信號和時鐘信號,兩個雙向移位寄存器按位傳輸進行數(shù)據(jù)交換,傳輸數(shù)據(jù)高位在前(MSB first),低位在后。如下圖所示,在CLK的下降沿上數(shù)據(jù)改變,上升沿一位數(shù)據(jù)被存入移位寄存器。

在一個SPI時鐘周期內(nèi),會完成如下操作:(1)Master通過MOSI線發(fā)送1位數(shù)據(jù),同時Slave通過MOSI線讀取這1位數(shù)據(jù);(2)Slave通過MISO線發(fā)送1位數(shù)據(jù),同時Master通過MISO線讀取這1位數(shù)據(jù)。Master和Slave各有一個移位寄存器,如圖1-3所示,而且這兩個移位寄存器連接成環(huán)狀。依照CLK的變化,數(shù)據(jù)以MSB first的方式依次移出Master寄存器和Slave寄存器,并且依次移入Slave寄存器和Master寄存器。當(dāng)寄存器中的內(nèi)容全部移出時,相當(dāng)于完成了兩個寄存器內(nèi)容的交換。
1.3. SPI總線傳輸模式
SPI總線傳輸一共有4種模式,這4種模式分別由時鐘極性(CPOL,Clock Polarity)和時鐘相位(CPHA,Clock Phase)來定義,其中CPOL參數(shù)規(guī)定了SCK時鐘信號空閑狀態(tài)的電平,CPHA規(guī)定了數(shù)據(jù)是在SCK時鐘的上升沿被采樣還是下降沿被采樣。這四種模式的時序圖如下圖1-4所示:
模式0:CPOL= 0,CPHA=0。CLK串行時鐘線空閑是為低電平,數(shù)據(jù)在SCK時鐘的上升沿被采樣,數(shù)據(jù)在CLK時鐘的下降沿切換 模式1:CPOL= 0,CPHA=1。CLK串行時鐘線空閑是為低電平,數(shù)據(jù)在SCK時鐘的下降沿被采樣,數(shù)據(jù)在CLK時鐘的上升沿切換 模式2:CPOL= 1,CPHA=0。CLK串行時鐘線空閑是為高電平,數(shù)據(jù)在SCK時鐘的下降沿被采樣,數(shù)據(jù)在CLK時鐘的上升沿切換 模式3:CPOL= 1,CPHA=1。CLK串行時鐘線空閑是為高電平,數(shù)據(jù)在SCK時鐘的上升沿被采樣,數(shù)據(jù)在CLK時鐘的下降沿切換 其中比較常用的模式是模式0和模式3。為了更清晰的描述SPI總線的時序,下面展現(xiàn)了模式0下的SPI時序圖1-5:

1.4. SPI總線的優(yōu)缺點
(1) 在點對點的通信中,SPI接口不需要進行尋址操作,且為全雙工通信,顯得簡單高效。(2) SPI接口沒有指定的流控制,沒有應(yīng)答機制確認(rèn)是否接收到數(shù)據(jù)。
2. Linux SPI 框架
2.1. 軟件架構(gòu)
Linux系統(tǒng)對spi設(shè)備具有很好的支持,linux系統(tǒng)下的spi驅(qū)動程序從邏輯上可以分為3個部分:
spi核心(SPI Core):SPI Core是Linux內(nèi)核用來維護和管理spi的核心部分,SPI Core提供操作接口函數(shù),允許一個spi master,spi driver和spi device初始化時在SPI Core中進行注冊,以及退出時進行注銷。 spi控制器驅(qū)動(SPI Master Driver):SPI Master針對不同類型的spi控制器硬件,實現(xiàn)spi總線的硬件訪問操作。SPI Master通過接口函數(shù)向SPI Core注冊一個控制器。 spi設(shè)備驅(qū)動(SPI Device Driver):SPI Driver是對應(yīng)于spi設(shè)備端的驅(qū)動程序,通過接口函數(shù)向SPI Core進行注冊,SPI Driver的作用是將spi設(shè)備掛接到spi總線上;Linux的軟件架構(gòu)圖如圖2-1所示:

2.2. 初始化及退出流程
2.2.1. 注冊spi控制器
注冊spi控制器到內(nèi)核分為兩個階段:第一個階段,使用spi_alloc_master,分配一個spi_master的空間,具體流程如圖2-2所示:
第二階段,使用spi_register_master將第一階段分配的spi_master注冊到內(nèi)核中,具體流程如2-3所示:
2.2.2. 注銷spi控制器
spi控制器注銷的流程如圖2-4所示:
2.3. 關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
2.3.1. spi_device
struct spi_device {
struct device dev; /*spi控制器對應(yīng)的device結(jié)構(gòu)
struct spi_master *master; /*設(shè)備使用的master結(jié)構(gòu),掛在哪個主控制器下*/
u32 max_speed_hz; /*通訊時鐘最大頻率*/
u8 chip_select; /*片選號,每個master支持多個spi_device */
u8 mode;
#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? */
#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 */
u8 bits_per_word; /*每個字長的比特數(shù),默認(rèn)是8*/
int irq;
void *controller_state; /*控制器狀態(tài)*/
void *controller_data; /*控制器數(shù)據(jù)*/
char modalias[SPI_NAME_SIZE]; /* 設(shè)備驅(qū)動的名字 */
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_device代表一個外圍spi設(shè)備,由master controller driver注冊完成后掃描BSP中注冊設(shè)備產(chǎn)生的設(shè)備鏈表并向spi_bus注冊產(chǎn)生。在內(nèi)核中,每個spi_device代表一個物理的spi設(shè)備。
2.3.2. spi_driver
struct spi_driver {
const struct spi_device_id *id_table; /*支持的spi_device設(shè)備表*/
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
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;
};
spi_driver代表一個SPI protocol drivers,即外設(shè)驅(qū)動
2.3.3. struct spi_master
struct spi_master {
struct device dev; /*spi控制器對應(yīng)的device結(jié)構(gòu)*/
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; /*總線(或控制器編號)*/
/* chipselects will be integral to many controllers; some others
* might use board-specific GPIOs.
*/
u16 num_chipselect; /*片選數(shù)量*/
/* 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支持的設(shè)備模式 */
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
/* other constraints relevant to this driver */
u16 flags; /*用于限定某些限制條件的標(biāo)志位
#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 */
/* 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); /*根據(jù)spi設(shè)備更新硬件配置。設(shè)置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); /*添加消息到隊列的方法,此函數(shù)不可睡眠。它的職責(zé)是安排發(fā)生的傳送并且調(diào)用注冊的回調(diào)函數(shù)complete()*/
/* called on release() to free memory provided by spi_master */
void (*cleanup)(struct spi_device *spi);/*cleanup函數(shù)會在spidev_release函數(shù)中被調(diào)用,spidev_release被登記為spi dev的release函數(shù)。*/
/*
* 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; /*用于管理數(shù)據(jù)傳輸消息隊列的工作隊列線程*/
struct task_struct *kworker_task;
struct kthread_work pump_messages; /*具體實現(xiàn)數(shù)據(jù)傳輸隊列的工作隊列*/
spinlock_t queue_lock;
struct list_head queue; /*該控制器的消息隊列,所有等待傳輸?shù)年犃袙煸谠撴湵硐?/
struct spi_message *cur_msg;/*當(dāng)前正在處理的消息隊列*/
bool busy; /忙狀態(tài)*/
bool running; /*正在跑*/
bool rt;
int (*prepare_transfer_hardware)(struct spi_master *master); /*回調(diào)函數(shù),正式發(fā)起傳輸前會被調(diào)用,用于準(zhǔn)備硬件資源*/
int (*transfer_one_message)(struct spi_master *master, struct spi_message *mesg); /*單個消息的原子傳輸回調(diào)函數(shù),隊列中每個消息都會回調(diào)一次該回調(diào)來完成傳輸工作*/
int (*unprepare_transfer_hardware)(struct spi_master *master); /*清理回調(diào)函數(shù)*/
/* gpio chip select */
int *cs_gpios;
};
spi_master代表一個spi控制器。
2.3.4. struct spi_message 和spi_transfer
要完成和SPI設(shè)備的數(shù)據(jù)傳輸工作,我們還需要另外兩個數(shù)據(jù)結(jié)構(gòu):spi_message和spi_transfer。
spi_message包含了一個的spi_transfer結(jié)構(gòu)序列,一旦控制器接收了一個spi_message,其中的spi_transfer應(yīng)該按順序被發(fā)送,并且不能被其它spi_message打斷,所以我們認(rèn)為spi_message就是一次SPI數(shù)據(jù)交換的原子操作。下面我們看看這兩個數(shù)據(jù)結(jié)構(gòu)的定義:
struct spi_message :
struct spi_message {
struct list_head transfers; /*spi_transfer鏈表隊列,此次消息的傳輸段隊列,一個消息可以包含多個傳輸段。*/
struct spi_device *spi; /*傳輸?shù)哪康脑O(shè)備*/
unsigned is_dma_mapped:1; /*如果為真,此次調(diào)用提供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);/*異步調(diào)用完成后的回調(diào)函數(shù)*/
void *context; /*回調(diào)函數(shù)的參數(shù)*/
unsigned actual_length; /*實際傳輸?shù)拈L度*/
int status; /*該消息的發(fā)送結(jié)果,成功被置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;
void *state;
};
鏈表字段queue用于把該結(jié)構(gòu)掛在代表控制器的spi_master結(jié)構(gòu)的queue字段上,控制器上可以同時被加入多個spi_message進行排隊。另一個鏈表字段transfers則用于鏈接掛在本message下的spi_tranfer結(jié)構(gòu)。complete回調(diào)函數(shù)則會在該message下的所有spi_transfer都被傳輸完成時被調(diào)用,以便通知協(xié)議驅(qū)動處理接收到的數(shù)據(jù)以及準(zhǔn)備下一批需要發(fā)送的數(shù)據(jù)。我們再來看看spi_transfer結(jié)構(gòu):spi_transfer
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
*/
const void *tx_buf; /*發(fā)送緩沖區(qū)*/
void *rx_buf; /*接收緩沖區(qū)*/
unsigned len; /*緩沖區(qū)長度,tx和rx的大小(字節(jié)數(shù))。指它們各自的大小*/
dma_addr_t tx_dma; /*tx的dma地址*/
dma_addr_t rx_dma; /*rx的dma地址*/
unsigned cs_change:1; /*當(dāng)前spi_transfer發(fā)送完成之后重新片選*/
u8 bits_per_word; /*每個字長的比特數(shù),0代表使用spi_device中的默認(rèn)值8*/
u16 delay_usecs; /*發(fā)送完成一個spi_transfer后的延時時間,此次傳輸結(jié)束和片選改變之間的延時,之后就會啟動另一個傳輸或者結(jié)束整個消息*/
u32 speed_hz; /*通信時鐘。如果是0,使用默認(rèn)值*/
#ifdef CONFIG_SPI_LOMBO
struct lombo_spi_operate_para *esop;
#endif
struct list_head transfer_list; /*用于鏈接到spi_message,用來連接的雙向鏈接節(jié)點*/
};
首先,transfer_list鏈表字段用于把該transfer掛在一個spi_message結(jié)構(gòu)中,tx_buf和rx_buf提供了非dma模式下的數(shù)據(jù)緩沖區(qū)地址,len則是需要傳輸數(shù)據(jù)的長度,tx_dma和rx_dma則給出了dma模式下的緩沖區(qū)地址。原則來講,spi_transfer才是傳輸?shù)淖钚挝唬杂忠M了spi_message進行打包,我覺得原因是:有時候希望往spi設(shè)備的多個不連續(xù)的地址(或寄存器)一次性寫入,如果沒有spi_message進行把這樣的多個spi_transfer打包,因為通常真正的數(shù)據(jù)傳送工作是在另一個內(nèi)核線程(工作隊列)中完成的,不打包的后果就是會造成更多的進程切換,效率降低,延遲增加,尤其對于多個不連續(xù)地址的小規(guī)模數(shù)據(jù)傳送而言就更為明顯。
2.3.5. spi_board_info
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]; /*名字*/
const void *platform_data; /*平臺數(shù)據(jù)*/
void *controller_data; /*控制器數(shù)據(jù)*/
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; /*spi總線編號*/
u16 chip_select; /*片選*/
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 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
*/
};
2.4. 數(shù)據(jù)傳輸流程
整體的數(shù)據(jù)傳輸流程大致上是這樣的:
定義一個spi_message結(jié)構(gòu); 用spi_message_init函數(shù)初始化spi_message; 定義一個或數(shù)個spi_transfer結(jié)構(gòu),初始化并為數(shù)據(jù)準(zhǔn)備緩沖區(qū)并賦值給spi_transfer相應(yīng)的字段(tx_buf,rx_buf等); 通過spi_message_init函數(shù)把這些spi_transfer掛在spi_message結(jié)構(gòu)下; 如果使用同步方式,調(diào)用spi_sync(),如果使用異步方式,調(diào)用spi_async();(我調(diào)試外設(shè)時,只使用過spi_sync
傳輸示意圖如圖2-5所示:
2.4.1. 數(shù)據(jù)準(zhǔn)備
2.4.1.1. spi_message_init
static inline void spi_message_init(struct spi_message *m)
{
memset(m, 0, sizeof *m);
INIT_LIST_HEAD(&m->transfers);
}
初始化spi_message:清空message,初始化transfers鏈表頭。
2.4.1.2. spi_message_add_tail
static inline void
spi_message_add_tail(struct spi_transfer *t, struct spi_message *m)
{
list_add_tail(&t->transfer_list, &m->transfers);
}
將spi_transfer加入到spi_message的鏈表尾部。
2.4.2. 數(shù)據(jù)傳輸
SPI數(shù)據(jù)傳輸可以有兩種方式:同步方式和異步方式。所謂同步方式是指數(shù)據(jù)傳輸?shù)陌l(fā)起者必須等待本次傳輸?shù)慕Y(jié)束,期間不能做其它事情,用代碼來解釋就是,調(diào)用傳輸?shù)暮瘮?shù)后,直到數(shù)據(jù)傳輸完成,函數(shù)才會返回。而異步方式則正好相反,數(shù)據(jù)傳輸?shù)陌l(fā)起者無需等待傳輸?shù)慕Y(jié)束,數(shù)據(jù)傳輸期間還可以做其它事情,用代碼來解釋就是,調(diào)用傳輸?shù)暮瘮?shù)后,函數(shù)會立刻返回而不用等待數(shù)據(jù)傳輸完成,我們只需設(shè)置一個回調(diào)函數(shù),傳輸完成后,該回調(diào)函數(shù)會被調(diào)用以通知發(fā)起者數(shù)據(jù)傳送已經(jīng)完成。同步方式簡單易用,很適合處理那些少量數(shù)據(jù)的單次傳輸。但是對于數(shù)據(jù)量大、次數(shù)多的傳輸來說,異步方式就顯得更加合適。對于SPI控制器來說,要支持異步方式必須要考慮以下兩種狀況:
對于同一個數(shù)據(jù)傳輸?shù)陌l(fā)起者,既然異步方式無需等待數(shù)據(jù)傳輸完成即可返回,返回后,該發(fā)起者可以立刻又發(fā)起一個message,而這時上一個message還沒有處理完。 對于另外一個不同的發(fā)起者來說,也有可能同時發(fā)起一次message傳輸請求 首先分析spi_sync()接口的實現(xiàn)流程,如圖2-6: 
其次分析spi_async_locked接口的實現(xiàn)流程,如圖2-7所示:
spi_queued_transfer接口的實現(xiàn)流程如圖3-8所示:
spi_pump_messages函數(shù)的處理流程如圖3-9所示:
圖中transfer_one_message是spi控制器驅(qū)動要實現(xiàn)的,主要功能是處理spi_message中的每個spi_transfer。
2.5. 關(guān)鍵函數(shù)解析
2.5.1. spi_alloc_master
原型:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
功能:分配一個spi_master結(jié)構(gòu)體指針。
參數(shù):dev:spi控制器device指針 size :分配的driver-private data大小
返回值 :成功,返回spi_master指針;否則返回NULL
2.5.2. spi_register_master
原型:
int spi_register_master(struct spi_master *master)
功能 注冊spi控制器驅(qū)動到內(nèi)核。
參數(shù) master:spi_master指針
返回值 成功,返回0;否則返回錯誤碼
2.5.3. spi_unregister_master
原型:
void spi_unregister_master(struct spi_master *master)
功能 注銷spi控制器驅(qū)動。
參數(shù) master:spi_master指針
返回值 無
3. Demo
(參考自正點原子)
#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"
/***************************************************************
Copyright ? ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : icm20608.c
作者 : 左工
版本 : V1.0
描述 : ICM20608 SPI驅(qū)動程序
其他 : 無
論壇 :
日志 : 初版V1.0 2019/9/2 左工創(chuàng)建
***************************************************************/
#define ICM20608_CNT 1
#define ICM20608_NAME "icm20608"
struct icm20608_dev {
dev_t devid; /* 設(shè)備號 */
struct cdev cdev; /* cdev */
struct class *class; /* 類 */
struct device *device; /* 設(shè)備 */
struct device_node *nd; /* 設(shè)備節(jié)點 */
int major; /* 主設(shè)備號 */
void *private_data; /* 私有數(shù)據(jù) */
int cs_gpio; /* 片選所使用的GPIO編號 */
signed int gyro_x_adc; /* 陀螺儀X軸原始值 */
signed int gyro_y_adc; /* 陀螺儀Y軸原始值 */
signed int gyro_z_adc; /* 陀螺儀Z軸原始值 */
signed int accel_x_adc; /* 加速度計X軸原始值 */
signed int accel_y_adc; /* 加速度計Y軸原始值 */
signed int accel_z_adc; /* 加速度計Z軸原始值 */
signed int temp_adc; /* 溫度原始值 */
};
static struct icm20608_dev icm20608dev;
/*
* @description : 從icm20608讀取多個寄存器數(shù)據(jù)
* @param - dev: icm20608設(shè)備
* @param - reg: 要讀取的寄存器首地址
* @param - val: 讀取到的數(shù)據(jù)
* @param - len: 要讀取的數(shù)據(jù)長度
* @return : 操作結(jié)果
*/
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); /* 申請內(nèi)存 */
/* 第1次,發(fā)送要讀取的寄存地址 */
txdata[0] = reg | 0x80; /* 寫數(shù)據(jù)的時候寄存器地址bit8要置1 */
t->tx_buf = txdata; /* 要發(fā)送的數(shù)據(jù) */
t->len = 1; /* 1個字節(jié) */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 將spi_transfer添加到spi_message隊列 */
ret = spi_sync(spi, &m); /* 同步發(fā)送 */
/* 第2次,讀取數(shù)據(jù) */
txdata[0] = 0xff; /* 隨便一個值,此處無意義 */
t->rx_buf = buf; /* 讀取到的數(shù)據(jù) */
t->len = len; /* 要讀取的數(shù)據(jù)長度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 將spi_transfer添加到spi_message隊列 */
ret = spi_sync(spi, &m); /* 同步發(fā)送 */
kfree(t); /* 釋放內(nèi)存 */
gpio_set_value(dev->cs_gpio, 1); /* 片選拉高,釋放ICM20608 */
return ret;
}
/*
* @description : 向icm20608多個寄存器寫入數(shù)據(jù)
* @param - dev: icm20608設(shè)備
* @param - reg: 要寫入的寄存器首地址
* @param - val: 要寫入的數(shù)據(jù)緩沖區(qū)
* @param - len: 要寫入的數(shù)據(jù)長度
* @return : 操作結(jié)果
*/
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 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;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申請內(nèi)存 */
gpio_set_value(dev->cs_gpio, 0); /* 片選拉低 */
/* 第1次,發(fā)送要讀取的寄存地址 */
txdata[0] = reg & ~0x80; /* 寫數(shù)據(jù)的時候寄存器地址bit8要清零 */
t->tx_buf = txdata; /* 要發(fā)送的數(shù)據(jù) */
t->len = 1; /* 1個字節(jié) */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 將spi_transfer添加到spi_message隊列 */
ret = spi_sync(spi, &m); /* 同步發(fā)送 */
/* 第2次,發(fā)送要寫入的數(shù)據(jù) */
t->tx_buf = buf; /* 要寫入的數(shù)據(jù) */
t->len = len; /* 寫入的字節(jié)數(shù) */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 將spi_transfer添加到spi_message隊列 */
ret = spi_sync(spi, &m); /* 同步發(fā)送 */
kfree(t); /* 釋放內(nèi)存 */
gpio_set_value(dev->cs_gpio, 1);/* 片選拉高,釋放ICM20608 */
return ret;
}
/*
* @description : 讀取icm20608指定寄存器值,讀取一個寄存器
* @param - dev: icm20608設(shè)備
* @param - reg: 要讀取的寄存器
* @return : 讀取到的寄存器值
*/
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
u8 data = 0;
icm20608_read_regs(dev, reg, &data, 1);
return data;
}
/*
* @description : 向icm20608指定寄存器寫入指定的值,寫一個寄存器
* @param - dev: icm20608設(shè)備
* @param - reg: 要寫的寄存器
* @param - data: 要寫入的值
* @return : 無
*/
static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
u8 buf = value;
icm20608_write_regs(dev, reg, &buf, 1);
}
/*
* @description : 讀取ICM20608的數(shù)據(jù),讀取原始數(shù)據(jù),包括三軸陀螺儀、
* : 三軸加速度計和內(nèi)部溫度。
* @param - dev : ICM20608設(shè)備
* @return : 無。
*/
void icm20608_readdata(struct icm20608_dev *dev)
{
unsigned char data[14];
icm20608_read_regs(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]);
}
/*
* @description : 打開設(shè)備
* @param - inode : 傳遞給驅(qū)動的inode
* @param - filp : 設(shè)備文件,file結(jié)構(gòu)體有個叫做privateate_data的成員變量
* 一般在open的時候?qū)rivate_data似有向設(shè)備結(jié)構(gòu)體。
* @return : 0 成功;其他 失敗
*/
static int icm20608_open(struct inode *inode, struct file *filp)
{
filp->private_data = &icm20608dev; /* 設(shè)置私有數(shù)據(jù) */
return 0;
}
/*
* @description : 從設(shè)備讀取數(shù)據(jù)
* @param - filp : 要打開的設(shè)備文件(文件描述符)
* @param - buf : 返回給用戶空間的數(shù)據(jù)緩沖區(qū)
* @param - cnt : 要讀取的數(shù)據(jù)長度
* @param - offt : 相對于文件首地址的偏移
* @return : 讀取的字節(jié)數(shù),如果為負值,表示讀取失敗
*/
static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
signed int 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;
}
/*
* @description : 關(guān)閉/釋放設(shè)備
* @param - filp : 要關(guān)閉的設(shè)備文件(文件描述符)
* @return : 0 成功;其他 失敗
*/
static int icm20608_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* icm20608操作函數(shù) */
static const struct file_operations icm20608_ops = {
.owner = THIS_MODULE,
.open = icm20608_open,
.read = icm20608_read,
.release = icm20608_release,
};
/*
* ICM20608內(nèi)部寄存器初始化函數(shù)
* @param : 無
* @return : 無
*/
void icm20608_reginit(void)
{
u8 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); /* 輸出速率是內(nèi)部采樣率 */
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); /* 關(guān)閉低功耗 */
icm20608_write_onereg(&icm20608dev, ICM20_FIFO_EN, 0x00); /* 關(guān)閉FIFO */
}
/*
* @description : spi驅(qū)動的probe函數(shù),當(dāng)驅(qū)動與
* 設(shè)備匹配以后此函數(shù)就會執(zhí)行
* @param - client : spi設(shè)備
* @param - id : spi設(shè)備ID
*
*/
static int icm20608_probe(struct spi_device *spi)
{
int ret = 0;
/* 1、構(gòu)建設(shè)備號 */
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);
}
/* 2、注冊設(shè)備 */
cdev_init(&icm20608dev.cdev, &icm20608_ops);
cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);
/* 3、創(chuàng)建類 */
icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
if (IS_ERR(icm20608dev.class)) {
return PTR_ERR(icm20608dev.class);
}
/* 4、創(chuàng)建設(shè)備 */
icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
if (IS_ERR(icm20608dev.device)) {
return PTR_ERR(icm20608dev.device);
}
/* 獲取設(shè)備樹中cs片選信號 */
icm20608dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(icm20608dev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
/* 2、 獲取設(shè)備樹中的gpio屬性,得到BEEP所使用的BEEP編號 */
icm20608dev.cs_gpio = of_get_named_gpio(icm20608dev.nd, "cs-gpio", 0);
if(icm20608dev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
/* 3、設(shè)置GPIO1_IO20為輸出,并且輸出高電平 */
ret = gpio_direction_output(icm20608dev.cs_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/*初始化spi_device */
spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
icm20608dev.private_data = spi; /* 設(shè)置私有數(shù)據(jù) */
/* 初始化ICM20608內(nèi)部寄存器 */
icm20608_reginit();
return 0;
}
/*
* @description : spi驅(qū)動的remove函數(shù),移除spi驅(qū)動的時候此函數(shù)會執(zhí)行
* @param - client : spi設(shè)備
* @return : 0,成功;其他負值,失敗
*/
static int icm20608_remove(struct spi_device *spi)
{
/* 刪除設(shè)備 */
cdev_del(&icm20608dev.cdev);
unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);
/* 注銷掉類和設(shè)備 */
device_destroy(icm20608dev.class, icm20608dev.devid);
class_destroy(icm20608dev.class);
return 0;
}
/* 傳統(tǒng)匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
{"alientek,icm20608", 0},
{}
};
/* 設(shè)備樹匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
{ .compatible = "alientek,icm20608" },
{ /* Sentinel */ }
};
/* SPI驅(qū)動結(jié)構(gòu)體 */
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,
};
/*
* @description : 驅(qū)動入口函數(shù)
* @param : 無
* @return : 無
*/
static int __init icm20608_init(void)
{
return spi_register_driver(&icm20608_driver);
}
/*
* @description : 驅(qū)動出口函數(shù)
* @param : 無
* @return : 無
*/
static void __exit icm20608_exit(void)
{
spi_unregister_driver(&icm20608_driver);
}
module_init(icm20608_init);
module_exit(icm20608_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(yikoulinux");
嵌入式編程專輯 Linux 學(xué)習(xí)專輯 C/C++編程專輯 Qt進階學(xué)習(xí)專輯 關(guān)注微信公眾號『技術(shù)讓夢想更偉大』,后臺回復(fù)“m”查看更多內(nèi)容。 長按前往圖中包含的公眾號關(guān)注
