• S5PV210之SPI和linux 内核3.0.8之SPI解析


     SPI(serial peripheral interface)串行外围接口,是主控制器与外设进行串口通信的接口。主要包括四条控制线,分别是SCLK(串行时钟)、MOSI(主出从入)、MISO(主入从出)、SS(芯片选择使能信号、低电平有效)。

    先说说S5PV210的SPI的特点

       1.全双工工作

        2.发送/接收的移位寄存器可以是8位/16位/32位

       3.主从模式

       4.发送和接收的最大频率可达50MHz

       5.支持摩托罗拉SPI协议和美国半导体总线协议

      6.两路SPI信号

    支持四种工作模式:


    当CPHA(同步时钟相位)为0时,为格式A,当CPHA为1时,为格式B

    当CPHA为0时,即格式A,串行同步时钟在第一个跳变(上升沿或下降沿)/(前沿)读取数据

    当CPHA为1时,即格式B时,串行同步时钟在第二个跳变沿(上升沿或下降沿)/(后沿)读取数据

    当CPOL(同步时钟极性)为1时,SPICLK空闲时处于高电平,CPOL为0时,SPICLK空闲时处于低电平


    至于两路SPI的控制寄存器,接收数据寄存器、发送数据寄存器之类的,就不做多介绍了,因为要根据具体情况而进行设定。详情参看S5PV20_UM手册。


    接下来使用source insight 查看SPI的原理

       因为linux设备驱动框架采用分层和分离的思想,像linux中SPI、IIC、USB之类的子系统都采用了分离的设计思想,即主机驱动与外设驱动分离。

      以下这张图是宋宝华老师写的设备驱动开发详解里面的:描述了主机驱动和外设驱动的关系,主机控制器驱动不用关心外设,同样的外设驱动也不用关心主机,两者都是通过核心层进行信息的交互。


    对于LINUX 3.0.8中,

    SPI总线的层次关系,这张图是嵌入式学院的刘洪涛老师讲的,我觉得讲的挺好的,就贴到着了

           对于上面SPI的层次图,解释一下,我们知道SPI总线分为主从设备,而在linux中SPI的主设备(主机控制驱动)采用platform_device在BSP(板级支持包)中存储于主机硬件相关的信息,在platform_driver中存储操作,用platform_bus_type进行连接platform_device和platform_driver;而从设备(外设驱动)采用spi_device(准确的说是spi_board_info结构体)存储外设硬件相关信息,用spi_driver存储操作,用spi_bus_type进行连接spi_device与spi_driver。

    先说主机控制器这部分

    在内核的 include/linux/spi/spi.h 中,定义了主机控制器比较重要的几个机构体:分别是spi_master、spi_message、spi_transfer

    spi_master结构体

    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;
    
    	/* 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;
    
    	/* 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);
    
    	/* 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);
    
    	/* called on release() to free memory provided by spi_master */
    	void			(*cleanup)(struct spi_device *spi);
    };
    

    对于spi_master结构体,个人觉得比较重要的就是

    1.dev成员变量,代表一个主机控制器设备

    2.bus_num成员变量,应该说是总线编号,用于连接与此主控制相关的从设备 在drivers/spi/Spi_s3c64xx.c(这个文件就是一个spi主控制器的实现)中的s3c64xx_spi_probe函数     中,追踪源码(s3c64xx_spi_probe-->spi_register_master-->spi_match_master_to_boardinfo)会发现这么一句话

    if (master->bus_num != bi->bus_num)
      return;

    即将主控制器的bus_num与从设备的bus_num进行匹配,不匹配则返回,如果匹配则调用spi_new_device函数,创建于此主控制器相关的从设备

    3.(*transfer)(struct spi_device *spi,struct spi_message *mesg); ,transfer函数指针,这个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;
    
    	dma_addr_t	tx_dma;
    	dma_addr_t	rx_dma;
    
    	unsigned	cs_change:1;
    	u8		bits_per_word;
    	u16		delay_usecs;
    	u32		speed_hz;
    
    	struct list_head transfer_list;
    };
    

    spi_transfer相当于主从设备发送消息时的一个数据包,

    重要的字段分别是tx_buf(发送缓存)、rx_buf(接受缓存)、len(长度)


    spi_message结构体

    struct spi_message {
    	struct list_head	transfers;
    
    	struct spi_device	*spi;
    
    	unsigned		is_dma_mapped:1;
    
    	/* 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;
    
    	/* 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;
    };
    

    spi_message相当于主从设备信息发送时的一帧数据,包含多个数据包,使用transfers字段将多个spi_transfer进行连接

    同样是在include/linux/spi/spi.h中,再来看看外设驱动这边,SPI的外设设备驱动的实现和platform设备驱动的实现很像,采用的是通过总线连接外设与驱动,所以比较重要的结构体有:

    spi_device、spi_board_info、spi_bus_type、spi_driver

    spi_device结构体

    struct spi_device {
    	struct device		dev;
    	struct spi_master	*master;
    	u32			max_speed_hz;
    	u8			chip_select;
    	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;
    	int			irq;
    	void			*controller_state;
    	void			*controller_data;
    	char			modalias[SPI_NAME_SIZE];
    
    	/*
    	 * 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用来描述一个从设备,比较重要的字段有dev,

    master(从设备隶属于哪一个主设备)、

    modalias(设备的名称,在spi_bus_type的spi_match_device函数中用于和spi_driver的name字段进行匹配的,

    在drivers/spi/Spi.c 中的 spi_match_device函数中源码

    return strcmp(spi->modalias, drv->name) == 0; )


    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;
    	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
    	 */
    };
    

    实际上spi_device的很多板信息都存储在spi_board_info结构体中,spi_board_info结构体存储了片选信号(chip_select),主机总线编号(即bus_num)、spi传输的模式(mode)

    等等

           在linux启动过程中,在init_machine函数中,通过spi_register_board_info函数进行BSP信息的注册,当注册从设备的板信息时,会调用spi_match_master_to_boardinfo(master, &bi->board_info);函数进行主控制器与从设备的匹配

    spi_bus_type结构体

    struct bus_type spi_bus_type = {
    	.name		= "spi",
    	.dev_attrs	= spi_dev_attrs,
    	.match		= spi_match_device,
    	.uevent		= spi_uevent,
    	.pm		= &spi_pm,
    };

    spi_bus_type用于连接spi_device和spi_driver,在spi_match_device函数中,

    static int spi_match_device(struct device *dev, struct device_driver *drv)
    {
    	const struct spi_device	*spi = to_spi_device(dev);
    	const struct spi_driver	*sdrv = to_spi_driver(drv);
    
    	/* Attempt an OF style match */
    	if (of_driver_match_device(dev, drv))
    		return 1;
    
    	if (sdrv->id_table)
    		return !!spi_match_id(sdrv->id_table, spi);
    
    	return strcmp(spi->modalias, drv->name) == 0;
    }


    先看of_driver_match_device函数,追踪源码会发现,它会先比较drv的of_match_table字段和dev的of_node字段,匹配两个字段的name,type,compatible三个字段是否相同,

    接着是spi_match_id函数,它会遍历sdrv的id_table(即spi_driver支持的设备列表)结构体中的name与spi->modalias,比较是否匹配,匹配则返回id结构体

    while (id->name[0]) {
    		if (!strcmp(sdev->modalias, id->name))
    			return id;
    		id++;
    	}


    最后才是strcmp(spi->modalias, drv->name) == 0,比较设备名称spi->modalias与驱动名称drv->name字段匹配


    spi_driver结构体

    struct spi_driver {
    	const struct spi_device_id *id_table;
    	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实例,进一步解析SPI原理,实现过程

    需要用到以下文件

    drivers/spi/spi_s3c64xx.c        主机控制器platform_driver

    arch/arm/mach-s5pv210/dev-spi.c   主机控制器platform_device

    drivers/spi/spi.c   核心层API

    arch/sh/boards/board-sh7757lcr.c     外设spi_device(即spi_board_info)

    先从drivers/spi/spi_s3c64xx.c中的

    1.模块初始化函数看起

    static int __init s3c64xx_spi_init(void)
    {
    	return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
    }

    查看platform_driver_probe,有两行代码需要注意

    drv->probe = probe;

    retval = code = platform_driver_register(drv);

    其实上面两行代码是将probe函数赋值给驱动,并且注册驱动。如果熟悉platform机制的朋友,就会很了解。其实platform_driver_probe只是对platform_driver_register的一个封装而已。

    查看platform_driver_register,

    int platform_driver_register(struct platform_driver *drv)
    {
    	drv->driver.bus = &platform_bus_type;
    	if (drv->probe)
    		drv->driver.probe = platform_drv_probe;
    	if (drv->remove)
    		drv->driver.remove = platform_drv_remove;
    	if (drv->shutdown)
    		drv->driver.shutdown = platform_drv_shutdown;
    
    	return driver_register(&drv->driver);
    }

         上面的代码

        drv->driver.bus = &platform_bus_type;  给platform_driver里面的driver字段的bus字段赋初值,将之设为platform_bus_type

       三个if语句,用于将platform_driver的操作赋值给driver

    查看driver_register,有一句话,将驱动添加到总线上,,即将驱动挂载为platform_bus_type

    ret = bus_add_driver(drv); 

    查看bus_add_driver,驱动捆绑函数

    error = driver_attach(drv);

    查看driver_attach,此时遍历设备链表,查找与驱动匹配的设备

    return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

    查看bus_for_each_dev,下面的while循环,即调用__driver_attach

    while ((dev = next_device(&i)) && !error)
      error = fn(dev, data);  

    查看__driver_attach,判断,如果设备的驱动为NULL的话,即调用driver_probe_device
    if (!dev->driver)
      driver_probe_device(drv, dev);

    查看driver_probe_device,在relly_probe里面即是最终的匹配的驱动和设备的连接

    ret = really_probe(dev, drv);

    查看really_probe,下面有三段,分别是将驱动绑定在设备上、调用驱动的probe函数、将设备绑定在驱动上

    dev->driver = drv;
    
    if (dev->bus->probe) {
      ret = dev->bus->probe(dev);
      if (ret)
       goto probe_failed;
     } else if (drv->probe) {
      ret = drv->probe(dev);
      if (ret)
       goto probe_failed;
     }
    
     driver_bound(dev);
    
    


    到此为止,即完成了驱动与设备的查找与绑定。

    在上面really_probe函数里面的第二段 ret = drv->probe(dev); 调用驱动的probe函数,回想一下,即在platform_driver_register函数里面的

    if (drv->probe)  drv->driver.probe = platform_drv_probe;  //查看platform_drv_probe源码会发现,是将platform_driver的probe函数赋值给driver的probe函数

    所以调用驱动的probe函数即是调用模块初始化的s3c64xx_spi_probe函数

    2.查看s3c64xx_spi_probe函数

    先是

    dmatx_res = platform_get_resource(pdev, IORESOURCE_DMA, 0);
    	if (dmatx_res == NULL) {
    		dev_err(&pdev->dev, "Unable to get SPI-Tx dma resource
    ");
    		return -ENXIO;
    	}
    
    	dmarx_res = platform_get_resource(pdev, IORESOURCE_DMA, 1);
    	if (dmarx_res == NULL) {
    		dev_err(&pdev->dev, "Unable to get SPI-Rx dma resource
    ");
    		return -ENXIO;
    	}
    
    	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (mem_res == NULL) {
    		dev_err(&pdev->dev, "Unable to get SPI MEM resource
    ");
    		return -ENXIO;
    	}
    


    获取到在arch/arm/mach-s3c64xx/dev-spi.c定义的资源文件

    接着是

    master = spi_alloc_master(&pdev->dev,sizeof(struct s3c64xx_spi_driver_data)); 

    创建一个spi_master返回给master指针

    接着是

    spi_register_master(master);

    注册一个master  ,

    查看spi_register_master源码中有一行:

    list_for_each_entry(bi, &board_list, list) spi_match_master_to_boardinfo(master, &bi->board_info); //遍历spi_board_info链表,查找与master匹配的spi_board_spi


     

    查看spi_match_master_to_boardinfo函数

    if (master->bus_num != bi->bus_num)
      return;
    
     dev = spi_new_device(master, bi);
    
    

    判断主从设备的总线编号是否匹配,匹配的话,则调用spi_new_device函数

    查看spi_new_device函数,创建spi_device

    proxy = spi_alloc_device(master); 
    查看spi_alloc_device

    struct spi_device *spi_alloc_device(struct spi_master *master)
    {
    	struct spi_device	*spi;
    	struct device		*dev = master->dev.parent;
    
    	if (!spi_master_get(master))
    		return NULL;
    
    	spi = kzalloc(sizeof *spi, GFP_KERNEL);
    	if (!spi) {
    		dev_err(dev, "cannot alloc spi_device
    ");
    		spi_master_put(master);
    		return NULL;
    	}
    
    	spi->master = master;
    	spi->dev.parent = dev;
    	spi->dev.bus = &spi_bus_type;
    	spi->dev.release = spidev_release;
    	device_initialize(&spi->dev);
    	return spi;
    }
    创建一个spi_device,并返回,  spi->master = master; 即将spi_device与之主master连接在一起


     

    上面第一步,第二步分别完成了主机设备与驱动的绑定、创建spi_master、若有匹配的spi_board_info,则创建与之master匹配的spi_device

    3.spi_device与spi_driver通过spi_bus_type进行绑定的过程,与第一步的很类似,就不叙述了,详情参看源码driversspiSpi.c文件

    4.注册spi_board_info信息时,与spi_master进行匹配

    先看driversspiSpi.c文件中的spi_register_board_info函数

    list_for_each_entry(master, &spi_master_list, list)
    			spi_match_master_to_boardinfo(master, &bi->board_info);

    遍历master列表,查找与之匹配的spi_board_info

    查看spi_match_master_to_boardinfo

    static void spi_match_master_to_boardinfo(struct spi_master *master,
    				struct spi_board_info *bi)
    {
    	struct spi_device *dev;
    
    	if (master->bus_num != bi->bus_num)
    		return;
    
    	dev = spi_new_device(master, bi);
    	if (!dev)
    		dev_err(master->dev.parent, "can't create new device for %s
    ",
    			bi->modalias);
    }


    接下来的过程与第二步又很像了,也不叙述了。

    至此,已对SPI主机控制器的驱动与设备如何连接,SPI从设备的驱动和设备如何连接,SPI主控制器设备如何与从设备进行连接,进行了分析,下一步应该是SPI的主从设备间相互通信了,以及实例的介绍了。

  • 相关阅读:
    关于事务
    jquery弹出框
    ??(怕忘记 特此记录)
    .net事务
    揭开iphone4 4S 5 之间的内幕!这次你们该相信了吧!
    net得到当前时间
    aspnet ajax2.0下载安装包 msi
    jquery css 逐渐增加div的大小
    DataTable转换为Json对象
    安装EntityFramework
  • 原文地址:https://www.cnblogs.com/liangxinzhi/p/4275618.html
Copyright © 2020-2023  润新知