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\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.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\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.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\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 #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("\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     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 內核正向, 設備樹方面轉,是大趨勢,老內核肯定是學不到。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM