我正在尝试为我通过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>;
};
};
有了这个,我现在看到了我希望在dmesg中看到的printk消息。
我现在正在尝试将我的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”)。我不知道相应的hw架构可能在Raspberry PI上(或者在哪里找出来)支持I2C链中任意新的I2C设备。
PI是否支持通用时钟框架?
如何将任意新的I2C设备添加到Raspberry PI设备树中?
在probe函数和lsmod中使用printk来检查我的驱动程序是否已加载足以确定我的设备是否已在设备树中找到并且我的驱动程序已与之关联?
#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);
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>;
};
};
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>;
};
};
};
这是启动后来自实时系统的设备树的一部分。这是通过将时钟添加到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";
...
};
};
现在我知道我正在处理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>;
};
};
};
现在我在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)
我不确定如何解释“模态失败”。
答案 0 :(得分:1)
原始帖子中的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>;
};
};
有了这个,我现在看到了我希望在dmesg中看到的printk消息。