使能MT7620的第二个SPI接口(cs1)——兼谈pinmux与pinctrl驱动

前言

根据MT7620的数据手册,该芯片支持两个独立的spi接口,由于驱动不完善等种种原因,一直没能顺利的使用第二个spi接口.近日对mt7620的spi好好研究了一下,终于使能了第二个spi接口,将过程记录成文.

 

实现过程

spi驱动的诡异之处

mt7620的spi驱动位于:drivers/spi/spi-rt2880.c,里面有关spi cs1的代码非常诡异,因为7620与5350使用的是同一个spi控制芯片,而该驱动却区别对待,7620只有一个spi,5350有两个.

   1: static struct rt2880_spi_ops spi_ops[] = {
   2:     {
   3:         .init_hw = rt2880_spi_reset,
   4:         .num_cs = 1,
   5:     }, {
   6:         .init_hw = rt5350_spi_reset,
   7:         .num_cs = 2,
   8:     }, 
   9: };
  10:  
  11: static const struct of_device_id rt2880_spi_match[] = {
  12:     { .compatible = "ralink,rt2880-spi", .data = &spi_ops[0]},
  13:     { .compatible = "ralink,rt5350-spi", .data = &spi_ops[1]},
  14:     {},
  15: };

因此,第一个修改便是,将dts文件中对应的compatible修改为5350的版本.重新编译之后,驱动成功加载,然而,问题还远没有解决.

 

cs1没有输出信号

在第二个spi接口上连接一个spi flash芯片作为测试,始终无法probe到芯片.觉得很奇怪,于是用示波器来观察cs1上的信号,居然没有任何信号输出!仔细阅读7620的编程手册,发现了一个关键的内容:

image

spi_cs1在系统上电默认状态下是用作reference clock的!很显然,该引脚必须设置为normal spi mode.那么问题来了:如何设置这个引脚的功能呢?答案就在pinmux驱动中.

 

pinmux寻踪

很多嵌入式设备都有管脚复用功能,7620也不例外.pinmux数据在arch/mips/ralink/mt7620.c中定义:

   1: static struct rt2880_pmx_func i2c_grp[] =  { FUNC("i2c", 0, 1, 2) };
   2: static struct rt2880_pmx_func spi_grp[] = { FUNC("spi", 0, 3, 4) };
   3: static struct rt2880_pmx_func uartlite_grp[] = { FUNC("uartlite", 0, 15, 2) };
   4: static struct rt2880_pmx_func mdio_grp[] = { FUNC("mdio", 0, 22, 2) };
   5: static struct rt2880_pmx_func rgmii1_grp[] = { FUNC("rgmii1", 0, 24, 12) };
   6: static struct rt2880_pmx_func refclk_grp[] = { FUNC("spi refclk", 0, 37, 3) };
   7: static struct rt2880_pmx_func ephy_grp[] = { FUNC("ephy", 0, 40, 5) };
   8: static struct rt2880_pmx_func rgmii2_grp[] = { FUNC("rgmii2", 0, 60, 12) };
   9: static struct rt2880_pmx_func wled_grp[] = { FUNC("wled", 0, 72, 1) };
  10: static struct rt2880_pmx_func pa_grp[] = { FUNC("pa", 0, 18, 4) };
  11:  
  12: static struct rt2880_pmx_group mt7620a_pinmux_data[] = {
  13:     GRP("i2c", i2c_grp, 1, MT7620_GPIO_MODE_I2C),
  14:     GRP("uartf", uartf_grp, MT7620_GPIO_MODE_UART0_MASK,
  15:         MT7620_GPIO_MODE_UART0_SHIFT),
  16:     GRP("spi", spi_grp, 1, MT7620_GPIO_MODE_SPI),
  17:     GRP("uartlite", uartlite_grp, 1, MT7620_GPIO_MODE_UART1),
  18:     GRP_G("wdt", wdt_grp, MT7620_GPIO_MODE_WDT_MASK,
  19:         MT7620_GPIO_MODE_WDT_GPIO, MT7620_GPIO_MODE_WDT_SHIFT),
  20:     GRP("mdio", mdio_grp, 1, MT7620_GPIO_MODE_MDIO),
  21:     GRP("rgmii1", rgmii1_grp, 1, MT7620_GPIO_MODE_RGMII1),
  22:     GRP("spi refclk", refclk_grp, 1, MT7620_GPIO_MODE_SPI_REF_CLK),
  23:     GRP_G("pcie", pcie_rst_grp, MT7620_GPIO_MODE_PCIE_MASK,
  24:         MT7620_GPIO_MODE_PCIE_GPIO, MT7620_GPIO_MODE_PCIE_SHIFT),
  25:     GRP_G("nd_sd", nd_sd_grp, MT7620_GPIO_MODE_ND_SD_MASK,
  26:         MT7620_GPIO_MODE_ND_SD_GPIO, MT7620_GPIO_MODE_ND_SD_SHIFT),
  27:     GRP("rgmii2", rgmii2_grp, 1, MT7620_GPIO_MODE_RGMII2),
  28:     GRP("wled", wled_grp, 1, MT7620_GPIO_MODE_WLED),
  29:     GRP("ephy", ephy_grp, 1, MT7620_GPIO_MODE_EPHY),
  30:     GRP("pa", pa_grp, 1, MT7620_GPIO_MODE_PA),
  31:     { 0 }
  32: };

