首页 » 智能 » Linux下SPI驱动详解_数据_装备

Linux下SPI驱动详解_数据_装备

少女玫瑰心 2025-01-02 21:09:59 0

扫一扫用手机浏览

文章目录 [+]

图1-1 SPI总线模型

1.2. SPI总线时序

SPI接口在Master掌握下产生的从设备使能旗子暗记和时钟旗子暗记,两个双向移位寄存器按位传输进行数据交流,传输数据高位在前(MSB first),低位在后。
如下图所示,在CLK的低落沿上数据改变,上升沿一位数据被存入移位寄存器。

Linux下SPI驱动详解_数据_装备 Linux下SPI驱动详解_数据_装备 智能

图1-2 spi传输时序图

Linux下SPI驱动详解_数据_装备 Linux下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个部分:

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

structspi_device{structdevicedev;/spi掌握器对应的device构造structspi_mastermaster;/设备利用的master构造,挂在哪个主掌握器下/u32max_speed_hz;/通讯时钟最大频率/u8chip_select;/片选号,每个master支持多个spi_device/u8mode;#defineSPI_CPHA0x01/clockphase/#defineSPI_CPOL0x02/clockpolarity/#defineSPI_MODE_0(0|0)/(originalMicroWire)/#defineSPI_MODE_1(0|SPI_CPHA)#defineSPI_MODE_2(SPI_CPOL|0)#defineSPI_MODE_3(SPI_CPOL|SPI_CPHA)#defineSPI_CS_HIGH0x04/chipselectactivehigh?/#defineSPI_LSB_FIRST0x08/per-wordbits-on-wire/#defineSPI_3WIRE0x10/SI/SOsignalsshared/#defineSPI_LOOP0x20/loopbackmode/#defineSPI_NO_CS0x40/1dev/bus,nochipselect/#defineSPI_READY0x80/slavepullslowtopause/u8bits_per_word;/每个字长的比特数,默认是8/intirq;voidcontroller_state;/掌握器状态/voidcontroller_data;/掌握器数据/charmodalias[SPI_NAME_SIZE];/设备驱动的名字/intcs_gpio;/chipselectgpio//likelyneedmorehooksformoreprotocoloptionsaffectinghowthecontrollertalkstoeachchip,like:-memorypacking(12bitsamplesintolowbits,otherszeroed)-priority-dropchipselectaftereachword-chipselectdelays-.../};

spi_device代表一个外围spi设备,由master controller driver注册完成后扫描BSP中注册设备产生的设备链表并向spi_bus注册产生。
在内核中,每个spi_device代表一个物理的spi设备。

2.3.2. spi_driver

structspi_driver{conststructspi_device_idid_table;/支持的spi_device设备表/int(probe)(structspi_devicespi);int(remove)(structspi_devicespi);void(shutdown)(structspi_devicespi);int(suspend)(structspi_devicespi,pm_message_tmesg);int(resume)(structspi_devicespi);structdevice_driverdriver;};

spi_driver代表一个SPI protocol drivers,即外设驱动

2.3.3. struct spi_master

structspi_master{structdevicedev;/spi掌握器对应的device构造/structlist_headlist;/链表/otherthannegative(==assignonedynamically),bus_numisfullyboard-specific.usuallythatsimplifiestobeingSOC-specific.example:oneSOChasthreeSPIcontrollers,numbered0..2,andoneboard'sschematicsmightshowitusingSPI-2.softwarewouldnormallyusebus_num=2forthatcontroller./s16bus_num;/总线(或掌握器编号)//chipselectswillbeintegraltomanycontrollers;someothersmightuseboard-specificGPIOs./u16num_chipselect;/片选数量//someSPIcontrollersposealignmentrequirementsonDMAablebuffers;letprotocoldriversknowabouttheserequirements./u16dma_alignment;/spi_device.modeflagsunderstoodbythiscontrollerdriver/u16mode_bits;/master支持的设备模式//bitmaskofsupportedbits_per_wordfortransfers/u32bits_per_word_mask;/otherconstraintsrelevanttothisdriver/u16flags;/用于限定某些限定条件的标志位#defineSPI_MASTER_HALF_DUPLEXBIT(0)/can'tdofullduplex/#defineSPI_MASTER_NO_RXBIT(1)/can'tdobufferread/#defineSPI_MASTER_NO_TXBIT(2)/can'tdobufferwrite//lockandmutexforSPIbuslocking/spinlock_tbus_lock_spinlock;structmutexbus_lock_mutex;/flagindicatingthattheSPIbusislockedforexclusiveuse/boolbus_lock_flag;/Setupmodeandclock,etc(spidrivermaycallmanytimes).IMPORTANT:thismaybecalledwhentransferstoanotherdeviceareactive.DONOTUPDATESHAREDREGISTERSinwayswhichcouldbreakthosetransfers./int(setup)(structspi_devicespi);/根据spi设备更新硬件配置。
设置spi事情模式、时钟等//bidirectionalbulktransfers+Thetransfer()methodmaynotsleep;itsmainroleisjusttoaddthemessagetothequeue.+Fornowthere'snoremove-from-queueoperation,oranyotherrequestmanagement+Toagivenspi_device,messagequeueingispurefifo+Themaster'smainjobistoprocessitsmessagequeue,selectingachipthentransferringdata+Iftherearemultiplespi_devicechildren,thei/oqueuearbitrationalgorithmisunspecified(roundrobin,fifo,priority,reservations,preemption,etc)+Chipselectstaysactiveduringtheentiremessage(unlessmodifiedbyspi_transfer.cs_change!=0).+ThemessagetransfersuseclockandSPImodeparameterspreviouslyestablishedbysetup()forthisdevice/int(transfer)(structspi_devicespi,structspi_messagemesg);/添加到行列步队的方法,此函数不可就寝。
它的职责是安排发生的传送并且调用注册的回调函数complete()//calledonrelease()tofreememoryprovidedbyspi_master/void(cleanup)(structspi_devicespi);/cleanup函数会在spidev_release函数中被调用,spidev_release被登记为spidev的release函数。
//Thesehooksarefordriversthatwanttousethegenericmastertransferqueueingmechanism.Iftheseareused,thetransfer()functionabovemustNOTbespecifiedbythedriver.OvertimeweexpectSPIdriverstobephasedovertothisAPI./boolqueued;structkthread_workerkworker;/用于管理数据传输行列步队的事情行列步队线程/structtask_structkworker_task;structkthread_workpump_messages;/详细实现数据传输行列步队的事情行列步队/spinlock_tqueue_lock;structlist_headqueue;/该掌握器的行列步队,所有等待传输的行列步队挂在该链表下/structspi_messagecur_msg;/当前正在处理的行列步队/boolbusy;/忙状态/boolrunning;/正在跑/boolrt;int(prepare_transfer_hardware)(structspi_mastermaster);/回调函数,正式发起传输前会被调用,用于准备硬件资源/int(transfer_one_message)(structspi_mastermaster,structspi_messagemesg);/单个的原子传输回调函数,行列步队中每个都会回调一次该回调来完成传输事情/int(unprepare_transfer_hardware)(structspi_mastermaster);/清理回调函数//gpiochipselect/intcs_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 :

structspi_message{structlist_headtransfers;/spi_transfer链表行列步队,这次的传输段行列步队,一个可以包含多个传输段。
/structspi_devicespi;/传输的目的设备/unsignedis_dma_mapped:1;/如果为真,这次调用供应dma和cpu虚拟地址。
//REVISIT:wemightwantaflagaffectingthebehaviorofthelasttransfer...allowingthingslike"read16bitlengthL"immediatelyfollowedby"readLbytes".Basicallyimposingaspecificmessageschedulingalgorithm.Somecontrollerdrivers(message-at-a-timequeueprocessing)couldprovidethatastheirdefaultschedulingalgorithm.Butothers(withmulti-messagepipelines)couldneedaflagtotellthemaboutsuchspecialcases.//completionisreportedthroughacallback/void(complete)(voidcontext);/异步调用完成后的回调函数/voidcontext;/回调函数的参数/unsignedactual_length;/实际传输的长度/intstatus;/该的发送结果,成功被置0,否则是一个负的缺点码。
//foroptionalusebywhateverdrivercurrentlyownsthespi_message...betweencallstospi_asyncandthenlatercomplete(),that'sthespi_mastercontrollerdriver./structlist_headqueue;voidstate;};

链表字段queue用于把该构造挂在代表掌握器的spi_master构造的queue字段上,掌握器上可以同时被加入多个spi_message进行排队。
另一个链表字段transfers则用于链接挂在本message下的spi_tranfer构造。
complete回调函数则会在该message下的所有spi_transfer都被传输完成时被调用,以便关照协议驱动处理吸收到的数据以及准备下一批须要发送的数据。
我们再来看看spi_transfer构造: spi_transfer

structspi_transfer{/it'sokiftx_buf==rx_buf(right?)forMicroWire,onebuffermustbenullbuffersmustworkwithdma_map_single()calls,unlessspi_message.is_dma_mappedreportsapre-existingmapping/constvoidtx_buf;/发送缓冲区/voidrx_buf;/吸收缓冲区/unsignedlen;/缓冲区长度,tx和rx的大小(字节数)。
指它们各自的大小/dma_addr_ttx_dma;/tx的dma地址/dma_addr_trx_dma;/rx的dma地址/unsignedcs_change:1;/当前spi_transfer发送完成之后重新片选/u8bits_per_word;/每个字长的比特数,0代表利用spi_device中的默认值8/u16delay_usecs;/发送完成一个spi_transfer后的延时时间,这次传输结束和片选改变之间的延时,之后就会启动另一个传输或者结束全体/u32speed_hz;/通信时钟。
如果是0,利用默认值/#ifdefCONFIG_SPI_LOMBOstructlombo_spi_operate_paraesop;#endifstructlist_headtransfer_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

structspi_board_info{/thedevicenameandmodulenamearecoupled,likeplatform_bus;"modalias"isnormallythedrivername.platform_datagoestospi_device.dev.platform_data,controller_datagoestospi_device.controller_data,irqiscopiedtoo/charmodalias[SPI_NAME_SIZE];/名字/constvoidplatform_data;/平台数据/voidcontroller_data;/掌握器数据/intirq;/slowersignalingonnoisyorlowvoltageboards/u32max_speed_hz;/最大速率//bus_numisboardspecificandmatchesthebus_numofsomespi_masterthatwillprobablyberegisteredlater.chip_selectreflectshowthischipiswiredtothatmaster;it'slessthannum_chipselect./u16bus_num;/spi总线编号/u16chip_select;/片选//modebecomesspi_device.mode,andisessentialforchipswherethedefaultofSPI_CS_HIGH=0iswrong./u8mode;/模式//...mayneedadditionalspi_devicechipconfigdatahere.avoidstuffprotocoldriverscanset;butincludestuffneededtobehavewithoutbeingboundtoadriver:-quirkslikeclockratematteringwhennotselected/};2.4. 数据传输流程

整体的数据传输流程大致上是这样的:

定义一个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

staticinlinevoidspi_message_init(structspi_messagem){memset(m,0,sizeofm);INIT_LIST_HEAD(&m->transfers);}

初始化spi_message:清空message,初始化transfers链表头。

2.4.1.2. spi_message_add_tail

staticinlinevoidspi_message_add_tail(structspi_transfert,structspi_messagem){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

原型:

structspi_masterspi_alloc_master(structdevicedev,unsignedsize)

功能: 分配一个spi_master构造体指针。

参数: dev:spi掌握器device指针 size :分配的driver-private data大小

返回值 : 成功,返回spi_master指针;否则返回NULL

2.5.2. spi_register_master

原型:

intspi_register_master(structspi_mastermaster)

功能 注册spi掌握器驱动到内核。

参数 master:spi_master指针

返回值 成功,返回0;否则返回缺点码

2.5.3. spi_unregister_master

原型:

voidspi_unregister_master(structspi_mastermaster)

功能 注销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©ALIENTEKCo.,Ltd.1998-2029.Allrightsreserved.文件名:icm20608.c作者:左工版本:V1.0描述:ICM20608SPI驱动程序其他:无论坛:日志:初版V1.02019/9/2左工创建/#defineICM20608_CNT1#defineICM20608_NAME"icm20608"structicm20608_dev{dev_tdevid;/设备号/structcdevcdev;/cdev/structclassclass;/类/structdevicedevice;/设备/structdevice_nodend;/设备节点/intmajor;/主设备号/voidprivate_data;/私有数据/intcs_gpio;/片选所利用的GPIO编号/signedintgyro_x_adc;/陀螺仪X轴原始值/signedintgyro_y_adc;/陀螺仪Y轴原始值/signedintgyro_z_adc;/陀螺仪Z轴原始值/signedintaccel_x_adc;/加速度计X轴原始值/signedintaccel_y_adc;/加速度计Y轴原始值/signedintaccel_z_adc;/加速度计Z轴原始值/signedinttemp_adc;/温度原始值/};staticstructicm20608_devicm20608dev;/@description:从icm20608读取多个寄存器数据@param-dev:icm20608设备@param-reg:要读取的寄存器首地址@param-val:读取到的数据@param-len:要读取的数据长度@return:操作结果/staticinticm20608_read_regs(structicm20608_devdev,u8reg,voidbuf,intlen){intret;unsignedchartxdata[len];structspi_messagem;structspi_transfert;structspi_devicespi=(structspi_device)dev->private_data;gpio_set_value(dev->cs_gpio,0);/片选拉低,选中ICM20608/t=kzalloc(sizeof(structspi_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/returnret;}/@description:向icm20608多个寄存器写入数据@param-dev:icm20608设备@param-reg:要写入的寄存器首地址@param-val:要写入的数据缓冲区@param-len:要写入的数据长度@return:操作结果/statics32icm20608_write_regs(structicm20608_devdev,u8reg,u8buf,u8len){intret;unsignedchartxdata[len];structspi_messagem;structspi_transfert;structspi_devicespi=(structspi_device)dev->private_data;t=kzalloc(sizeof(structspi_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/returnret;}/@description:读取icm20608指定寄存器值,读取一个寄存器@param-dev:icm20608设备@param-reg:要读取的寄存器@return:读取到的寄存器值/staticunsignedcharicm20608_read_onereg(structicm20608_devdev,u8reg){u8data=0;icm20608_read_regs(dev,reg,&data,1);returndata;}/@description:向icm20608指定寄存器写入指定的值,写一个寄存器@param-dev:icm20608设备@param-reg:要写的寄存器@param-data:要写入的值@return:无/staticvoidicm20608_write_onereg(structicm20608_devdev,u8reg,u8value){u8buf=value;icm20608_write_regs(dev,reg,&buf,1);}/@description:读取ICM20608的数据,读取原始数据,包括三轴陀螺仪、:三轴加速度计和内部温度。
@param-dev:ICM20608设备@return:无。
/voidicm20608_readdata(structicm20608_devdev){unsignedchardata[14];icm20608_read_regs(dev,ICM20_ACCEL_XOUT_H,data,14);dev->accel_x_adc=(signedshort)((data[0]<<8)|data[1]);dev->accel_y_adc=(signedshort)((data[2]<<8)|data[3]);dev->accel_z_adc=(signedshort)((data[4]<<8)|data[5]);dev->temp_adc=(signedshort)((data[6]<<8)|data[7]);dev->gyro_x_adc=(signedshort)((data[8]<<8)|data[9]);dev->gyro_y_adc=(signedshort)((data[10]<<8)|data[11]);dev->gyro_z_adc=(signedshort)((data[12]<<8)|data[13]);}/@description:打开设备@param-inode:通报给驱动的inode@param-filp:设备文件,file构造体有个叫做privateate_data的成员变量一样平常在open的时候将private_data似有向设备构造体。
@return:0成功;其他失落败/staticinticm20608_open(structinodeinode,structfilefilp){filp->private_data=&icm20608dev;/设置私有数据/return0;}/@description:从设备读取数据@param-filp:要打开的设备文件(文件描述符)@param-buf:返回给用户空间的数据缓冲区@param-cnt:要读取的数据长度@param-offt:相对付文件首地址的偏移@return:读取的字节数,如果为负值,表示读取失落败/staticssize_ticm20608_read(structfilefilp,char__userbuf,size_tcnt,loff_toff){signedintdata[7];longerr=0;structicm20608_devdev=(structicm20608_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));return0;}/@description:关闭/开释设备@param-filp:要关闭的设备文件(文件描述符)@return:0成功;其他失落败/staticinticm20608_release(structinodeinode,structfilefilp){return0;}/icm20608操作函数/staticconststructfile_operationsicm20608_ops={.owner=THIS_MODULE,.open=icm20608_open,.read=icm20608_read,.release=icm20608_release,};/ICM20608内部寄存器初始化函数@param:无@return:无/voidicm20608_reginit(void){u8value=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("ICM20608ID=%#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/staticinticm20608_probe(structspi_devicespi){intret=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)){returnPTR_ERR(icm20608dev.class);}/4、创建设备/icm20608dev.device=device_create(icm20608dev.class,NULL,icm20608dev.devid,NULL,ICM20608_NAME);if(IS_ERR(icm20608dev.device)){returnPTR_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("ecspi3nodenotfind!\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'tgetcs-gpio");return-EINVAL;}/3、设置GPIO1_IO20为输出,并且输出高电平/ret=gpio_direction_output(icm20608dev.cs_gpio,1);if(ret<0){printk("can'tsetgpio!\r\n");}/初始化spi_device/spi->mode=SPI_MODE_0;/MODE0,CPOL=0,CPHA=0/spi_setup(spi);icm20608dev.private_data=spi;/设置私有数据//初始化ICM20608内部寄存器/icm20608_reginit();return0;}/@description:spi驱动的remove函数,移除spi驱动的时候此函数会实行@param-client:spi设备@return:0,成功;其他负值,失落败/staticinticm20608_remove(structspi_devicespi){/删除设备/cdev_del(&icm20608dev.cdev);unregister_chrdev_region(icm20608dev.devid,ICM20608_CNT);/注销掉类和设备/device_destroy(icm20608dev.class,icm20608dev.devid);class_destroy(icm20608dev.class);return0;}/传统匹配办法ID列表/staticconststructspi_device_idicm20608_id[]={{"alientek,icm20608",0},{}};/设备树匹配列表/staticconststructof_device_idicm20608_of_match[]={{.compatible="alientek,icm20608"},{/Sentinel/}};/SPI驱动构造体/staticstructspi_drivericm20608_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:无/staticint__initicm20608_init(void){returnspi_register_driver(&icm20608_driver);}/@description:驱动出口函数@param:无@return:无/staticvoid__exiticm20608_exit(void){spi_unregister_driver(&icm20608_driver);}module_init(icm20608_init);module_exit(icm20608_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR(yikoulinux");

标签:

相关文章

全球语言多样性,探索世界语言的瑰宝

语言是文化的载体,是人类文明的重要组成部分。全球约有7000种语言,这些语言犹如世界各地的瑰宝,展现出丰富多彩的语言文化。本文将按...

智能 2025-01-04 阅读0 评论0