Hello World для обычного драйвера рамок часов на малине Pi 3

Я пытаюсь написать общий драйвер рамок часов для часов, которые я подключил к моей малиновой PI 3 через I2C. ПРИМЕЧАНИЕ. Я очень новичок в Linux и программировании ядра.

Обновление: УСПЕХ!

Код ниже работает для драйвера Hello World, и единственным изменением, которое я должен был внести в дерево устройств, чтобы загрузить мой драйвер, было добавление дочернего элемента i2c1 node (в arch/arm/boot/DTS/bcm2708_common.dts):

i2c1: [email protected] {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
        myclock: [email protected] {
                #clock-cells = <0>;
                compatible = "dbc,myclock";
                reg = <0x6a>;
                clock-frequency = <75000000>;
        };
};

С этим я теперь вижу сообщения printk, которые я ожидал увидеть в dmesg.

Что работает

  • Оценочная плата с часами подключается к PI через последовательную шину. Проверено с помощью i2cdetect/i2cdump. Устройство I2C - это @подчиненный адрес 0x6a.
  • Я перекрестно скомпилировал свою версию ядра Raspberry PI 3 (4.4.16-v7) из Ubuntu (работает на VirtualBox) и успешно развернул его в PI. Проверено с помощью uname -a (проверка информации EXTRAVERSION, которую я добавил в Makefile).
  • Я создал драйвер устройства Hello World, который можно загрузить с помощью insmod.
  • Я создал драйвер устройства Hello World, который можно добавить в дерево устройств (в bcm2708_common.dtsi и bcm2710-rpi-3-b.dts). Я могу развернуть новое дерево устройств в PI. Проверено, что драйвер устройства загружается с помощью операторов printk (просматривается с помощью dmesg после загрузки PI), а также проверяет lsmod после загрузки.
  • Я создал начальную попытку для обычного драйвера рамки Hello World в драйверах /clk (clk-myclock.c). Этот драйвер в конечном итоге будет использоваться для изменения скорости на часах, поэтому я реализую recalc_rate, round_rate и set_rate в структуре clk_ops. Я добавил этот драйвер к файлам драйверов /clk/Makefile и добавил опцию config для драйверов /clk/Kconfig. Я использовал menuconfig для включения опции, и я проверил, что модуль строится (clk-myconfig.o создается сборкой).

Что не работает

Теперь я пытаюсь добавить мой драйвер Hello World ccf в дерево устройств на малине Pi. Я не понимаю дерево устройства достаточно хорошо, чтобы знать, где его добавить (или даже если Ccf фактически поддерживается на PI).

Две основные вещи, которые я пробовал:

  • Добавление устройства в качестве дочернего элемента под i2c0 и i2c1 в bcm2708_common.dtsi.

  • Добавление устройства в раздел часов {} в bcm2708_common.dtsi, а затем обращение к моим новым часам из свойства clocks i2c0 и i2c1.

Насколько я могу судить, мой драйвер никогда не загружается и не используется. Это связано с тем, что я не вижу своего отладочного сообщения (из вызова printk в верхней части моей функции * _probe), и я не вижу, что мой модуль загружен в lsmod после загрузки.

Глядя на файл arch/arm/boot/dts/zynq-zc702.dts, кажется, что на плате есть i2cswitch (compatible = "nxp, pca9548" ) в качестве дочернего элемента устройства i2c0, а ребенок i2c0 в это, а затем общий драйвер рамок часов ( "silabs, si570" ) под ним. Я понятия не имею, какая соответствующая hw-архитектура может быть в PI малины (или где посмотреть, чтобы понять это), чтобы поддерживать произвольные новые устройства I2C в цепочке I2C.

Вопросы

  • Поддерживается ли общая структура часов в PI?

  • Как вы добавляете произвольное новое устройство I2C в дерево устройств PIP-малины?

  • Использует printk в функции пробника и lsmod для проверки, достаточно ли загружен мой драйвер для определения того, было ли обнаружено мое устройство в дереве устройств, и мой драйвер был связан с ним?

