公众号

欢迎扫码关注本人微信公众号:公众号上分享更多嵌入式知识和资料,分享个人学习嵌入式的心得体会。欢迎大家一起来玩呀。
在这里插入图片描述

linux使用设备树的SPI总线分析


linux下SPI的总线模型

linux下spi总线和platform总线类似,platform分为platform device和platform driver,spi总线分为spi_device和spi_driver。

在一个Soc芯片上有多个spi的控制器spi0、spi1等等,例如我们把spi0控制器称为一个spi_master,每个spi控制器下面可以挂载多个设备,比如有spi norflash device,spi lcd device等,每个设备的片选不一样比如spi norflash挂载在cs0,spi lcd挂载在cs1,spi norflash频率工作是16M,spi lcd工作频率是8M,这部分的资源就用spi_device来抽象描述。总线下挂载的spi norflash和spi lcd的驱动我们就用spi_driver来描述。

  • spi_master:确定芯片spi控制器。
  • spi_device:确定总线的一些属性,比如spi的mode,cs片选,频率等。
  • spi_driver:描述总线下挂载的设备的驱动,比如spi norflash的驱动和spi lcd的驱动等。

查看芯片平台spi_master dts文件定义

//描述一个spi_master即spi0控制器的芯片资源比如spi使用dma,gpio配置为spi模式,clock时钟等,
每个平台都不一样,这部分由芯片平台提供,我们只要知道这个是描述spi0控制器的即可,通过这个
设备数节点rockchip_spi_probe函数会被调用注册spi_master到linux spi的框架里面去,下面会再次
说明这个过程。
spi0: spi@ff1d0000 {   
		compatible = "rockchip,px30-spi", "rockchip,rk3066-spi";
		reg = <0x0 0xff1d0000 0x0 0x1000>;
		interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
		#address-cells = <1>;
		#size-cells = <0>;
		clocks = <&cru SCLK_SPI0>, <&cru PCLK_SPI0>;
		clock-names = "spiclk", "apb_pclk";
		dmas = <&dmac 12>, <&dmac 13>;
		#dma-cells = <2>;
		dma-names = "tx", "rx";
		pinctrl-names = "default", "high_speed";
		pinctrl-0 = <&spi0_clk &spi0_csn &spi0_miso &spi0_mosi>;
		pinctrl-1 = <&spi0_clk_hs &spi0_csn &spi0_miso_hs &spi0_mosi_hs>;
		status = "disabled"; //默认为disabled当下面添加spi device的时候会设置为okay
	};

查看spi device dts文件描述

&spi0 { //引用上面的spi0 master的设备节点,这个spi0控制器下面有两个device节点 norflash device和lcd device
	status = "okay";
	
	flash@0 { //这一段节点是描述一个spi device 比如这个是添加一个spi norflash device
		compatible = "m25p80"; //通过这个属性查找那个driver挂载在这个device上
		reg = <0>;  //片选 cs 0
		m25p,fast-read;
		spi-max-frequency = <16000000>;
	};
	
	spi_lcd@0{//这一段节点是描述一个spi device 比如这个是添加一个spi led device
	    compatible = "spi_lcd"; //通过这个属性查找那个driver挂载在这个device上
		reg = <1>;  //片选 cs 1
		spi-max-frequency = <8000000>;
	};
	
};

跟踪芯片平台注册spi_master和spi_device代码过程

//拿rk芯片为例子,芯片平台没添加一个spi0或者spi1,rockchip_spi_probe这个probe函数会被调用一次
static int rockchip_spi_probe(struct platform_device *pdev)
    devm_spi_register_master(&pdev->dev, master);//spi_register_master的一种封装形式
       spi_register_master(master);//注册一个spi控制器,即spi_master
           of_spi_register_master//通过设备树的形式注册spi_master
	          of_register_spi_devices(master); //添加spi spi_device节点spi led device
	              for_each_available_child_of_node(master->dev.of_node, nc) {
	                  //循环添加所有的spi_device,比如我们这里有两个norflash device和spi lcd device
		              spi = of_register_spi_device(master, nc)}

上面是rockchip平台的芯片添加spi控制器spi_master到linux spi框架的过程,rockchip_spi_probe这个函数是rockchip芯片这个独有,每个芯片平台都有一个类似的xxx_spi_probe(命名的方式不固定由芯片平台自己定义),在这个probe函数里面我们会调用devm_spi_register_master添加spi控制器到linux spi的框架里面。

norflash deiver如何使用spi进行读写

static const struct of_device_id m25p_of_table[] = {
	{ .compatible = "m25p80" },
};

static struct spi_driver m25p80_driver = {
	.driver = {
		.name	= "m25p80",//我们使用设备树进行匹配这个不用管  
		.of_match_table = m25p_of_table, //这个比较重要这个和设备树的device的compatible = "m25p80",进行匹配
	},
	.id_table	= m25p_ids,
	.probe	= m25p_probe,
	.remove	= m25p_remove,
};

module_spi_driver(m25p80_driver);

查看上面的代码,m25p80_driver注册到module_spi_driver,这个表示在系统启动的时候会把这个结构体添加到spi driver的链表当中,当有新的driver这时候会去查找是否有和它匹配的device,如果查找到匹配的device,m25p80_driver里面的probe函数就会被调用,这个和platform的总线驱动的思想是一样的,下面我们将看到m25p_probe这个函数。

static int m25p_probe(struct spi_device *spi)
{
    flash->spi = spi;  //保存spi_device *spi结构体指针,这个很重要,到时候就是用这个使用spi进行读写数据
	return mtd_device_parse_register(&nor->mtd, NULL, &ppdata, //注册一个mtd device我们不是很关心
			data ? data->parts : NULL,
			data ? data->nr_parts : 0);
}

static void m25p80_write(struct spi_nor *nor, loff_t to, size_t len,
			size_t *retlen, const u_char *buf)
{
    struct spi_transfer t[2] = {};
	struct spi_message m;
	
	spi_message_init(&m);  //初始化一个spi消息
	
	t[0].tx_buf = flash->command; //填充要发送的消息
	t[0].len = cmd_sz;
	spi_message_add_tail(&t[0], &m);//将要发送的消息添加到消息链表中

	spi_sync(spi, &m); //使用probe保存spi_device *spi结构体指针,将数据发送出去
}

查看上面norflash write(m25p80_write)函数我们可以看到调用这个系统API的SPI框架会使用这个注册到系统中的spi device属性和spi master将数据发送出去到外设上,spi核心框架会根据匹配的device的属性设置外设对应的寄存器,设置好时钟片选等,然后将数据发送数据出去,这个部分我们无需关心,我们主要关心的是如何使用这些系统提供的函数,设置寄存器就由芯片厂商去完成,因为他们比我们更熟悉如何配置寄存器。

Logo

更多推荐