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

我正在尝试为通过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消息。

什么是工作

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

什么不工作

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

我已经尝试的两件事情是:

  • 在bcm2708_common.dtsi的i2c0和i2c1下面添加设备。

  • 将设备添加到b​​cm2708_common.dtsi中的clocks {}部分,然后从i2c0和i2c1的clocks属性中引用我的新时钟。

据我所知,我的驱动程序永远不会被加载或使用。 这是基于这样一个事实,即我没有看到我的debugging消息(来自我的* _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. 如何在Raspberry PI设备树中添加一个任意的新I2C器件?

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

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: 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尝试#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>; }; }; }; 

更新:引导后的设备树(来自#1)

这是启动后直播系统中设备树的一部分。 这是由于将时钟添加到dts中的时钟部分,然后在i2c0和i2c1时钟属性中引用时钟。 这是从运行dtc -I fs /proc/device-tree 。 (整棵树超过了post的限制)。

它看起来像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中删除了所有无关的testing代码。 在这一点上,我只是想这样做:

 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) 

我不知道如何解释一个“modalias失败”。

原帖中的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消息。