Код

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 Попытка # 1: добавление к часам

i2c0: [email protected] {
    compatible = "brcm,bcm2708-i2c";
    reg = <0x7e205000 0x1000>;
    interrupts = <2 21>;
    clocks = <&clk_core &clk_myclock>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
};
i2c1: [email protected] {
    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: [email protected] {
        compatible = "fixed-clock";
        reg = <0>;
        #clock-cells = <0>;
        clock-output-names = "core";
        clock-frequency = <250000000>;
    };

    ...   
    clk_myclock: [email protected] {
        #clock-cells = <0>;
        reg = <0x6a>;
        compatible = "dbc,myclock";
        clock-frequency = <75000000>;
    };
};

bcm2708_common.dtsi Попытка # 2: добавление в качестве дочернего элемента i2c

i2c0: [email protected] {
    compatible = "brcm,bcm2708-i2c";
    reg = <0x7e205000 0x1000>;
    interrupts = <2 21>;
    clocks = <&clk_core &clk_myclock>;
    #address-cells = <1>;
    #size-cells = <0>;
    status = "disabled";
    [email protected] {
        #address-cells = <1>;
        #size-cells = <0>;
        reg = <0>;
        myclock: [email protected] {
            #clock-cells = <0>;
            compatible = "dbc,myclock";
            reg = <0x6a>;
            clock-frequency = <75000000>;
        };
    };
};

UPDATE: дерево устройств после загрузки (из # 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>;
        ...

        [email protected] {
            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";
        };
        [email protected] {
            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";
        };
        [email protected] {
            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>;
        };


        [email protected] {
            ...
            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>;

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

    ...
    __symbols__ {
        ...
        i2c0 = "/soc/[email protected]";
        i2c1 = "/soc/[email protected]";
        i2c2 = "/soc/[email protected]";
        ...
    };

    aliases {
        ...
        i2c0 = "/soc/[email protected]";
        i2c1 = "/soc/[email protected]";
        i2c2 = "/soc/[email protected]";
        ...
        i2c_arm = "/soc/[email protected]";
    };

    __overrides__ {
        ...
        i2c0 = "", "", "", "(status";
        i2c1 = "", "", "", ")status";
        i2c_arm = "", "", "", ")status";
        ...
    };
};

UPDATE: получена ошибка при загрузке

Теперь, когда я знаю, что имею дело с i2c1, я удалил все посторонние тестовые коды из dts. На данный момент я просто пытаюсь это сделать:

i2c1: [email protected] {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
    [email protected] {
            #address-cells = <1>;
            #size-cells = <0>;
            reg = <0>;
            myclock: [email protected] {
                    #clock-cells = <0>;
                    compatible = "dbc,myclock";
                    reg = <0x6a>;
                    clock-frequency = <75000000>;
            };
    };
};

Теперь я получаю следующую ошибку в dmesg:

[    5.071515] bcm2708_i2c_probe
[    5.086179] i2c i2c-1: of_i2c: modalias failure on /soc/[email protected]/[email protected]
[    5.086224] bcm2708_i2c 3f804000.i2c: BSC1 Controller at 0x3f804000 (irq 83) (baudrate 100000)

Я не уверен, как интерпретировать "отказ модальности".

Ответ 1

Код C в исходном сообщении работает для драйвера Hello World, и единственным изменением, которое я действительно должен был внести в дерево устройств, чтобы загрузить мой драйвер, было добавление дочернего элемента i2c1 node (в arch/arm/boot/dts/bcm2708_common.dts):

i2c1: [email protected] {
        compatible = "brcm,bcm2708-i2c";
        reg = <0x7e804000 0x1000>;
        interrupts = <2 21>;
        clocks = <&clk_core>;
        #address-cells = <1>;
        #size-cells = <0>;
        status = "disabled";
        myclock: [email protected] {
                #clock-cells = <0>;
                compatible = "dbc,myclock";
                reg = <0x6a>;
                clock-frequency = <75000000>;
        };
};

С этим я теперь вижу сообщения printk, которые я ожидал увидеть в dmesg.