如何使能这些复用功能,则由dts与pinmux驱动协同完成,为了能够仔细分析pinmux实现机理,在drivers/pinctrl/pinctrl-rt2880.c的关键函数rt2880_pmx_group_enable中加入诊断代码:

   1: static int rt2880_pmx_group_enable(struct pinctrl_dev *pctrldev,
   2:                 unsigned func,
   3:                 unsigned group)
   4: {
   5:     struct rt2880_priv *p = pinctrl_dev_get_drvdata(pctrldev);
   6:         u32 mode = 0;
   7:     int i;
   8:  
   9:     /* dont allow double use */
  10:     if (p->groups[group].enabled) {
  11:         dev_err(p->dev, "%s is already enabled\n", p->groups[group].name);
  12:         return -EBUSY;
  13:     }
  14:  
  15:     p->groups[group].enabled = 1;
  16:     p->func[func]->enabled = 1;
  17:  
  18:     mode = rt_sysc_r32(SYSC_REG_GPIO_MODE);
  19:     mode &= ~(p->groups[group].mask << p->groups[group].shift);
  20:  
  21:     /* mark the pins as gpio */
  22:     for (i = 0; i < p->groups[group].func[0].pin_count; i++)
  23:         p->gpio[p->groups[group].func[0].pins[i]] = 1;
  24:  
  25:     /* function 0 is gpio and needs special handling */
  26:     if (func == 0) {
  27:         mode |= p->groups[group].gpio << p->groups[group].shift;
  28:     } else {
  29:         for (i = 0; i < p->func[func]->pin_count; i++)
  30:             p->gpio[p->func[func]->pins[i]] = 0;
  31:         mode |= p->func[func]->value << p->groups[group].shift;
  32:     }
  33:     rt_sysc_w32(mode, SYSC_REG_GPIO_MODE);
  34:  
  35:     // manfeel, add debug info
  36:     
  37:     dev_info(p->dev, "%s(%d),%s(%d)\t\t= %x\n", p->groups[group].name, group, p->func[func]->name, func, mode);
  38:  
  39:     return 0;
  40: }

在TTL控制台中,能观察到这些信息:

   1: [    0.080000] rt2880-pinmux pinctrl.1: ephy(12),gpio(0)                = ab11d
   2: [    0.080000] rt2880-pinmux pinctrl.1: wled(11),gpio(0)                = ab11d
   3: [    0.090000] rt2880-pinmux pinctrl.1: pa(13),gpio(0)          = 1ab11d
   4: [    0.090000] rt2880-pinmux pinctrl.1: wdt(4),gpio(0)          = 5ab11d
   5: [    0.100000] rt2880-pinmux pinctrl.1: uartf(1),gpio(0)                = 5ab11d
   6: [    0.100000] rt2880-pinmux pinctrl.1: mdio(5),gpio(0)         = 5ab11d
   7: [    0.110000] bio: create slab <bio-0> at 0
   8: [    0.120000] rt2880_gpio 10000600.gpio: registering 24 gpios
   9: [    0.120000] rt2880_gpio 10000600.gpio: registering 24 irq handlers
  10: [    0.130000] rt2880_gpio 10000638.gpio: registering 16 gpios
  11: [    0.130000] rt2880_gpio 10000638.gpio: registering 16 irq handlers
  12: [    0.140000] rt2880_gpio 10000660.gpio: registering 32 gpios
  13: [    0.140000] rt2880_gpio 10000660.gpio: registering 32 irq handlers
  14: [    0.150000] rt2880_gpio 10000688.gpio: registering 1 gpios
  15: [    0.150000] rt2880_gpio 10000688.gpio: registering 1 irq handlers
  16: ... ...
  17: [    0.160000] rt2880-pinmux pinctrl.1: i2c(0),i2c(1)           = 5ab11c
  18: [    0.170000] i2c-ralink 10000900.i2c: loaded
  19: ... ...
  20: [    0.410000] rt2880-pinmux pinctrl.1: uartlite(3),uartlite(10)                = 5ab11c
  21: [    0.410000] 10000c00.uartlite: ttyS0 at MMIO 0x10000c00 (irq = 20, base_baud = 2500000) is a 16550A
  22: ... ...
  23: [    0.470000] rt2880-pinmux pinctrl.1: spi(2),spi(9)           = 5ab11c
  24: [    0.500000] m25p80 spi32766.0: found en25p64, expected w25q256
  25: [    0.500000] m25p80 spi32766.0: en25p64 (8192 Kbytes)

结合dts文件,能够理解得更加透彻(用I2C来分析):

   1: i2c@900 {
   2:     compatible = "link,mt7620a-i2c", "ralink,rt2880-i2c";
   3:     reg = <0x900 0x100>;
   4:  
   5:     resets = <&rstctrl 16>;
   6:     reset-names = "i2c";
   7:  
   8:     #address-cells = <1>;
   9:     #size-cells = <0>;
  10:  
  11:     status = "disabled";
  12:  
  13:     pinctrl-names = "default";
  14:     pinctrl-0 = <&i2c_pins>;
  15: };
  16: ... ...
  17: pinctrl {
  18:     compatible = "ralink,rt2880-pinmux";
  19:     pinctrl-names = "default";
  20:     pinctrl-0 = <&state_default>;
  21:     
  22:     i2c_pins: i2c {
  23:         i2c {
  24:             ralink,group = "i2c";
  25:             ralink,function = "i2c";
  26:         };
  27:     };
  28: };

dts中,每一个设备节点可能需要用到一些复用管脚,该机制通过pinctrl-0来指定,如i2c中的i2c_pins.我们再来看看spi设备节点中的数据:

   1: spi@b00 {
   2:     compatible = "ralink,mt7620a-spi", "ralink,rt2880-spi";
   3:     reg = <0xb00 0x100>;
   4:  
   5:     resets = <&rstctrl 18>;
   6:     reset-names = "spi";
   7:  
   8:     #address-cells = <1>;
   9:     #size-cells = <1>;
  10:  
  11:     status = "disabled";
  12:  
  13:     pinctrl-names = "default";
  14:     pinctrl-0 = <&spi_pins>;
  15: };

结合前面的知识,对dts修改如下:

   1: spi_pins: spi {
   2:     spi {
   3:         ralink,group = "spi";
   4:         ralink,function = "spi";
   5:     };
   6:     /* added by manfeel */    
   7:     cs1 {
   8:         ralink,group = "spi refclk";
   9:         ralink,function = "spi refclk";
  10:     };
  11: };

至此,mt7620的第二个spi终于可以使用.

 

总结

