Linux下如何進行SPI驅動,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
1. SPI總線
1.1. SPI總線概述
SPI,是英語Serial Peripheral interface的縮寫,顧名思義就是串行外圍設備接口。是Motorola首先在其MC68HCXX系列處理器上定義的。SPI接口主要應用在 EEPROM,FLASH,實時時鐘,AD轉換器,還有數字信號處理器和數字信號解碼器之間。SPI,是一種高速的,全雙工,同步的通信總線,并且在芯片的管腳上只占用四根線,節約了芯片的管腳,同時為PCB的布局上節省空間,提供方便,正是出于這種簡單易用的特性,現在越來越多的芯片集成了這種通信協議。SPI總線的構成及信號類型如圖1-1所示:
MOSI – 主設備數據輸出,從設備數據輸入 對應MOSI master output slave input
MISO – 主設備數據輸入,從設備數據輸出 對應MISO master input slave output
CLK – 時鐘信號,由主設備產生
nCS – 從設備使能信號,由主設備控制

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

圖1-2 spi傳輸時序圖
在一個SPI時鐘周期內,會完成如下操作:(1)Master通過MOSI線發送1位數據,同時Slave通過MOSI線讀取這1位數據;(2)Slave通過MISO線發送1位數據,同時Master通過MISO線讀取這1位數據。Master和Slave各有一個移位寄存器,如圖1-3所示,而且這兩個移位寄存器連接成環狀。依照CLK的變化,數據以MSB first的方式依次移出Master寄存器和Slave寄存器,并且依次移入Slave寄存器和Master寄存器。當寄存器中的內容全部移出時,相當于完成了兩個寄存器內容的交換。
1.3. SPI總線傳輸模式
SPI總線傳輸一共有4種模式,這4種模式分別由時鐘極性(CPOL,Clock Polarity)和時鐘相位(CPHA,Clock Phase)來定義,其中CPOL參數規定了SCK時鐘信號空閑狀態的電平,CPHA規定了數據是在SCK時鐘的上升沿被采樣還是下降沿被采樣。這四種模式的時序圖如下圖1-4所示:

模式0:CPOL= 0,CPHA=0。CLK串行時鐘線空閑是為低電平,數據在SCK時鐘的上升沿被采樣,數據在CLK時鐘的下降沿切換
模式1:CPOL= 0,CPHA=1。CLK串行時鐘線空閑是為低電平,數據在SCK時鐘的下降沿被采樣,數據在CLK時鐘的上升沿切換
模式2:CPOL= 1,CPHA=0。CLK串行時鐘線空閑是為高電平,數據在SCK時鐘的下降沿被采樣,數據在CLK時鐘的上升沿切換
模式3:CPOL= 1,CPHA=1。CLK串行時鐘線空閑是為高電平,數據在SCK時鐘的上升沿被采樣,數據在CLK時鐘的下降沿切換 其中比較常用的模式是模式0和模式3。為了更清晰的描述SPI總線的時序,下面展現了模式0下的SPI時序圖1-5:

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

圖2-1 spi軟件架構圖
2.2. 初始化及退出流程
2.2.1. 注冊spi控制器
注冊spi控制器到內核分為兩個階段:第一個階段,使用spi_alloc_master,分配一個spi_master的空間,具體流程如圖2-2所示:

第二階段,使用spi_register_master將第一階段分配的spi_master注冊到內核中,具體流程如2-3所示:

2.2.2. 注銷spi控制器
spi控制器注銷的流程如圖2-4所示:

