中山网站建设托管,网站备案取消 后果,用自己电脑做主机做网站,郑州专业网站制作的公司哪家好目录 一、问题描述二、讲该问题的解决方案三、imx6ull的spi适配器驱动程序控制片选分析3.1 设备icm20608的驱动程序分析3.2 imx的spi适配器的驱动程序分析 四、BUG修复测试五、其他问题 一、问题描述
使用正点的im6ull开发板进行spi通信驱动开发实验的时候#xff0c;主机无法… 目录 一、问题描述二、讲该问题的解决方案三、imx6ull的spi适配器驱动程序控制片选分析3.1 设备icm20608的驱动程序分析3.2 imx的spi适配器的驱动程序分析 四、BUG修复测试五、其他问题 一、问题描述
使用正点的im6ull开发板进行spi通信驱动开发实验的时候主机无法与从机进行正常通信。就算使用正点的例程也无法正常通信。读不到从机寄存器中的值。以读取从机ID为例例子为正点原子的例程基础上添加了几行printk用来打印信息
void icm20608_reginit(void)
{u8 value 1;u8 test 1;icm20608_write_onereg(icm20608dev, ICM20_PWR_MGMT_1, 0x80); /* 复位 */mdelay(50);icm20608_write_onereg(icm20608dev, ICM20_PWR_MGMT_1, 0x01); /* 关闭睡眠 */mdelay(50);printk(ICM20608 ID %#X\r\n, value); value icm20608_read_onereg(icm20608dev, ICM20_WHO_AM_I); /* 这里读出来的ID不对 */printk(ICM20608 ID %#X\r\n, value); ...
}
...
static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{u8 data 0;icm20608_read_regs(dev, reg, data, 1);return data;
}
...
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{int ret -1;unsigned char txdata[1];unsigned char * rxdata;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); /* 申请内存 */if(!t) {return -ENOMEM;}rxdata kzalloc(sizeof(char) * len, GFP_KERNEL); /* 申请内存 */if(!rxdata) {goto out1;}/* 一共发送len1个字节的数据第一个字节为寄存器首地址一共要读取len个字节长度的数据*/txdata[0] reg | 0x80; /* 写数据的时候首寄存器地址bit8要置1 */ t-tx_buf txdata; /* 要发送的数据 */t-rx_buf rxdata; /* 要读取的数据 */t-len len1; /* t-len发送的长度读取的长度 */spi_message_init(m); /* 初始化spi_message */spi_message_add_tail(t, m);/* 将spi_transfer添加到spi_message队列 */ret spi_sync(spi, m); /* 同步发送 */if(ret) {goto out2;}memcpy(buf , rxdata1, len); /* 只需要读取的数据 */out2:kfree(rxdata); /* 释放内存 */
out1: kfree(t); /* 释放内存 */return ret;
}读ID失败读出来ID是0单纯是因为调用icm20608_read_onereg函数时会把返回值初始化为0也就是根本没读到寄存器中内容 二、讲该问题的解决方案
首先直接说明问题所在如果使用正点的spi驱动开发例程则设备树中spi适配器设备节点下的cs-gpios属性不能写成cs-gpio。当使用的属性名位cs-gpios的时候是由该spi适配器匹配的驱动程序来控制片选。
其实正点原子也在开发指南中说了
第 2 行设置当前片选数量为 1因为就只接了一个 ICM20608。
第 3 行一定要使用 “cs-gpios”属性来描述片选引脚SPI 主机驱动就会控制片选引脚。
第 5 行设置 IO 要使用的 pinctrl 子节点也就是我们在示例代码 62.5.1.1 中新建的
pinctrl_ecspi3。正点提供的linux源码中的documentation中也提到怎么编写对应设备树了该文件位于linux源码位置/Documentation/devicetree/bindings/spi中
* Freescale (Enhanced) Configurable Serial Peripheral Interface(CSPI/eCSPI) for i.MXRequired properties:
- compatible :- fsl,imx1-cspi for SPI compatible with the one integrated on i.MX1- fsl,imx21-cspi for SPI compatible with the one integrated on i.MX21- fsl,imx27-cspi for SPI compatible with the one integrated on i.MX27- fsl,imx31-cspi for SPI compatible with the one integrated on i.MX31- fsl,imx35-cspi for SPI compatible with the one integrated on i.MX35- fsl,imx51-ecspi for SPI compatible with the one integrated on i.MX51
- reg : Offset and length of the register set for the device
- interrupts : Should contain CSPI/eCSPI interrupt
- fsl,spi-num-chipselects : Contains the number of the chipselect
- cs-gpios : Specifies the gpio pins to be used for chipselects.
- clocks : Clock specifiers for both ipg and per clocks.
- clock-names : Clock names should include both ipg and per
See the clock consumer binding,Documentation/devicetree/bindings/clock/clock-bindings.txt
- dmas: DMA specifiers for tx and rx dma. See the DMA client binding,Documentation/devicetree/bindings/dma/dma.txt
- dma-names: DMA request names should include tx and rx if present.Example:ecspi70010000 {#address-cells 1;#size-cells 0;compatible fsl,imx51-ecspi;reg 0x70010000 0x4000;interrupts 36;fsl,spi-num-chipselects 2;cs-gpios gpio3 24 0, /* GPIO3_24 */gpio3 25 0; /* GPIO3_25 */dmas sdma 3 7 1, sdma 4 7 2;dma-names rx, tx;
};但是我太信任正点提供的linux源码了直接用了正点提供的出厂linux源码这个出厂linux源码中的READ_ME解释如下
说明
1、使用此uboot和linux源码可以编译得到正点原子I.MX6U出货时的uboot和Linux固件。兼容正点原子所有RGB屏板子上所有功能也相应调试好。直接按【正点原子】I.MX6U用户快速体验Vx.x.pdf文档编译出来使用
2、驱动指南也提及过编译这里的源码只是教学编译体验。问题答疑
1、看到1、例程源码\10、开发板教程对应的uboot和linux源码这里还有一份正点原子的uboot和linux源码为什么会有两份源码
答1、例程源码\10、开发板教程对应的uboot和linux源码是正点原子驱动指南做驱动实验、移植uboot和linux所用的源码。而出厂源码则是用于出货所使用客户无需再调试。直接编译使用2、为什么要和出厂源码分开
答由于多种原因出厂使用的源码不能与驱动指南所使用的源码同时进行。出厂源码会随时修复bug或者添加新的驱动以兼容正点原子的其他模块。3、那我可不可以这么理解出厂源码是给客户直接用在产品上使用而教程源码则是用于初学者用于学习驱动和移植uboot和Linux上使用
答恩可以这么理解。因为出厂源码是几乎无需再调试了适用正点原子的ALPHA和Mini开发板给一些快速上手的客户使用而教程源码则是用于学习或者自己开发使用他说的是出厂源码是几乎无需再调试了适用于正点原子的ALPHA开发板可以直接上手用我就直接用了。但其实他的设备树是这么写的
ecspi3 {fsl,spi-num-chipselects 1;cs-gpio gpio1 20 GPIO_ACTIVE_LOW; /* 必须使用cs-gpios属性名来描述片选引脚(如果你想让spi适配器驱动程序来设置片选的话) */pinctrl-names default;pinctrl-0 pinctrl_ecspi3; /* 难不成是因为这里是pinctrl-0这个0正好对应这第0通道即icm20608 */status okay;spidev: icm206080 {compatible alientek,icm20608;spi-max-frequency 8000000;reg 0;};
};打眼一看内容跟正点的驱动指南中提到的都一样所以我就觉得没啥问题但是实际上可以看到有一个属性名写成了cs-gpio而不是cs-gpios这是与正点的spi驱动开发例程不匹配的。为什么不匹配这得分析分析适配器的驱动程序
三、imx6ull的spi适配器驱动程序控制片选分析
3.1 设备icm20608的驱动程序分析
首先说明问题问题出现在厂商编写的spi适配器驱动程序上。但在此之前我们要先来看看我们的设备驱动也就是正点使用的设备icm20608的驱动程序中的一个函数spi_setup()
static int icm20608_probe(struct spi_device *spi)
{.../*初始化spi_device */spi-mode SPI_MODE_0; /*MODE0CPOL0CPHA0*/spi_setup(spi);icm20608dev.private_data spi; /* 设置私有数据 *//* 初始化ICM20608内部寄存器 */icm20608_reginit(); return 0;
}该函数传入一个struct spi_device类型的变量。正点驱动对这个函数一带而过只是说这个函数必须得有。其实就是这个函数中出了问题。该函数位于drivers/spi/spi.c中我只列出源码中与错误相关的部分
int spi_setup(struct spi_device *spi)
{unsigned bad_bits, ugly_bits;int status 0;...spi_set_cs(spi, false);if (spi-master-setup)status spi-master-setup(spi);...return status;
}
...
...
static void spi_set_cs(struct spi_device *spi, bool enable)
{if (spi-mode SPI_CS_HIGH)enable !enable;if (spi-cs_gpio 0)gpio_set_value(spi-cs_gpio, !enable);else if (spi-master-set_cs)spi-master-set_cs(spi, !enable);
}从spi_setup调用的spi_set_cs函数中可以看到如果spi-cs_gpio 0函数调用gpio_set_value(spi-cs_gpio, !enable)来设置某个goio口的电平比如在正点的教程中如果要选中icm20608外设那么就要设置gpio1_20这个gpio口的电平所以应该出现的情况是spi-cs_gpio 20。如果没满足第一个if就会判断第二个else if这个master-set_cs根本没设置所以也用不到。
那么spi-cs_gpio是在哪里设置的呢在spi.c文件中有这么一个函数spi_add_device()截取出相关的内容
int spi_add_device(struct spi_device *spi)
{static DEFINE_MUTEX(spi_add_lock);struct spi_master *master spi-master;struct device *dev master-dev.parent;int status;/* Chipselects are numbered 0..max; validate. */if (spi-chip_select master-num_chipselect) {dev_err(dev, cs%d max %d\n,spi-chip_select,master-num_chipselect);return -EINVAL;}...if (master-cs_gpios)spi-cs_gpio master-cs_gpios[spi-chip_select];...
}可以看到在最后一个if中如果master-cs_gpios不为0那么就会设置spi-cs_gpio master-cs_gpios[spi-chip_select]。这个spi-chip_select相当于master-cs_gpios这个数组的索引(master-cs_gpios中cs_gpios的是struct spi_master结构体下的一个成员变量为一个int类型的指针其实就是一个数组)这个会在下一部分提到。
那spi-chip_select又是在哪设置的呢看一下spi.c文件中的of_register_spi_device()函数
#if defined(CONFIG_OF)
static struct spi_device *
of_register_spi_device(struct spi_master *master, struct device_node *nc)
{struct spi_device *spi;int rc;u32 value;/* Alloc an spi_device */spi spi_alloc_device(master);if (!spi) {dev_err(master-dev, spi_device alloc error for %s\n,nc-full_name);rc -ENOMEM;goto err_out;}/* Select device driver */rc of_modalias_node(nc, spi-modalias,sizeof(spi-modalias));if (rc 0) {dev_err(master-dev, cannot find modalias for %s\n,nc-full_name);goto err_out;}/* Device address */rc of_property_read_u32(nc, reg, value);if (rc) {dev_err(master-dev, %s has no valid reg property (%d)\n,nc-full_name, rc);goto err_out;}spi-chip_select value;.../* Register the new device */rc spi_add_device(spi);if (rc) {dev_err(master-dev, spi_device register error %s\n,nc-full_name);goto err_out;}return spi;
}该函数会调用of_property_read_u32()函数解析设备树中的spi设备的reg属性值并赋值给变量value而在设备树中这个值中填入的正是片选值然后设置spi-chip_select value并调用刚刚提到的spi_add_device()函数。该函数会在of_register_spi_devices()函数中调用
static void of_register_spi_devices(struct spi_master *master)
{struct spi_device *spi;struct device_node *nc;if (!master-dev.of_node)return;for_each_available_child_of_node(master-dev.of_node, nc) {spi of_register_spi_device(master, nc);if (IS_ERR(spi))dev_warn(master-dev, Failed to create SPI device for %s\n,nc-full_name);}
}就相当于可能会注册很多个spi设备所以用个for循环来一个一个注册。这个注册很多个spi设备的函数会在spi_register_master()函数中被调用而这个函数是跟spi适配器有关。
可以看到最终的最终spi-cs_gpio其实是跟master-cs_gpios有关系并且上述函数的调用的源头也是spi_register_master()函数。所以必须得去看spi适配器的驱动函数了。但在此之前先来看一下当设备树中写的属性为cs-gpio时出现的情况我们在驱动程序中编写如下测试代码
static int icm20608_probe(struct spi_device *spi)
{.../*初始化spi_device */spi-mode SPI_MODE_0; /*MODE0CPOL0CPHA0*/spi_setup(spi);printk(chip select:%d\r\n, spi-chip_select);printk(cs gpio:%d\r\n, spi-cs_gpio);icm20608dev.private_data spi; /* 设置私有数据 */...return 0;
}注册编译出来的ko模块的时候结果如下 可以看到spi-chip_select正常获取到了设备树中spi设备reg属性的值但是spi-cs_gpio却没有获取到我们想要获取的gpio号20吗而是-2。至于为什么需要看下一部分。
3.2 imx的spi适配器的驱动程序分析
在第二部分中列出的设备树代码中只是列出了引用ecspi3时的一些修改该设备真正定义在imx6ull.dtsi下
ecspi3: ecspi02010000 {#address-cells 1;#size-cells 0;compatible fsl,imx6ul-ecspi, fsl,imx51-ecspi;reg 0x02010000 0x4000;interrupts GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH;clocks clks IMX6UL_CLK_ECSPI3,clks IMX6UL_CLK_ECSPI3;clock-names ipg, per;dmas sdma 7 7 1, sdma 8 7 2;dma-names rx, tx;status disabled;
};其中的属性compatible fsl,imx6ul-ecspi, fsl,imx51-ecspi;根据这个去找对应的驱动程序找到的驱动程序为drivers/spi/spi-imx.c找到其.probe函数并列出其与问题相关的部分
static int spi_imx_probe(struct platform_device *pdev)
{struct device_node *np pdev-dev.of_node;const struct of_device_id *of_id of_match_device(spi_imx_dt_ids, pdev-dev);struct spi_imx_master *mxc_platform_info dev_get_platdata(pdev-dev);struct spi_master *master;struct spi_imx_data *spi_imx;struct resource *res;int i, ret, num_cs, irq;if (!np !mxc_platform_info) {dev_err(pdev-dev, cant get the platform data\n);return -EINVAL;}ret of_property_read_u32(np, fsl,spi-num-chipselects, num_cs); /* 获取到num_cs为1 */if (ret 0) {if (mxc_platform_info)num_cs mxc_platform_info-num_chipselect;elsereturn ret;}master spi_alloc_master(pdev-dev,sizeof(struct spi_imx_data) sizeof(int) * num_cs); /* 这个函数会把num_chipselect初始化为1 */if (!master)return -ENOMEM;...spi_imx-bitbang.master master;for (i 0; i master-num_chipselect; i) {int cs_gpio of_get_named_gpio(np, cs-gpios, i);if (!gpio_is_valid(cs_gpio) mxc_platform_info) /* gpio_is_valid: asm-generic/gpio.h return 1 or 0*/cs_gpio mxc_platform_info-chipselect[i];spi_imx-chipselect[i] cs_gpio;if (!gpio_is_valid(cs_gpio))continue;ret devm_gpio_request(pdev-dev, spi_imx-chipselect[i],DRIVER_NAME);if (ret) {dev_err(pdev-dev, cant get cs gpios\n);goto out_master_put;}}spi_imx-bitbang.chipselect spi_imx_chipselect;spi_imx-bitbang.setup_transfer spi_imx_setupxfer;spi_imx-bitbang.txrx_bufs spi_imx_transfer;spi_imx-bitbang.master-setup spi_imx_setup;spi_imx-bitbang.master-cleanup spi_imx_cleanup;spi_imx-bitbang.master-prepare_message spi_imx_prepare_message;spi_imx-bitbang.master-unprepare_message spi_imx_unprepare_message;spi_imx-bitbang.master-mode_bits SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;...master-dev.of_node pdev-dev.of_node;ret spi_bitbang_start(spi_imx-bitbang);...return ret;
}并会在spi_bitbang_start函数中调用了spi_register_master()函数也就是上一部分提到的很多函数的源头向内核注册spi_master设备该函数位于drivers/spi/spi-bitbang.c中列出相关部分
int spi_bitbang_start(struct spi_bitbang *bitbang)
{struct spi_master *master bitbang-master;int ret;if (!master || !bitbang-chipselect)return -EINVAL;spin_lock_init(bitbang-lock);...ret spi_register_master(spi_master_get(master));if (ret)spi_master_put(master);return 0;
}接下来我们就可以看一下spi_register_master()这个函数了。上一部分讲的函数主要作用是帮助注册spi设备而这个函数的作用是向内核注册spi适配器设备该函数同样位于spi.c中只列出与我们的bug相关的代码
int spi_register_master(struct spi_master *master)
{static atomic_t dyn_bus_id ATOMIC_INIT((115) - 1);struct device *dev master-dev.parent;struct boardinfo *bi;int status -ENODEV;int dynamic 0;if (!dev)return -ENODEV;status of_spi_register_master(master);.../* Register devices from the device tree and ACPI */of_register_spi_devices(master); /* 这里会调用of_register_spi_device */acpi_register_spi_devices(master);
done:return status;
}关键函数就是of_spi_register_master()这个函数该函数同样定义在spi.c中其源码如下
#ifdef CONFIG_OF
static int of_spi_register_master(struct spi_master *master)
{int nb, i, *cs;struct device_node *np master-dev.of_node;printk(in of_spi_register_master?\r\n); /* 我自己加的test判断一下CONFIG_OF这个宏定义是否开启了 */if (!np)return 0;nb of_gpio_named_count(np, cs-gpios);master-num_chipselect max_t(int, nb, master-num_chipselect);/* Return error only for an incorrectly formed cs-gpios property */if (nb 0 || nb -ENOENT)return 0;else if (nb 0)return nb;cs devm_kzalloc(master-dev,sizeof(int) * master-num_chipselect,GFP_KERNEL);master-cs_gpios cs;if (!master-cs_gpios)return -ENOMEM;for (i 0; i master-num_chipselect; i)cs[i] -ENOENT;for (i 0; i nb; i)cs[i] of_get_named_gpio(np, cs-gpios, i);return 0;
}从这个函数中可以看出该函数首先调用of_gpio_named_count来统计cs-gpios这个属性中设置的gpio的个数。很不幸我们在设备树中把属性写成了cs-gpio所以该函数找不到节点就会返回负值。然后该函数将master-num_chipselect设定为了master-num_chipselect和nb这两个变量中的最大值master-num_chipselect这个变量会在.probe函数中通过读取设备树中spi适配器设备节点中的fsl,spi-num-chipselects属性来初始化。根据设备树来看该属性的值被设为了1。
前文提到master-cs_gpios是一个int类型的指针所以在该函数中将他指向了一段使用devm_kzalloc()函数分配的内存可以理解为现在master-cs_gpios就是一个数组了里面有num_chipselect个数据
然后该函数调用两个for循环来对master-cs_gpios这个数组进行初始化因为master-num_chipselect为1所以第一个for循环正常执行csp[i] -ENOENT而这个宏定义ENOENT恰恰就是2。并且因为变量nb是个负数所以第二个for不会执行就算执行这个循环他也根本找不到cs-gpios这个属性(我写的设备树少了一个s)。
所以综上master-cs_gpios全被初始化为了-2导致第一部分提到的spi设备结构体中的变量(即spi-cs_gpio)也为-2从而导致在调用gpio_set_value()函数来对spi-cs_gpio这个gpio号的时候没有正确设置。
四、BUG修复测试
前面已经说明了把设备树中的属性改为cs-gpios应该就行了
ecspi3 {fsl,spi-num-chipselects 1;cs-gpios gpio1 20 GPIO_ACTIVE_LOW; /* 必须使用cs-gpio属性来描述片选引脚可能spi适配器驱动里用的就是这个名称cs-gpio */pinctrl-names default;pinctrl-0 pinctrl_ecspi3; /* 难不成是因为这里是pinctrl-0这个0正好对应这第0通道即icm20608 */status okay;spidev: icm206080 {compatible alientek,icm20608;spi-max-frequency 8000000;reg 0;};
};然后重新编译设备树测试结果如下 gpio口获取也对了也能正确读到icm20608的设备id了。
那如果我就是想用cs-gpio这个名呢那也有办法就是在你自己写的设备驱动程序中使用of_get_named_gpio()函数来手动获取gpio号但是传入该函数的属性名就不要是cs-gpios了而应该是cs-gpio(如果用这样的方法相当于跳过spi框架和spi适配器驱动函数那么你取啥名都可以我把属性写成本gpios-cs也没问题)。然后再使用gpio_set_value()函数来设置这个gpio口即可
五、其他问题
其实有个问题不知道有没有注意到就是在分析驱动程序的时候出现了一个#ifdef CONFIG_OF上述很多函数都依赖于这个宏必须得被定义了才能起作用。这个宏我根本没找到在哪里定义的如果没定义这个宏那之前的分析就白费了所以找了一个依赖于这个宏的函数进行测试
#ifdef CONFIG_OF
static int of_spi_register_master(struct spi_master *master)
{int nb, i, *cs;struct device_node *np master-dev.of_node;printk(in of_spi_register_master?\r\n); /* 我自己加的test判断一下CONFIG_OF这个宏定义是否开启了 */...return 0;
}然后重新编译内核再次启动内核启动过程中打印结果如下 看来这个宏确实是定义了但是定义在哪个位置我真没找到有知道的老哥可以教一下。