充分利用dts的功能,能够达到很多意想不到的效果.一句话:dts的强大超乎我们的想象!

最后一点也是最重要的一点:很多7620的开发板在设计的时候,并没有考虑要使用第二个spi接口,因此对cs1的管脚是做悬浮处理的,如果要正确使用spi的cs1,则需要对该引脚做上拉处理,否则会导致系统出现各种奇怪的问题.

设备树(device tree)机制是Linux内核从linux-3.x版本开始引进的一种机制,目的是解决内核源码的arch/arm目录下代码混乱的问题:随着ARM生态的快速发展,在内核源码的arch/arm目录下,存放着几十种arm芯片和几百个开发板相关的源文件,很多开发板和处理器的中断、寄存器等相关硬件资源都在这个目录下以.c或.h的文件格式定义。而对于内核来说,这些硬件耦合,会导致内核代码混乱不堪,每个开发板上运行的内核镜像都必须单独编译配置,无法通用。什么时候Linux内核能像Windows镜像那样,无论你的电脑什么配置,一个Windows安装包,都可以直接下载安装运行呢?设备树机制,实现了Linux内核和硬件平台的解耦:每个硬件平台的硬件资源使用一个设备树文件(xxx.dts)来描述,而不是在arch/arm下以.c 或 .h 文件来定义。Linux内核是一个通用的内核,在启动过程中,在通过解析设备树中的硬件资源来初始化某个具体的平台。 引入设备树后,很多和内核驱动开发的工作也发生了变化:以往驱动工程师关注的头文件宏定义、寄存器定义,现在这些基本上不用关注,关注的重点则转向了如何根据硬件平台去配置和修改设备树文件。很多驱动的编程接口也发生了变化,开始慢慢使用device tree提供的编程接口去开发驱动。本期课程主要面向嵌入式开发人员,分享Linux下驱动开发所需要的设备树知识和必备技能
<think>好的,用户的问题是关于在MT7981芯片上通过DTS配置将四个GPIO引脚设置为SPI功能。我需要先理解用户的需求,然后结合提供的引用资料来解答。 首先,用户提到了MT7981芯片,这应该是联发科的一款处理器,常用于路由器等嵌入式设备。DTS(设备树源文件)用于描述硬件配置,特别是在Linux内核中。用户想配置四个GPIO作为SPI使用,这通常涉及到引脚复用(pinctrl)和SPI控制器的设置。 根据引用内容,引用[3]提到了MT7688/MT7628的GPIO复用配置和SPI的DTS配置,这可能MT7981类似,但需要确认MT7981的具体情况。另外,引用[1]中有一个DTS示例,里面提到了leds的配置,虽然不直接相关,但展示了DTS的结构。引用[2]提到了GPIO-LEDS的配置,可能涉及到GPIO的编号方式,比如使用&gpio1这样的节点。 首先,SPI通常需要四个引脚:CS(片选)、CLK(时钟)、MOSI(主出从入)、MISO(主入从出)。所以用户需要配置这四个GPIO为SPI功能。 步骤可能包括: 1. 确定要使用的GPIO引脚编号,并检查它们是否支持SPI复用。 2. 在DTS中配置pinctrl,将这四个GPIO复用为SPI功能。 3. 配置SPI控制器节点,引用这些pinctrl设置。 4. 可能需要处理片选(CS)引脚,特别是如果有多个CS的话。 引用[3]中提到,SPI_CS1的配置需要修改pinctrl,并注意pinctrl-0中的设置。例如,使用系统SPI控制器时,需要配置CS1SPI_CS1功能,并调整pinctrl设置。这可能涉及到在pinctrl节点中定义SPI相关的引脚组。 另外,GPIO的编号方式可能像引用[2]中提到的,使用类似&pio 35 0的格式,其中35是GPIO号,0可能表示激活电平。但在SPI配置中,可能需要使用不同的属性,比如function和groups来设置复用功能。 需要注意的是,不同芯片的GPIO复用方式可能不同,所以必须参考MT7981的具体文档。但用户提供的引用中没有MT7981的具体SPI配置,只有MT7688/MT7628的例子,因此可能需要类比处理。 可能的DTS配置步骤: -pinctrl节点中定义SPI的引脚组,设置复用功能为SPI-SPI控制器节点中引用这些pinctrl设置。 - 配置CS引脚,可能需要单独的GPIO控制,特别是如果使用软件CS。 例如,在MT7688中(引用[3]),SPI的配置包括pinctrl-0和pinctrl-names,以及设置CS引脚。对于MT7981,可能需要类似的配置,但具体GPIO编号和复用功能需要调整。 总结,用户需要: 1. 确定四个GPIO对应的SPI功能引脚。 2. 在DTS的pinctrl部分配置这些引脚为SPI功能。 3. 在SPI控制器节点中启用并配置这些引脚。 4. 处理片选信号,可能需要额外的GPIO配置。 可能遇到的问题包括GPIO编号是否正确,复用功能名称是否正确,以及是否其他功能冲突。需要确保没有其他节点使用了相同的GPIO,并且pinctrl配置正确。 最后,生成相关问题,可能涉及SPI的其他配置、调试方法或性能优化。</think>### MT7981芯片DTS配置SPI功能分步说明 #### 1. 确定GPIO复用关系 首先需查阅MT7981芯片手册确认四个GPIO对应的SPI功能引脚: - **SPI_CLK**:时钟信号线 - **SPI_MOSI**:主设备输出从设备输入 - **SPI_MISO**:主设备输入从设备输出 - **SPI_CS**:片选信号线(可配置多个) 示例假设使用GPIO35作为SPI_CLK,GPIO36作为MOSI,GPIO37作为MISO,GPIO38作为CS0(具体编号需根据实际芯片手册调整)[^3]。 #### 2. 配置pinctrl复用 在DTS文件中添加引脚控制组: ```dts &pinctrl { spi_pins: spi { pins-spi { pinmux = <GPIO35_FUNC_SPI_CLK>, // 复用功能值需查手册 <GPIO36_FUNC_SPI_MOSI>, <GPIO37_FUNC_SPI_MISO>, <GPIO38_FUNC_SPI_CS0>; bias-disable; // 关闭上下拉 drive-strength = <8>;// 驱动强度设置 }; }; }; ``` #### 3. 配置SPI控制器节点 启用SPI控制器并关联引脚: ```dts &spi0 { status = "okay"; // 启用SPI控制器 pinctrl-names = "default"; // 引脚组名称 pinctrl-0 = <&spi_pins>; // 绑定定义的引脚组 // 配置SPI参数 num-cs = <1>; // 片选信号数量 cs-gpios = <&pio 38 0>; // CS0使用GPIO38 // 挂接SPI设备示例(如Flash) flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <50000000>; }; }; ``` #### 4. 关键配置说明 - **GPIO复用函数**:`GPIOXX_FUNC_XXX`需替换为芯片手册中定义的宏,例如MT7628芯片中`GPIO38`作为SPI_CS1时对应`SPI_CS1`功能[^3] - **片选信号处理**:若使用硬件CS信号需配置`num-cs`参数,若需软件控制CS可增加`cs-gpios`定义 - **驱动强度**:根据实际信号质量调整`drive-strength`参数(单位mA) #### 5. 完整配置示例 ```dts / { compatible = "mediatek,mt7981"; pinctrl { spi_pins: spi { pins-spi { pinmux = <GPIO35_FUNC_SPI_CLK>, <GPIO36_FUNC_SPI_MOSI>, <GPIO37_FUNC_SPI_MISO>, <GPIO38_FUNC_SPI_CS0>; bias-disable; drive-strength = <8>; }; }; }; }; &spi0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi_pins>; num-cs = <1>; cs-gpios = <&pio 38 0>; flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <50000000>; }; }; ``` #### 6. 编译验证 执行`make menuconfig`确认SPI驱动已启用,编译固件后通过`dmesg | grep spi`查看设备注册状态,使用逻辑分析仪或示波器验证信号波形。
评论 23
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值