Raspberry Pi 3上的常见时钟框架驱动程序Hello World

Dav*_*ter 11 c linux linux-kernel device-tree

我正在尝试为我通过I2C连接到Raspberry PI 3的时钟编写一个通用时钟框架驱动程序.注意:我对Linux和内核编程都很陌生.

更新:成功!

下面的代码适用于Hello World驱动程序,我必须对设备树进行的唯一更改才能让我的驱动程序加载是添加i2c1节点的子节点(在arch/arm/boot/dts/bcm2708_common.dts中) ):

i2c1: i2c@7e804000 {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
        myclock: clock-generator@6a {
                #clock-cells = <0>;
                compatible = "dbc,myclock";
                reg = <0x6a>;
                clock-frequency = <75000000>;
        };
};
Run Code Online (Sandbox Code Playgroud)

有了这个,我现在看到我希望在dmesg中看到的printk消息.

什么工作

  • 带时钟的评估板通过串行总线连接到PI.已通过i2cdetect/i2cdump验证.I2C器件是@从地址0x6a.
  • 我从Ubuntu(在VirtualBox上运行)交叉编译了我自己的Raspberry PI 3内核版本(4.4.16-v7)并成功将其部署到PI.用uname -a验证(检查我添加到Makefile的EXTRAVERSION信息).
  • 我创建了一个Hello World设备驱动程序,我可以使用insmod加载.
  • 我创建了一个Hello World设备驱动程序,我可以将其添加到设备树中(在bcm2708_common.dtsi和bcm2710-rpi-3-b.dts中).我可以将新设备树部署到PI.已验证设备驱动程序正在使用printk语句加载(在PI引导后使用dmesg查看)以及在引导后检查lsmod.
  • 我在drivers/clk(clk-myclock.c)中创建了一个Hello World公共时钟框架驱动程序的初始尝试.此驱动程序最终将用于更改时钟速率,因此我在clk_ops结构中实现recalc_rate,round_rate和set_rate.我将此驱动程序添加到drivers/clk/Makefile,并为drivers/clk/Kconfig添加了一个配置选项.我使用menuconfig来启用该选项,并且我已经验证了正在构建模块(clk-myconfig.o是由构建创建的).

什么不起作用

我现在正在尝试将我的Hello World ccf驱动程序添加到Raspberry Pi上的设备树中.我不太了解设备树,无法知道添加它的位置(甚至PI是否实际支持ccf).

我尝试过的两件主要事情是:

  • 将设备作为子项添加到bcm2708_common.dtsi中的i2c0和i2c1下.

  • 在bcm2708_common.dtsi的clocks {}部分添加设备,然后从i2c0和i2c1的clocks属性引用我的新时钟.

据我所知,我的驱动程序永远不会被加载或使用.这是因为我没有看到我的调试消息(来自我的*_probe函数顶部的printk调用),并且在启动后我没有看到我的模块在lsmod中加载.

看看arch/arm/boot/dts/zynq-zc702.dts文件,看来电路板有一个i2cswitch(compatible ="nxp,pca9548")作为i2c0设备的子节点,以及一个i2c0子节点,并且然后是一个常见的时钟框架驱动程序("silabs,si570").我不知道Raspberry PI上相应的hw架构可能是什么(或者在哪里可以看出来)以支持I2C链中的任意新I2C设备.

问题

  1. PI上是否支持通用时钟框架?

  2. 如何将任意新的I2C设备添加到Raspberry PI设备树?

  3. 在probe函数和lsmod中使用printk来检查我的驱动程序是否已加载足以确定我的设备是否已在设备树中找到并且我的驱动程序已与其关联?

CLK-myclock.c

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>

#define DRV_NAME        "myclock"

struct clk_myclock {
    struct clk_hw hw;
    struct regmap *regmap;
    unsigned int div_offset;
    u64 max_freq;
    u64 fxtal;
    unsigned int n1;
    unsigned int hs_div;
    u64 rfreq;
    u64 frequency;
    struct i2c_client *i2c_client;
};
#define to_clk_myclock(_hw) container_of(_hw, struct clk_myclock, hw)

enum clk_myclock_variant {
    myclock
};

static int myclock_set_rate(struct clk_hw *hw, unsigned long rate,
        unsigned long parent_rate)
{
    struct clk_myclock *data = to_clk_myclock(hw);
    data->frequency = rate;
    return 0;
}

static unsigned long myclock_recalc_rate(struct clk_hw *hw,
        unsigned long parent_rate)
{
    u64 rate;
    struct clk_myclock *data = to_clk_myclock(hw);
    rate = data->fxtal;
    return rate;
}

static long myclock_round_rate(struct clk_hw *hw, unsigned long rate,
        unsigned long *parent_rate)
{
    if (!rate)
        return 0;
    return rate;
}

static const struct clk_ops myclock_clk_ops = {
    .recalc_rate = myclock_recalc_rate,
    .round_rate = myclock_round_rate,
    .set_rate = myclock_set_rate,
};

static bool myclock_regmap_is_volatile(struct device *dev, unsigned int reg)
{
    return false;
}

