Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hello World for common clock framework driver on Raspberry Pi 3

I am trying to write a common clock framework driver for a clock that I have attached to my Raspberry PI 3 via I2C. NOTE: I am very new to both Linux and kernel programming.

Update: SUCCESS!

The code below works for a Hello World driver, and the sole change I had to make to the device tree to get my driver to load was to add a child of the i2c1 node (in 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>;
        };
};

With that in place, I now see the printk messages I expected to see in dmesg.

What is working

  • The evaluation board with the clock is hooked up to the PI via the serial bus. Verified with i2cdetect/i2cdump. The I2C device is @ slave address 0x6a.
  • I have cross-compiled my own version of the Raspberry PI 3 kernel (4.4.16-v7) from Ubuntu (running on VirtualBox) and successfully deployed it to the PI. Verified with uname -a (checking EXTRAVERSION info I added to the Makefile).
  • I have created a Hello World device driver that I can load with insmod.
  • I have created a Hello World device driver that I can add to the device tree (in bcm2708_common.dtsi and bcm2710-rpi-3-b.dts). I can deploy the new device tree to the PI. Verified that the device driver is loading using printk statements (viewed using dmesg after PI boot) as well as checking lsmod after boot.
  • I have created an initial attempt at a Hello World common clock framework driver in drivers/clk (clk-myclock.c). This driver will ultimately be used to change the rate on the clock, so I am implementing recalc_rate, round_rate and set_rate in the clk_ops struct. I added this driver to drivers/clk/Makefile and added a config option to drivers/clk/Kconfig. I used menuconfig to enable the option, and I have verified that the module is being built (clk-myconfig.o is created by the build).

What is not working

I am now trying to add my Hello World ccf driver to the device tree on the Raspberry Pi. I don't understand the device tree well enough to know where to add it (or even whether the ccf is actually supported on the PI).

The two main things I have tried are:

  • Adding the device as a child under i2c0 and i2c1 in bcm2708_common.dtsi.

  • Adding the device in the clocks {} section in bcm2708_common.dtsi and then referring to my new clock from the clocks property of i2c0 and i2c1.

As far as I can tell, my driver is never being loaded or used. This is based on the fact that I don't see my debug message (from a printk call at the top of my *_probe function), and I don't see my module loaded in lsmod after booting.

Looking at the arch/arm/boot/dts/zynq-zc702.dts file, it appears that board has an i2cswitch (compatible="nxp,pca9548") as a child of the i2c0 device, and an i2c0 child under that, and then a common clock framework driver ("silabs,si570") under there. I have no idea what the corresponding hw architecture might be on the Raspberry PI (or where to look to figure that out) to support arbitrary new I2C devices in the I2C chain.

Questions

  1. Is the common clock framework supported on the PI?

  2. How do you add an arbitrary new I2C device to the Raspberry PI device tree?

  3. Is using printk in the probe function and lsmod to check to see if my driver is loaded sufficient for determining whether or not my device has been found in the device tree and my driver has been associated with it?

Code

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);

bcm2708_common.dtsi Attempt #1: adding to clocks

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>;
    };
};

bcm2708_common.dtsi Attempt #2: adding as child of 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>;
        };
    };
};

UPDATE: device tree after boot (from #1)

This is a portion of the device tree from the live system after boot. This is from adding the clocks to the clock section in the dts and then referencing the clock in the i2c0 and i2c1 clocks properties. This is from running dtc -I fs /proc/device-tree. (The whole tree exceeds the limits of the post).

It looks like i2c0 is disabled, but i2c1 is enabled.

/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";
        ...
    };
};

UPDATE: Got error on boot

Now that I know I'm dealing with i2c1, I removed all extraneous test code from the dts. At this point I am just trying this:

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>;
            };
    };
};

Now I am getting the following error in 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)

I'm not sure how to interpret a "modalias failure".

like image 558
David Cater Avatar asked Aug 10 '16 22:08

David Cater


1 Answers

The C code in the original post works for a Hello World driver, and the sole change I actually had to make to the device tree to get my driver to load was to add a child of the i2c1 node (in 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>;
        };
};

With that in place, I now see the printk messages I expected to see in dmesg.

like image 75
David Cater Avatar answered Nov 16 '22 17:11

David Cater