2.3. 關鍵數據結構
2.3.1. spi_device
struct spi_device { struct device dev; /*spi控制器對應的device結構 struct spi_master *master; /*設備使用的master結構,掛在哪個主控制器下*/ 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; /*每個字長的比特數,默認是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_device代表一個外圍spi設備,由master controller driver注冊完成后掃描BSP中注冊設備產生的設備鏈表并向spi_bus注冊產生。在內核中,每個spi_device代表一個物理的spi設備。
2.3.2. spi_driver
struct spi_driver { const struct spi_device_id *id_table; /*支持的spi_device設備表*/ 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,即外設驅動
2.3.3. struct spi_master
struct spi_master { struct device dev; /*spi控制器對應的device結構*/ 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; /*片選數量*/ /* 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; /* 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 */ /* 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函數。*/ /* * 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; 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); /*清理回調函數*/ /* gpio chip select */ int *cs_gpios; };spi_master代表一個spi控制器。
2.3.4. struct spi_message 和spi_transfer
要完成和SPI設備的數據傳輸工作,我們還需要另外兩個數據結構:spi_message和spi_transfer。
spi_message包含了一個的spi_transfer結構序列,一旦控制器接收了一個spi_message,其中的spi_transfer應該按順序被發送,并且不能被其它spi_message打斷,所以我們認為spi_message就是一次SPI數據交換的原子操作。下面我們看看這兩個數據結構的定義:
struct spi_message :
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 actual_length; /*實際傳輸的長度*/ 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; void *state; };鏈表字段queue用于把該結構掛在代表控制器的spi_master結構的queue字段上,控制器上可以同時被加入多個spi_message進行排隊。另一個鏈表字段transfers則用于鏈接掛在本message下的spi_tranfer結構。complete回調函數則會在該message下的所有spi_transfer都被傳輸完成時被調用,以便通知協議驅動處理接收到的數據以及準備下一批需要發送的數據。我們再來看看spi_transfer結構: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; /*發送緩沖區*/ void *rx_buf; /*接收緩沖區*/ unsigned len; /*緩沖區長度,tx和rx的大?。ㄗ止潝担?。指它們各自的大小*/ dma_addr_t tx_dma; /*tx的dma地址*/ dma_addr_t rx_dma; /*rx的dma地址*/ unsigned cs_change:1; /*當前spi_transfer發送完成之后重新片選*/ u8 bits_per_word; /*每個字長的比特數,0代表使用spi_device中的默認值8*/ u16 delay_usecs; /*發送完成一個spi_transfer后的延時時間,此次傳輸結束和片選改變之間的延時,之后就會啟動另一個傳輸或者結束整個消息*/ u32 speed_hz; /*通信時鐘。如果是0,使用默認值*/ #ifdef CONFIG_SPI_LOMBO struct lombo_spi_operate_para *esop; #endif struct list_head transfer_list; /*用于鏈接到spi_message,用來連接的雙向鏈接節點*/ };首先,transfer_list鏈表字段用于把該transfer掛在一個spi_message結構中,tx_buf和rx_buf提供了非dma模式下的數據緩沖區地址,len則是需要傳輸數據的長度,tx_dma和rx_dma則給出了dma模式下的緩沖區地址。原則來講,spi_transfer才是傳輸的最小單位,之所以又引進了spi_message進行打包,我覺得原因是:有時候希望往spi設備的多個不連續的地址(或寄存器)一次性寫入,如果沒有spi_message進行把這樣的多個spi_transfer打包,因為通常真正的數據傳送工作是在另一個內核線程(工作隊列)中完成的,不打包的后果就是會造成更多的進程切換,效率降低,延遲增加,尤其對于多個不連續地址的小規模數據傳送而言就更為明顯。
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; /*平臺數據*/ 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; /*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. 數據傳輸流程
整體的數據傳輸流程大致上是這樣的:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
定義一個spi_message結構;
用spi_message_init函數初始化spi_message;
定義一個或數個spi_transfer結構,初始化并為數據準備緩沖區并賦值給spi_transfer相應的字段(tx_buf,rx_buf等);
通過spi_message_init函數把這些spi_transfer掛在spi_message結構下;
如果使用同步方式,調用spi_sync(),如果使用異步方式,調用spi_async();(我調試外設時,只使用過spi_sync
傳輸示意圖如圖2-5所示:

2.4.1. 數據準備
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. 數據傳輸
SPI數據傳輸可以有兩種方式:同步方式和異步方式。所謂同步方式是指數據傳輸的發起者必須等待本次傳輸的結束,期間不能做其它事情,用代碼來解釋就是,調用傳輸的函數后,直到數據傳輸完成,函數才會返回。而異步方式則正好相反,數據傳輸的發起者無需等待傳輸的結束,數據傳輸期間還可以做其它事情,用代碼來解釋就是,調用傳輸的函數后,函數會立刻返回而不用等待數據傳輸完成,我們只需設置一個回調函數,傳輸完成后,該回調函數會被調用以通知發起者數據傳送已經完成。同步方式簡單易用,很適合處理那些少量數據的單次傳輸。但是對于數據量大、次數多的傳輸來說,異步方式就顯得更加合適。對于SPI控制器來說,要支持異步方式必須要考慮以下兩種狀況:
對于同一個數據傳輸的發起者,既然異步方式無需等待數據傳輸完成即可返回,返回后,該發起者可以立刻又發起一個message,而這時上一個message還沒有處理完。
對于另外一個不同的發起者來說,也有可能同時發起一次message傳輸請求 首先分析spi_sync()接口的實現流程,如圖2-6:

其次分析spi_async_locked接口的實現流程,如圖2-7所示:
spi_queued_transfer接口的實現流程如圖3-8所示:

spi_pump_messages函數的處理流程如圖3-9所示:

圖中transfer_one_message是spi控制器驅動要實現的,主要功能是處理spi_message中的每個spi_transfer。
2.5. 關鍵函數解析
2.5.1. spi_alloc_master
原型:
struct spi_master *spi_alloc_master(struct device *dev, unsigned size)
功能:分配一個spi_master結構體指針。
參數: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控制器驅動到內核。
參數 master:spi_master指針
返回值 成功,返回0;否則返回錯誤碼
2.5.3. spi_unregister_master
原型:
void spi_unregister_master(struct spi_master *master)
功能 注銷spi控制器驅動。
參數 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驅動程序 其他 : 無 論壇 : 日志 : 初版V1.0 2019/9/2 左工創建 ***************************************************************/ #define ICM20608_CNT 1 #define ICM20608_NAME "icm20608" struct icm20608_dev { dev_t devid; /* 設備號 */ struct cdev cdev; /* cdev */ struct class *class; /* 類 */ struct device *device; /* 設備 */ struct device_node *nd; /* 設備節點 */ int major; /* 主設備號 */ void *private_data; /* 私有數據 */ 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讀取多個寄存器數據 * @param - dev: icm20608設備 * @param - reg: 要讀取的寄存器首地址 * @param - val: 讀取到的數據 * @param - len: 要讀取的數據長度 * @return : 操作結果 */ 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; } /* * @description : 向icm20608多個寄存器寫入數據 * @param - dev: icm20608設備 * @param - reg: 要寫入的寄存器首地址 * @param - val: 要寫入的數據緩沖區 * @param - len: 要寫入的數據長度 * @return : 操作結果 */ 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); /* 申請內存 */ gpio_set_value(dev->cs_gpio, 0); /* 片選拉低 */ /* 第1次,發送要讀取的寄存地址 */ txdata[0] = reg & ~0x80; /* 寫數據的時候寄存器地址bit8要清零 */ 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次,發送要寫入的數據 */ 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; } /* * @description : 讀取icm20608指定寄存器值,讀取一個寄存器 * @param - dev: icm20608設備 * @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設備 * @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的數據,讀取原始數據,包括三軸陀螺儀、 * : 三軸加速度計和內部溫度。 * @param - dev : ICM20608設備 * @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 : 打開設備 * @param - inode : 傳遞給驅動的inode * @param - filp : 設備文件,file結構體有個叫做privateate_data的成員變量 * 一般在open的時候將private_data似有向設備結構體。 * @return : 0 成功;其他 失敗 */ static int icm20608_open(struct inode *inode, struct file *filp) { filp->private_data = &icm20608dev; /* 設置私有數據 */ return 0; } /* * @description : 從設備讀取數據 * @param - filp : 要打開的設備文件(文件描述符) * @param - buf : 返回給用戶空間的數據緩沖區 * @param - cnt : 要讀取的數據長度 * @param - offt : 相對于文件首地址的偏移 * @return : 讀取的字節數,如果為負值,表示讀取失敗 */ 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 : 關閉/釋放設備 * @param - filp : 要關閉的設備文件(文件描述符) * @return : 0 成功;其他 失敗 */ static int icm20608_release(struct inode *inode, struct file *filp) { return 0; } /* icm20608操作函數 */ static const struct file_operations icm20608_ops = { .owner = THIS_MODULE, .open = icm20608_open, .read = icm20608_read, .release = icm20608_release, }; /* * ICM20608內部寄存器初始化函數 * @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); /* 輸出速率是內部采樣率 */ 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 */ } /* * @description : spi驅動的probe函數,當驅動與 * 設備匹配以后此函數就會執行 * @param - client : spi設備 * @param - id : spi設備ID * */ static int icm20608_probe(struct spi_device *spi) { int ret = 0; /* 1、構建設備號 */ 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、注冊設備 */ cdev_init(&icm20608dev.cdev, &icm20608_ops); cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT); /* 3、創建類 */ icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME); if (IS_ERR(icm20608dev.class)) { return PTR_ERR(icm20608dev.class); } /* 4、創建設備 */ icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME); if (IS_ERR(icm20608dev.device)) { return PTR_ERR(icm20608dev.device); } /* 獲取設備樹中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、 獲取設備樹中的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、設置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; /* 設置私有數據 */ /* 初始化ICM20608內部寄存器 */ icm20608_reginit(); return 0; } /* * @description : spi驅動的remove函數,移除spi驅動的時候此函數會執行 * @param - client : spi設備 * @return : 0,成功;其他負值,失敗 */ static int icm20608_remove(struct spi_device *spi) { /* 刪除設備 */ cdev_del(&icm20608dev.cdev); unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT); /* 注銷掉類和設備 */ device_destroy(icm20608dev.class, icm20608dev.devid); class_destroy(icm20608dev.class); return 0; } /* 傳統匹配方式ID列表 */ static const struct spi_device_id icm20608_id[] = { {"alientek,icm20608", 0}, {} }; /* 設備樹匹配列表 */ static const struct of_device_id icm20608_of_match[] = { { .compatible = "alientek,icm20608" }, { /* Sentinel */ } }; /* SPI驅動結構體 */ 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 : 驅動入口函數 * @param : 無 * @return : 無 */ static int __init icm20608_init(void) { return spi_register_driver(&icm20608_driver); } /* * @description : 驅動出口函數 * @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下如何進行SPI驅動問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。