static bool myclock_regmap_is_writeable(struct device *dev, unsigned int reg)
{
    return true;
}

static const struct regmap_config myclock_regmap_config = {
    .reg_bits = 8,
    .val_bits = 8,
    .cache_type = REGCACHE_RBTREE,
    .max_register = 0xff,
    .writeable_reg = myclock_regmap_is_writeable,
    .volatile_reg = myclock_regmap_is_volatile,
};


static int myclock_probe(struct i2c_client *client,
        const struct i2c_device_id *id)
{
    struct clk_myclock *data;
    struct clk_init_data init;
    struct clk *clk;
    u32 initial_fout;
    int err;

    printk(KERN_ALERT "myclock_probe\n");
    data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
    if (!data)
        return -ENOMEM;

    init.ops = &myclock_clk_ops;
    init.flags = CLK_IS_ROOT;
    init.num_parents = 0;
    data->hw.init = &init;
    data->i2c_client = client;

    init.name = "myclock";

    data->regmap = devm_regmap_init_i2c(client, &myclock_regmap_config);
    if (IS_ERR(data->regmap)) {
        dev_err(&client->dev, "failed to allocate register map\n");
        return PTR_ERR(data->regmap);
    }

    i2c_set_clientdata(client, data);

    clk = devm_clk_register(&client->dev, &data->hw);
    if (IS_ERR(clk)) {
        dev_err(&client->dev, "clock registration failed\n");
        return PTR_ERR(clk);
    }
    err = of_clk_add_provider(client->dev.of_node, of_clk_src_simple_get,
            clk);
    if (err) {
        dev_err(&client->dev, "unable to add clk provider\n");
        return err;
    }

    /* Read the requested initial output frequency from device tree */
    if (!of_property_read_u32(client->dev.of_node, "clock-frequency",
                &initial_fout)) {
        dev_info(&client->dev, "initial output frequency: %u\n", initial_fout);
    }

    /* Display a message indicating that we've successfully registered */
    dev_info(&client->dev, "registered, current frequency %llu Hz\n",
            data->frequency);

    return 0;
}

static int myclock_remove(struct i2c_client *client)
{
   printk(KERN_ALERT "myclock_remove\n");
   of_clk_del_provider(client->dev.of_node);
   return 0;
}

static const struct i2c_device_id myclock_id[] = {
    { "myclock", myclock },
    { }
};

MODULE_DEVICE_TABLE(i2c, myclock_id);

static const struct of_device_id myclock_of_match[] = {
        { .compatible = "dbc,myclock" },
        {},
};

MODULE_DEVICE_TABLE(of, myclock_of_match);

static struct i2c_driver myclock_driver = {
    .driver = {
        .name = DRV_NAME,
        .of_match_table = myclock_of_match,
    },
    .probe      = myclock_probe,
    .remove     = myclock_remove,
    .id_table   = myclock_id,
};

module_i2c_driver(myclock_driver);

MODULE_DESCRIPTION("Hello World Common clock framework driver");
MODULE_AUTHOR("David Cater");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);
Run Code Online (Sandbox Code Playgroud)

bcm2708_common.dtsi尝试#1:添加到时钟

i2c0: i2c@7e205000 {
    compatible = "brcm,bcm2708-i2c";
    reg = <0x7e205000 0x1000>;
    interrupts = <2 21>;
    clocks = <&clk_core &clk_myclock>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};
i2c1: i2c@7e804000 {
    compatible = "brcm,bcm2708-i2c";
    reg = <0x7e804000 0x1000>;
    interrupts = <2 21>;
    clocks = <&clk_core &clk_myclock>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};
clocks: clocks {
    clk_core: clock@0 {
        compatible = "fixed-clock";
        reg = <0>;
        #clock-cells = <0>;
        clock-output-names = "core";
        clock-frequency = <250000000>;
    };

    ...   
    clk_myclock: clock@7 {
        #clock-cells = <0>;
        reg = <0x6a>;
        compatible = "dbc,myclock";
        clock-frequency = <75000000>;
    };
};
Run Code Online (Sandbox Code Playgroud)

bcm2708_common.dtsi尝试#2:添加为i2c的子级

i2c0: i2c@7e205000 {
    compatible = "brcm,bcm2708-i2c";
    reg = <0x7e205000 0x1000>;
    interrupts = <2 21>;
    clocks = <&clk_core &clk_myclock>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
    i2c@0 {
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0>;
        myclock: clock-generator@6a {
            #clock-cells = <0>;
            compatible = "dbc,myclock";
            reg = <0x6a>;
            clock-frequency = <75000000>;
        };
    };
};
Run Code Online (Sandbox Code Playgroud)

