zoukankan      html  css  js  c++  java
  • linux4.1.36 解决 SPI 时钟找不到 不生成设备 device

    最初的问题是 编译内核添加了 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 ");
        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.c
    smdk2440_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 cs
    s3c2410-spi: probe of s3c2410-spi.1 failed with error -16

    if (!pdata->set_cs) {
        //因为我没有提供 pin_cs 这里判断是小于0 所以没有判断出来
        if (pdata->pin_cs < 0) {
            dev_err(&pdev->dev, "No chipselect pin ");
            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 ");
            goto err_register;
        }

        //设置操作函数
        hw->set_cs = s3c24xx_spi_gpiocs;
        //设置为输出功能
        gpio_direction_output(pdata->pin_cs, 1);
    } else
        hw->set_cs = pdata->set_cs;

    看来只能自己设置一个选中函数
    devs.c
    static 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 use
    s3c2410-spi s3c2410-spi.1: can't create new device for spi_flash

    经过查找代码在
    spi.c
    int 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 ",
                    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 #135
    Hardware name: SMDK2440

    在 git 上找到了补丁
    https://github.com/raspberrypi/linux/commit/a97c883a16da7e0691a3be5465926c92a8da4da6
    方法是,不过和我的有点不同。
    drivers/spi/spi-dw.c
    devm_kzalloc 换为 kzalloc

    我这里修改
    drivers/spi/spi-s3c24xx.c

    static 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(" spi_clanup ok ");
    }

    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     else
    13     {
    14         /**
    15         SPIMI GPG5 SPICLK GPG7
    16         SPIMO GPG6 nSSSPI GPG2
    17         */
    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 内核正向, 设备树方面转,是大趋势,老内核肯定是学不到。

  • 相关阅读:
    C#常用笔记.cs
    OpenFileDialog 和 FolderBrowserDialog
    C#双缓存.cs
    数据库设计范式
    js 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果
    Study Plan The Sixth Day
    Study Plan The First Day
    Study Plan The Seventh Day
    Study Plan The Second Day
    C#CustomAttribute和泛型约束 应用于经典数据处理适配
  • 原文地址:https://www.cnblogs.com/ningci/p/6607577.html
Copyright © 2011-2022 走看看