Nous voici dans l’écriture proprement dite du driver. Comme expliqué
auparavant, nous allons nous inspirer du driver du ds1374. La stratégie
consiste à copier/coller le code rtc-ds1374.c puis en modifier le code:
$ cp linux-2.6.38.8/drivers/rtc/rtc-ds1374.c
linux-2.6.38.8/drivers/rtc/rtc-mcp7940x.c
Puis chercher/remplacer tous les ds1374 par mcp7940x dans
le fichier rtc-mcp7940x.c nouvellement créé. Ce qui se fait dans vim par la
commande :%s/ds1374/mcp7940x/g
Une fois cela fait on va pouvoir commencer à adapter notre driver.
Notre chip mcp7940x est un composant qui se connecte sur le bus I²C pour
«exporter» une interface d’horloge nommé RTC. Le rôle du driver est donc de
faire le liens entre le bus i²c et la RTC. On peut résumer cela visuellement
avec l’image suivante
On commence toujours la lecture d’un driver par la fin, passons sur le nom
de l’auteur et la licence. Et intéressons nous à la connexion au bus.
On charge le driver en le connectant au bus i²c, pour se faire, on utilise
la structure i2c_driver que l’on ajoute sur le bus au moment du
chargement du driver:
static int __init mcp7940x_init(void)
{
return i2c_add_driver(&mcp7940x_driver);
}
Structure que l’on supprime au déchargement du driver bien évidemment:
static void __exit mcp7940x_exit(void)
{
i2c_del_driver(&mcp7940x_driver);
}
Cette structure déclare un certain nombre de fonctions et de structures
propres au mcp7940x:
static struct i2c_driver mcp7940x_driver = {
.driver = {
.name = "rtc-mcp7940x",
.owner = THIS_MODULE,
},
.probe = mcp7940x_probe,
.suspend = mcp7940x_suspend,
.resume = mcp7940x_resume,
.remove = __devexit_p(mcp7940x_remove),
.id_table = mcp7940x_id,
};
Ce qui nous intéresse particulièrement pour l’instant c’est le
probe et le driver.name. En effet c’est tout
simplement le point d’entrée de notre driver. Une fois le driver chargé dans
le kernel, Linux observe le bus i²c et guette le nom des périphériques (struct
devices) que l’on charge dessus. Dès qu’un périphériques avec le nom
« rtc-mcp7940x » se pointe, le kernel appel la fonction de probe
mcp7940x_probe.
La structure de description du périphérique présent physiquement sur le bus
est quelques chose qui doit être chargé dans le noyau. En règle générale les
périphériques du bus i²c ne sont pas plug&play et sont donc présent sur la
carte électronique dès le démarrage du kernel.
C’est pour cette raison qu’on a l’habitude de charger les devices dans le
fichier dit de «plate-forme». Dans le cas de l’apf51dev, ce fichier de
plate-forme se nomme apf51dev-baseboard.c et se trouve dans le
répertoire
buildroot/output/build/linux-2.6.38.8/arch/arm/mach-mx5/
Dans ce fichier, le device se déclare simplement avec sont adresse sur le
bus, et son nom bien sur:
static struct i2c_board_info apf51dev_i2c2_devices[] __initdata = {
{
I2C_BOARD_INFO("mcp79400", 0x6f),
},
};
Revenons à notre probe
static int mcp7940x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct mcp7940x *mcp7940x;
int ret;
mcp7940x = kzalloc(sizeof(struct mcp7940x), GFP_KERNEL);
if (!mcp7940x)
return -ENOMEM;
C’est la fonction d’initialisation de notre driver. C’est dans cette
fonction que l’on va démarrer le mcp7940x et allouer la mémoire pour la
structure mcp7940x. C’est aussi dans cette fonction que l’on va rattacher
notre driver à l’interface RTC.
L’interface Linux pour la RTC est extrêmement simpliste dans notre cas. En
effet on cherche juste à lire l’heure et la date dans le composant et à
l’écrire (Le mcp7940x à plein d’autre fonctionnalités mais mon besoin n’est
que l’heure). Nous n’aurons donc que les fonctions read_time et
set_time à écrire dans la structure rtc_class_ops
du driver:
static const struct rtc_class_ops mcp7940x_rtc_ops = {
.read_time = mcp7940x_read_time,
.set_time = mcp7940x_set_time,
};
Structure que l’on enregistre à la fin de la fonction probe:
mcp7940x->rtc = rtc_device_register(client->name, &client->dev,
&mcp7940x_rtc_ops, THIS_MODULE);
if (IS_ERR(mcp7940x->rtc)) {
ret = PTR_ERR(mcp7940x->rtc);
dev_err(&client->dev, "unable to register the class device\n");
goto out_free;
}
L’écriture de la fonction read_time se résume ensuite à lire
les valeurs des registres du composant au moyen de la fonction
i2c_smbus_read_byte_data() et de renseigner la structure
rtc_time passée en paramètre.
Et pour la fonction set_time on fait l’inverse. On récupère
les valeurs se trouvant dans la structure rtc_time
passée en paramètre et on écrit les valeurs dans le composant au moyen de la
fonction i2c_smbus_write_byte_data().
Et voila !
En réalité le driver n’est que partiellement écrit vu que le composant
dispose de bien plus de fonctionnalités que simplement lire/écrire l’heure.
Le driver pourrait notament être étendu pour gérer les deux alarmes, la
gestion du signal de clock de sortie et surtout la SRAM, qui permet de stocker
des variables quand l’appareil est éteint.
Et non ça n’est pas terminé ! Il reste encore à publier ce driver pour
armadeus, et plus (mainline kernel) si affinité !
Maintenant publions le tout
Maintenant que nous avons écrit notre driver, l’objectif va être de le publier sur le git Armadeus. Pour cela il faut que nous finalisions le patch que nous avions commencé dans la partie 2 de cet article. Si tout a bien été fait dans la partie 2 il n’y a plus qu’à rafraichir le patch quilt:
$ cd buildroot/output/build/linux-2.6.38.8/
$ quilt refresh
Refreshed patch 450-armadeus-add_mcp7940x_rtc_driver.patch
Puis modifier l'entête du patch pour y mettre son nom (pour qu'on connaisse le coupable ;):
$ vim patches/450-armadeus-add_mcp7940x_rtc_driver.patch
et
Add mcp79400 Linux driver
Signed-off-by: Fabien Marteau
---
Index: linux-2.6.38.8/drivers/rtc/Kconfig
...
Notre patch est prêt, on peut maintenant le proposer à la communauté Armadeus en le postant sur la liste de diffusion
armadeus-forum@lists.sourceforge.net.
Bon j'avoue, vu que j'ai les droits sur le git je l'ai commité directement 😉 Mais, pour ceux qui n'ont pas les droits, c'est la procédure qu'il faudrait respecter. Du coup pour voir le code complet du driver c'est par là
Nous voici dans l’écriture proprement dite du driver. Comme expliqué auparavant, nous allons nous inspirer du driver du ds1374. La stratégie consiste à copier/coller le code rtc-ds1374.c puis en modifier...