更新:启动后的设备树(来自#1)

这是启动后来自实时系统的设备树的一部分.这是通过将时钟添加到dts中的时钟部分,然后引用i2c0和i2c1时钟属性中的时钟.这是从跑步dtc -I fs /proc/device-tree.(整棵树超出了帖子的限制).

看起来i2c0被禁用,但i2c1已启用.

/dts-v1/;

/ {
    model = "Raspberry Pi 3 Model B Rev 1.2";
    compatible = "brcm,bcm2710", "brcm,bcm2709";
    memreserve = <0x3b000000 0x4000000>;
    #address-cells = <0x1>;
    #size-cells = <0x1>;
    interrupt-parent = <0x1>;

    soc {
        compatible = "simple-bus";
        ranges = <0x7e000000 0x3f000000 0x1000000 0x40000000 0x40000000 0x40000>;
        #address-cells = <0x1>;
        phandle = <0x30>;
        #size-cells = <0x1>;
        ...

        i2c@7e205000 {
            reg = <0x7e205000 0x1000>;
            interrupts = <0x2 0x15>;
            pinctrl-0 = <0x10>;
            compatible = "brcm,bcm2708-i2c";
            clock-frequency = <0x186a0>;
            clocks = <0x8 0xf>;
            status = "disabled";
            #address-cells = <0x1>;
            phandle = <0x28>;
            #size-cells = <0x0>;
            pinctrl-names = "default";
        };
        i2c@7e804000 {
            reg = <0x7e804000 0x1000>;
            interrupts = <0x2 0x15>;
            pinctrl-0 = <0x18>;
            compatible = "brcm,bcm2708-i2c";
            clock-frequency = <0x186a0>;
            clocks = <0x8 0xf>;
            status = "okay";
            #address-cells = <0x1>;
            phandle = <0x29>;
            #size-cells = <0x0>;
            pinctrl-names = "default";
        };
        i2c@7e805000 {
            reg = <0x7e805000 0x1000>;
            interrupts = <0x2 0x15>;
            compatible = "brcm,bcm2708-i2c";
            clock-frequency = <0x186a0>;
            clocks = <0x8>;
            status = "disabled";
            #address-cells = <0x1>;
            phandle = <0x19>;
            #size-cells = <0x0>;
        };


        gpio@7e200000 {
            ...
            i2c0 {
                phandle = <0x10>;
                brcm,function = <0x4>;
                brcm,pins = <0x0 0x1>;
            };

            i2c1 {
                phandle = <0x18>;
                brcm,function = <0x4>;
                brcm,pins = <0x2 0x3>;
            };
            ...
        };
    };
    ...
    clocks {
        compatible = "simple-bus";
        #address-cells = <0x1>;
        phandle = <0x45>;
        #size-cells = <0x0>;

        clock@0 {
            reg = <0x0>;
            #clock-cells = <0x0>;
            compatible = "fixed-clock";
            clock-frequency = <0x17d78400>;
            clock-output-names = "core";
            phandle = <0x8>;
        };
        ...
        clock@7 {
            reg = <0x6a>;
            #clock-cells = <0x0>;
            compatible = "dbc,myclock";
            clock-frequency = <0x47868c0>;
            phandle = <0xf>;
        };
    };

    ...
    __symbols__ {
        ...
        i2c0 = "/soc/i2c@7e205000";
        i2c1 = "/soc/i2c@7e804000";
        i2c2 = "/soc/i2c@7e805000";
        ...
    };

    aliases {
        ...
        i2c0 = "/soc/i2c@7e205000";
        i2c1 = "/soc/i2c@7e804000";
        i2c2 = "/soc/i2c@7e805000";
        ...
        i2c_arm = "/soc/i2c@7e804000";
    };

    __overrides__ {
        ...
        i2c0 = "", "", "", "(status";
        i2c1 = "", "", "", ")status";
        i2c_arm = "", "", "", ")status";
        ...
    };
};
Run Code Online (Sandbox Code Playgroud)

更新:启动时出错

现在我知道我正在处理i2c1,我从dts中删除了所有无关的测试代码.在这一点上,我只是想这样做:

i2c1: i2c@7e804000 {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
    i2c@0 {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <0>;
            myclock: clock-generator@6a {
                    #clock-cells = <0>;
                    compatible = "dbc,myclock";
                    reg = <0x6a>;
                    clock-frequency = <75000000>;
            };
    };
};
Run Code Online (Sandbox Code Playgroud)

现在我在dmesg中收到以下错误:

[    5.071515] bcm2708_i2c_probe
[    5.086179] i2c i2c-1: of_i2c: modalias failure on /soc/i2c@7e804000/i2c@0
[    5.086224] bcm2708_i2c 3f804000.i2c: BSC1 Controller at 0x3f804000 (irq 83) (baudrate 100000)
Run Code Online (Sandbox Code Playgroud)

我不确定如何解释"模态失败".

Dav*_*ter 2

原始帖子中的 C 代码适用于 Hello World 驱动程序,为了加载驱动程序,我实际上必须对设备树进行的唯一更改是添加 i2c1 节点的子节点(在 arch/arm/boot/ dts/bcm2708_common.dts):

i2c1: i2c@7e804000 {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
        myclock: clock-generator@6a {
                #clock-cells = <0>;
                compatible = "dbc,myclock";
                reg = <0x6a>;
                clock-frequency = <75000000>;
        };
};
Run Code Online (Sandbox Code Playgroud)

完成此操作后,我现在可以看到我期望在 dmesg 中看到的 printk 消息。