Я пытаюсь написать общий драйвер рамок часов для часов, которые я подключил к моей малиновой 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)
Я не уверен, как интерпретировать "отказ модальности".