Bonjour World pour pilote d’horloge commune sur Raspberry Pi 3

J’essaie d’écrire un pilote de cadre d’horloge commun pour une horloge que j’ai attaché à mon Raspberry PI 3 via I2C. NOTE: Je suis très nouveau sur la programmation Linux et le kernel.

Mise à jour: SUCCESS!

Le code ci-dessous fonctionne pour un pilote Hello World, et le seul changement que j’ai dû apporter à l’arborescence de périphériques pour que mon pilote se charge était d’append un enfant du nœud i2c1 (dans arch / arm / boot / dts / bcm2708_common.dts). ):

i2c1: i2c@7e804000 { compatible = "brcm,bcm2708-i2c"; reg = ; interrupts = ; clocks = ; #address-cells = ; #size-cells = ; status = "disabled"; myclock: clock-generator@6a { #clock-cells = ; compatible = "dbc,myclock"; reg = ; clock-frequency = ; }; }; 

Avec cela en place, je vois maintenant les messages printk que je m’attendais à voir dans dmesg.

Ce qui fonctionne

  • La carte d’évaluation avec l’horloge est connectée au PI via le bus série. Vérifié avec i2cdetect / i2cdump. Le périphérique I2C est l’adresse @ esclave 0x6a.
  • J’ai compilé de manière croisée ma propre version du kernel Raspberry PI 3 (4.4.16-v7) à partir d’Ubuntu (exécuté sur VirtualBox) et l’ai déployé avec succès sur le PI. Vérifié avec uname -a (vérification des informations EXTRAVERSION que j’ai ajoutées au Makefile).
  • J’ai créé un pilote de périphérique Hello World que je peux charger avec insmod.
  • J’ai créé un pilote de périphérique Hello World que je peux append à l’arborescence des périphériques (dans bcm2708_common.dtsi et bcm2710-rpi-3-b.dts). Je peux déployer la nouvelle arborescence de périphérique sur le PI. Vérifié que le pilote de périphérique est en train de charger à l’aide des instructions printk (affichées avec dmesg après le démarrage de PI) et de vérifier lsmod après le démarrage.
  • J’ai créé une première tentative de pilote d’horloge commune Hello World dans drivers / clk (clk-myclock.c). Ce pilote sera finalement utilisé pour changer le taux de l’horloge, donc j’implémente recalc_rate, round_rate et set_rate dans la structure clk_ops. J’ai ajouté ce pilote à drivers / clk / Makefile et ajouté une option de configuration à drivers / clk / Kconfig. J’ai utilisé menuconfig pour activer cette option et j’ai vérifié que le module est en cours de construction (clk-myconfig.o est créé par la construction).

Ce qui ne fonctionne pas

J’essaie maintenant d’append mon pilote ccf Hello World à l’arborescence du Raspberry Pi. Je ne comprends pas assez bien l’arborescence des périphériques pour savoir où l’append (ou même si le ccf est réellement pris en charge sur le PI).

Les deux choses principales que j’ai essayées sont:

  • Ajout de l’appareil en tant qu’enfant sous i2c0 et i2c1 dans bcm2708_common.dtsi.

  • Ajout du périphérique dans la section horloges {} de bcm2708_common.dtsi, puis référence à ma nouvelle horloge à partir de la propriété horloges de i2c0 et i2c1.

Autant que je sache, mon pilote n’est jamais chargé ou utilisé. Ceci est basé sur le fait que je ne vois pas mon message de débogage (à partir d’un appel printk en haut de ma fonction * _probe), et je ne vois pas mon module chargé dans lsmod après le démarrage.

En regardant le fichier arch / arm / boot / dts / zynq-zc702.dts, il semble que la carte ait un i2cswitch (compatible = “nxp, pca9548”) en tant qu’enfant du périphérique i2c0 et un enfant i2c0 en dessous, et puis un pilote de cadre d’horloge commun (“silabs, si570”) sous là. Je n’ai aucune idée de ce que l’architecture hw correspondante pourrait être sur le Raspberry PI (ou où chercher pour trouver cela) pour prendre en charge de nouveaux périphériques I2C arbitraires dans la chaîne I2C.

Des questions

  1. Le cadre d’horloge commun est-il pris en charge sur le PI?

  2. Comment ajoutez-vous un nouvel appareil I2C arbitraire à l’arborescence de périphériques Raspberry PI?

  3. Est-ce que l’utilisation de printk dans la fonction probe et de lsmod permet de vérifier si mon pilote est suffisamment chargé pour déterminer si mon périphérique a été détecté ou non dans l’arborescence et si mon pilote y a été associé?

Code

clk-myclock.c

 #include  #include  #include  #include  #include  #include  #include  #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 Tentative n ° 1: append des horloges

 i2c0: i2c@7e205000 { compatible = "brcm,bcm2708-i2c"; reg = ; interrupts = ; clocks = ; #address-cells = ; #size-cells = ; status = "disabled"; }; i2c1: i2c@7e804000 { compatible = "brcm,bcm2708-i2c"; reg = ; interrupts = ; clocks = ; #address-cells = ; #size-cells = ; status = "disabled"; }; clocks: clocks { clk_core: clock@0 { compatible = "fixed-clock"; reg = ; #clock-cells = ; clock-output-names = "core"; clock-frequency = ; }; ... clk_myclock: clock@7 { #clock-cells = ; reg = ; compatible = "dbc,myclock"; clock-frequency = ; }; }; 

bcm2708_common.dtsi Tentative n ° 2: ajout d’un enfant d’i2c

 i2c0: i2c@7e205000 { compatible = "brcm,bcm2708-i2c"; reg = ; interrupts = ; clocks = ; #address-cells = ; #size-cells = ; status = "disabled"; i2c@0 { #address-cells = ; #size-cells = ; reg = ; myclock: clock-generator@6a { #clock-cells = ; compatible = "dbc,myclock"; reg = ; clock-frequency = ; }; }; }; 

UPDATE: arborescence de périphérique après démarrage (à partir de # 1)

Ceci est une partie de l’arborescence du système en direct après le démarrage. Il s’agit d’append les horloges à la section horloge du fichier dts, puis de faire référence à l’horloge dans les propriétés des horloges i2c0 et i2c1. Cela provient de l’exécution de dtc -I fs /proc/device-tree . (L’arbre entier dépasse les limites du poteau).

Il semble que i2c0 soit désactivé, mais que i2c1 est activé.

 /dts-v1/; / { model = "Raspberry Pi 3 Model B Rev 1.2"; compatible = "brcm,bcm2710", "brcm,bcm2709"; memreserve = ; #address-cells = ; #size-cells = ; interrupt-parent = ; soc { compatible = "simple-bus"; ranges = ; #address-cells = ; phandle = ; #size-cells = ; ... i2c@7e205000 { reg = ; interrupts = ; pinctrl-0 = ; compatible = "brcm,bcm2708-i2c"; clock-frequency = ; clocks = ; status = "disabled"; #address-cells = ; phandle = ; #size-cells = ; pinctrl-names = "default"; }; i2c@7e804000 { reg = ; interrupts = ; pinctrl-0 = ; compatible = "brcm,bcm2708-i2c"; clock-frequency = ; clocks = ; status = "okay"; #address-cells = ; phandle = ; #size-cells = ; pinctrl-names = "default"; }; i2c@7e805000 { reg = ; interrupts = ; compatible = "brcm,bcm2708-i2c"; clock-frequency = ; clocks = ; status = "disabled"; #address-cells = ; phandle = ; #size-cells = ; }; gpio@7e200000 { ... i2c0 { phandle = ; brcm,function = ; brcm,pins = ; }; i2c1 { phandle = ; brcm,function = ; brcm,pins = ; }; ... }; }; ... clocks { compatible = "simple-bus"; #address-cells = ; phandle = ; #size-cells = ; clock@0 { reg = ; #clock-cells = ; compatible = "fixed-clock"; clock-frequency = ; clock-output-names = "core"; phandle = ; }; ... clock@7 { reg = ; #clock-cells = ; compatible = "dbc,myclock"; clock-frequency = ; phandle = ; }; }; ... __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"; ... }; }; 

MISE À JOUR: Erreur reçue au démarrage

Maintenant que je sais que je traite avec i2c1, j’ai retiré tout le code de test de dts. À ce stade, j’essaie juste ceci:

 i2c1: i2c@7e804000 { compatible = "brcm,bcm2708-i2c"; reg = ; interrupts = ; clocks = ; #address-cells = ; #size-cells = ; status = "disabled"; i2c@0 { #address-cells = ; #size-cells = ; reg = ; myclock: clock-generator@6a { #clock-cells = ; compatible = "dbc,myclock"; reg = ; clock-frequency = ; }; }; }; 

Maintenant, je reçois l’erreur suivante dans 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) 

Je ne sais pas comment interpréter un “échec de modalias”.

Le code C dans la publication d’origine fonctionne pour un pilote Hello World, et le seul changement que j’ai dû apporter à l’arborescence pour charger mon pilote a été d’append un enfant du noeud i2c1 (dans 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>; }; }; 

Avec cela en place, je vois maintenant les messages printk que je m’attendais à voir dans dmesg.