最初的问题是 编译内核添加了 spi 支持,配置了 board 后,加载25q64驱动不执行probe 函数。
然后发现是,spi-s3c24xx.c 中的 probe 没有执行完就退出了 没有生成 spi-master
/drivers/spi/spi-s3c24xx.c
定位在 出错hw->clk = devm_clk_get(&pdev->dev, "spi");if (IS_ERR(hw->clk)) { dev_err(&pdev->dev, "No clock for device\n"); err = PTR_ERR(hw->clk); goto err_no_pdata;}对应下面struct clk *clk_get(struct device *dev, const char *con_id){ const char *dev_id = dev ? dev_name(dev) : NULL; struct clk *clk; if (dev) { clk = __of_clk_get_by_name(dev->of_node, dev_id, con_id); if (!IS_ERR(clk) || PTR_ERR(clk) == -EPROBE_DEFER) return clk; } return clk_get_sys(dev_id, con_id);}of_是设备树相关的函数。 clk_get_sys 是 调用 clk_get 时 传的 NULL 的情况下调用。看一下 clk_get_sys(dev_id, con_id) 实现struct clk *clk_get_sys(const char *dev_id, const char *con_id){ struct clk_lookup *cl; struct clk *clk = NULL; mutex_lock(&clocks_mutex); cl = clk_find(dev_id, con_id); if (!cl) goto out; clk = __clk_create_clk(__clk_get_hw(cl->clk), dev_id, con_id); if (IS_ERR(clk)) goto out; if (!__clk_get(clk)) { __clk_free_clk(clk); cl = NULL; goto out; }out: mutex_unlock(&clocks_mutex); return cl ? clk : ERR_PTR(-ENOENT);}查找 clk_find(dev_id, con_id); 函数static struct clk_lookup *clk_find(const char *dev_id, const char *con_id){ struct clk_lookup *p, *cl = NULL; int match, best_found = 0, best_possible = 0; if (dev_id) best_possible += 2; if (con_id) best_possible += 1; list_for_each_entry(p, &clocks, node) { match = 0; if (p->dev_id) { if (!dev_id || strcmp(p->dev_id, dev_id)) continue; match += 2; } if (p->con_id) { if (!con_id || strcmp(p->con_id, con_id)) continue; match += 1; } if (match > best_found) { cl = p; if (match != best_possible) best_found = match; else break; } } return cl;}检查 clocks 创建过程,有非常多的嵌套,就像是 俄罗期套娃。 /arch/arm/mach-s3c24xx/mach-smdk2440.csmdk2440_init_time() /arch/arm/mach-s3c24xx/common.c s3c2440_init_clocks(12000000) /drivers/clk/samsung/clk-s3c2410.c s3c2410_common_clk_init(NULL, 12000000, 1, S3C24XX_VA_CLKPWR); /* Register common internal clocks. 通用的内部时钟 */ samsung_clk_register_mux(ctx, s3c2410_common_muxes, ARRAY_SIZE(s3c2410_common_muxes)); samsung_clk_register_div(ctx, s3c2410_common_dividers, ARRAY_SIZE(s3c2410_common_dividers)); samsung_clk_register_gate(ctx, s3c2410_common_gates, ARRAY_SIZE(s3c2410_common_gates)); if (current_soc == S3C2440 || current_soc == S3C2442) { samsung_clk_register_div(ctx, s3c244x_common_dividers, ARRAY_SIZE(s3c244x_common_dividers)); samsung_clk_register_gate(ctx, s3c244x_common_gates, ARRAY_SIZE(s3c244x_common_gates)); samsung_clk_register_mux(ctx, s3c244x_common_muxes, ARRAY_SIZE(s3c244x_common_muxes)); samsung_clk_register_fixed_factor(ctx, s3c244x_common_ffactor, ARRAY_SIZE(s3c244x_common_ffactor)); } /* 注册别名 * Register common aliases at the end, as some of the aliased clocks * are SoC specific. */ samsung_clk_register_alias(ctx, s3c2410_common_aliases, ARRAY_SIZE(s3c2410_common_aliases)); if (current_soc == S3C2440 || current_soc == S3C2442) { samsung_clk_register_alias(ctx, s3c244x_common_aliases, ARRAY_SIZE(s3c244x_common_aliases)); }最终定位到struct samsung_gate_clock s3c2410_common_gates[] __initdata = { GATE(PCLK_SPI, "spi", "pclk", CLKCON, 18, 0, 0), GATE(PCLK_I2S, "i2s", "pclk", CLKCON, 17, 0, 0), GATE(PCLK_I2C, "i2c", "pclk", CLKCON, 16, 0, 0), GATE(PCLK_ADC, "adc", "pclk", CLKCON, 15, 0, 0), GATE(PCLK_RTC, "rtc", "pclk", CLKCON, 14, 0, 0), GATE(PCLK_GPIO, "gpio", "pclk", CLKCON, 13, CLK_IGNORE_UNUSED, 0), GATE(PCLK_UART2, "uart2", "pclk", CLKCON, 12, 0, 0), GATE(PCLK_UART1, "uart1", "pclk", CLKCON, 11, 0, 0), GATE(PCLK_UART0, "uart0", "pclk", CLKCON, 10, 0, 0), GATE(PCLK_SDI, "sdi", "pclk", CLKCON, 9, 0, 0), GATE(PCLK_PWM, "pwm", "pclk", CLKCON, 8, 0, 0), GATE(HCLK_USBD, "usb-device", "hclk", CLKCON, 7, 0, 0), GATE(HCLK_USBH, "usb-host", "hclk", CLKCON, 6, 0, 0), GATE(HCLK_LCD, "lcd", "hclk", CLKCON, 5, 0, 0), GATE(HCLK_NAND, "nand", "hclk", CLKCON, 4, 0, 0),};/* should be added _after_ the soc-specific clocks are created */struct samsung_clock_alias s3c2410_common_aliases[] __initdata = { ALIAS(PCLK_I2C, "s3c2410-i2c.0", "i2c"), ALIAS(PCLK_ADC, NULL, "adc"), ALIAS(PCLK_RTC, NULL, "rtc"), ALIAS(PCLK_PWM, NULL, "timers"), ALIAS(HCLK_LCD, NULL, "lcd"), ALIAS(HCLK_USBD, NULL, "usb-device"), ALIAS(HCLK_USBH, NULL, "usb-host"), ALIAS(UCLK, NULL, "usb-bus-host"), ALIAS(UCLK, NULL, "usb-bus-gadget"), ALIAS(ARMCLK, NULL, "armclk"), ALIAS(UCLK, NULL, "uclk"), ALIAS(HCLK, NULL, "hclk"), ALIAS(MPLL, NULL, "mpll"), ALIAS(FCLK, NULL, "fclk"), ALIAS(PCLK, NULL, "watchdog"), ALIAS(PCLK_SDI, NULL, "sdi"), ALIAS(HCLK_NAND, NULL, "nand"), ALIAS(PCLK_I2S, NULL, "iis"), ALIAS(PCLK_I2C, NULL, "i2c"), ALIAS(PCLK_SPI, NULL, "spi"), // 添加一行 };使用新内核启动WARNING: CPU: 0 PID: 1 at drivers/clk/clk.c:1067 clk_core_enable+0x94/0xa4()Modules linked in:不确定到底是何原因引起是 SPI 时钟添加的方式不对,还是spi-s3c24xx.c 中使用的不对。先改为 i2c 启动看是否还有错误hw->clk = devm_clk_get(&pdev->dev, "i2c");这一次启动没有了 WARNING 信息,说明 spi-s3c24xx.c 驱动没有问题。 后来我又一想,可能 是因为内核中有 I2C 的驱动,I2C 初始化了时钟,所以 没有问题。重新编译 内核去掉了 I2C 后,重新启动,果然还是有 WARNING 说明 spi-s3c24xx.c 有问题。自己写一个小的驱动来测试下 SPI 时钟是否可以工作使用。clk_get(NULL, "spi"); 已经可以生效了。程序还不是很好用,但是可以读到 flash id ,id 好像也不太对。 后期在更新。
更新2: 使用spi 平台总线驱动测试
------------------------------------------------------------------------------------------------------------
启动出错
WARNING: CPU: 0 PID: 1 at drivers/clk/clk.c:1067 clk_core_enable+0x94/0xa4()s3c2410-spi s3c2410-spi.1: Failed to get gpio for css3c2410-spi: probe of s3c2410-spi.1 failed with error -16if (!pdata->set_cs) { //因为我没有提供 pin_cs 这里判断是小于0 所以没有判断出来 if (pdata->pin_cs < 0) { dev_err(&pdev->dev, "No chipselect pin\n"); err = -EINVAL; goto err_register; } //出错位置 err = devm_gpio_request(&pdev->dev, pdata->pin_cs, dev_name(&pdev->dev)); if (err) { dev_err(&pdev->dev, "Failed to get gpio for cs\n"); goto err_register; } //设置操作函数 hw->set_cs = s3c24xx_spi_gpiocs; //设置为输出功能 gpio_direction_output(pdata->pin_cs, 1);} else hw->set_cs = pdata->set_cs;看来只能自己设置一个选中函数devs.cstatic void s3c2400_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol){ gpio_set_value(cs, pol);}struct s3c2410_spi_info spi_info0 = { //平台 总线号 .bus_num = 0, //最大片选数量 .num_cs = 0xff, .set_cs = s3c2400_spi_gpiocs,};struct s3c2410_spi_info spi_info1 = { //平台 总线号 .bus_num = 1, //最大片选数量 .num_cs = 0xff, .set_cs = s3c2400_spi_gpiocs,};添加了一个函数后,编译内核,重新启动还是有 WARNING: CPU: 0 PID: 1 at drivers/clk/clk.c:1067 clk_core_enable+0x94/0xa4()出错位置 对比下 i2c /* initialise the i2c controller */clk_prepare_enable(i2c->clk);ret = s3c24xx_i2c_init(i2c);clk_disable(i2c->clk); static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw){ /* for the moment, permanently enable the clock */clk_prepare_enable(i2c->clk); //新添加 可能是要先启用父级时钟?
clk_enable(hw->clk); }重编译并启动s3c2410-spi s3c2410-spi.1: chipselect 194 already in uses3c2410-spi s3c2410-spi.1: can't create new device for spi_flash经过查找代码在 spi.cint spi_add_device(struct spi_device *spi){ status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check); if (status) { dev_err(dev, "chipselect %d already in use\n", spi->chip_select); goto done; }}//在这里,检查 board 的片选是否相同static int spi_dev_check(struct device *dev, void *data){ struct spi_device *spi = to_spi_device(dev); struct spi_device *new_spi = data; if (spi->master == new_spi->master && spi->chip_select == new_spi->chip_select) return -EBUSY; return 0;}因为我写的2个board的片选是同一个引脚。(因为我是自己焊接连线,要是买成本板就不会遇到这种事,为了接线比较简单,就用同一个片选)结果在这里出错。static struct spi_board_info spi_info_jz2440[] = { { .modalias = "spi_tft", /* 对应的spi_driver名字也是"spi_tft" */ .max_speed_hz = 10000000, /* max spi clock (SCK) speed in HZ */ .bus_num = 1, /* jz2440里OLED接在SPI CONTROLLER 1 */ .mode = SPI_MODE_0, /* 相位极性 */ //.chip_select = S3C2410_GPG(2), /* oled_cs, 它的含义由spi_master确定 */ //改为不同的片选 和下面的 spi_flash 不能相同 .chip_select = S3C2410_GPE(14), /* IICSCL, 它的含义由spi_master确定 */ .platform_data = (const void *)S3C2410_GPE(15), /* SDA oled_dc, 它在spi_driver里使用 */ }, { .modalias = "spi_flash", .max_speed_hz = 80000000, .bus_num = 1, .mode = SPI_MODE_0, .chip_select = S3C2410_GPG(2), },};static int spi_info_jz2440_init(void){ return spi_register_board_info(spi_info_jz2440, ARRAY_SIZE(spi_info_jz2440));}使用内核启动以后发现2个 spi 设备。就是刚才注册的 spi_tft 和 spi_flash加载驱动后出错。
可以看到 spi 平台driver 了。
驱动还是有点问题,后期在更新。(此驱动和上面的测试时钟的不是一样的,是采用 spi 平台总线编写,不是直接操作spi寄存器)
第3次,更新解决问题。
WARNING: CPU: 0 PID: 986 at drivers/base/dd.c:286 driver_probe_device+0x270/0x298()Modules linked in: spi_flash_mtd(O+)CPU: 0 PID: 986 Comm: insmod Tainted: G O 4.1.36 #135Hardware name: SMDK2440在 git 上找到了补丁https://github.com/raspberrypi/linux/commit/a97c883a16da7e0691a3be5465926c92a8da4da6方法是,不过和我的有点不同。 drivers/spi/spi-dw.c devm_kzalloc 换为 kzalloc我这里修改drivers/spi/spi-s3c24xx.cstatic int s3c24xx_spi_setup(struct spi_device *spi){ struct s3c24xx_spi_devstate *cs = spi->controller_state; struct s3c24xx_spi *hw = to_hw(spi); int ret; /* allocate settings on the first call */ if (!cs) { /* cs = devm_kzalloc(&spi->dev, sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL); */ cs = kzalloc(sizeof(struct s3c24xx_spi_devstate), GFP_KERNEL); if (!cs) return -ENOMEM; cs->spcon = SPCON_DEFAULT; cs->hz = -1; spi->controller_state = cs; }}添加一个清理函数, master 好像也不会调用销毁, 对了,当SPI核心支持做为模块,卸载的时候就会调用下我在这里加个 printk 看看是不是会调用static void s3c24xx_spi_cleanup(struct spi_device *spi){ struct chip_data *chip = spi_get_ctldata(spi); kfree(chip); spi_set_ctldata(spi, NULL); printk("\n\n\n\n\n\n spi_clanup ok \n\n\n\n\n");}static int s3c24xx_spi_probe(struct platform_device *pdev){ 添加一行 hw->master->cleanup = s3c24xx_spi_cleanup;}编译新内核,spi 变成模块,加载后,卸载,打印出来了,清理信息。
重新加载 spi_flash 模块,正常了没有错误。
spi-s3c24xx.c 改后的文件
第4次更新,读不到flash id
读出来的id 都是 0xff , 检查代码,发现没有设置spi 的 gpio 引脚功能。
spi-s3c24xx.c
static void s3c24xx_spi_initialsetup(struct s3c24xx_spi *hw)
{if (hw->pdata) {
if (hw->set_cs == s3c24xx_spi_gpiocs) gpio_direction_output(hw->pdata->pin_cs, 1); if (hw->pdata->gpio_setup) hw->pdata->gpio_setup(hw->pdata, 1); }}
这里需要自己在 平台资源中提供一个 。
改后的 devs.c
1 static void s3c2400_spi_gpiocs(struct s3c2410_spi_info *spi, int cs, int pol) 2 { 3 gpio_set_value(cs, pol); 4 } 5 6 static void s3c2400_spi_gpiosetup(struct s3c2410_spi_info *spi, int pin) 7 { 8 if(0 == spi->bus_num) 9 {10 11 }12 else13 {14 /**15 SPIMI GPG5 SPICLK GPG716 SPIMO GPG6 nSSSPI GPG217 */18 s3c_gpio_cfgpin(S3C2410_GPG(5), S3C2410_GPG5_SPIMISO1);19 s3c_gpio_cfgpin(S3C2410_GPG(6), S3C2410_GPG6_SPIMOSI1);20 s3c_gpio_cfgpin(S3C2410_GPG(7), S3C2410_GPG7_SPICLK1);21 22 }23 }24 25 struct s3c2410_spi_info spi_info0 = {26 //平台 总线号27 .bus_num = 0,28 //最大片选数量29 .num_cs = 0xff,30 .set_cs = s3c2400_spi_gpiocs,31 .gpio_setup = s3c2400_spi_gpiosetup,32 };33 34 struct s3c2410_spi_info spi_info1 = {35 //平台 总线号36 .bus_num = 1,37 //最大片选数量38 .num_cs = 0xff,39 .set_cs = s3c2400_spi_gpiocs,40 .gpio_setup = s3c2400_spi_gpiosetup,41 };42 43 static struct resource s3c_spi0_resource[] = {44 [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI, SZ_32),45 [1] = DEFINE_RES_IRQ(IRQ_SPI0),46 };47 48 struct platform_device s3c_device_spi0 = {49 .name = "s3c2410-spi",50 .id = 0,51 .num_resources = ARRAY_SIZE(s3c_spi0_resource),52 .resource = s3c_spi0_resource,53 .dev = {54 .dma_mask = &samsung_device_dma_mask,55 .coherent_dma_mask = DMA_BIT_MASK(32),56 .platform_data = &spi_info0,57 }58 };59 60 static struct resource s3c_spi1_resource[] = {61 [0] = DEFINE_RES_MEM(S3C24XX_PA_SPI1, SZ_32),62 [1] = DEFINE_RES_IRQ(IRQ_SPI1),63 };64 65 struct platform_device s3c_device_spi1 = {66 .name = "s3c2410-spi",67 .id = 1,68 .num_resources = ARRAY_SIZE(s3c_spi1_resource),69 .resource = s3c_spi1_resource,70 .dev = {71 .dma_mask = &samsung_device_dma_mask,72 .coherent_dma_mask = DMA_BIT_MASK(32),73 .platform_data = &spi_info1,74 }75 };
因为我这里没有使用 spi0 pcb 上也没有接线, 这里就不设置了。
到此,spi 总线工作正常。
总结问题有:
1, spi 时钟获取不到
2,分配内存函数使用不当 devm_kzalloc
3,同一个spi 总线上的 片选不能相同
4,需要提供,初始化 spi gpio 功能设置函数
后记:有人说,用新的内核,更简单,随便配置下,就能使用,完全学不到东西。
现在linux 内核正向, 设备树方面转,是大趋势,老内核肯定是学不到。