Habr, bonjour!
Cet article se concentre sur le développement du module du noyau Linux I2C (Inter-Integrated Circuit). Ce qui suit décrit le processus d'implémentation de la structure de base du pilote I2C, dans lequel vous pouvez facilement ajouter l'implémentation des fonctionnalités nécessaires.
Nous décrivons les données d'entrée: bloc I2C pour le nouveau processeur «cùblé» au FPGA, exécutant la version 3.18.19 de Linux et les périphériques (EEPROM AT24C64 et BME280).
Le principe de fonctionnement d'I2C est assez simple, mais si vous avez besoin de rafraĂźchir vos connaissances, vous pouvez lire
ici .
Figure 1. Diagramme temporel des signaux du bus I2CAvant de commencer à développer un pilote, voyons comment les applications de l'espace utilisateur interagissent avec le module du noyau, pour cela:
- Nous mettons en Ćuvre une petite application d'espace utilisateur, dont le but est de lire l'ID de registre I2C unique de l'appareil. Cette Ă©tape vous permettra de comprendre l'interface Ă travers laquelle l'Ă©change se produit entre le module du noyau et l'application utilisateur;
- Familiarisons-nous avec l'option de transmission des messages I2C par le module du noyau;
- Ajoutez le module du noyau à l'assembly et décrivez le matériel des périphériques dans l'arborescence des périphériques;
- Nous implémentons la structure générale (squelette) du pilote I2C avec quelques explications.
Malheureusement, il n'est pas possible de joindre les vraies sources du pilote dĂ©veloppĂ©. De plus, je tiens Ă noter que tous les noms, noms et carte d'enregistrement du contrĂŽleur sont modifiĂ©s. MĂȘme la moitiĂ© des fonctionnalitĂ©s dĂ©veloppĂ©es n'Ă©taient pas incluses dans le squelette du pilote, cependant, la structure du pilote est un bon point de dĂ©part pour le dĂ©veloppement. Des exemples de pilotes I2C peuvent ĂȘtre trouvĂ©s
ici .
PremiÚre étape
Tout d'abord, familiarisons-nous avec l'utilitaire i2cdetect. Le résultat d'i2cdetect est le suivant:
./i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 abcdef 00: â â â â â â â â â â â â â 10: â â â â â â â â â â â â â â â â 20: â â â â â â â â â â â â â â â â 30: â â â â â â â â â â â â â â â â 40: â â â â â â â â â â â â â â â â 50: 50 â â â â â â â â â â â â â â â 60: â â â â â â â â â â â â â â â â 70: â â â â â â â â
L'utilitaire expose séquentiellement le bus d'adresse de périphérique sur l'I2C et, à la réception d'une réponse positive (dans ce cas, la réponse positive est ACK), affiche le numéro d'adresse de périphérique sur le bus dans la console.
Nous allons écrire un petit programme qui lit l'ID unique du capteur de température et afficher le résultat de son travail dans la console. Cela semble trÚs simple:
#include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #define I2C_ADAPTER "/dev/i2c-0" int read_buffer(int fd) { struct i2c_rdwr_ioctl_data data; struct i2c_msg messages[2]; unsigned char write_buf[1] = {0xD0}, read_buf[1] = {0x00}; unsigned char write[200]; /* * .addr - () * .flags - (0 - w, 1 - r) * .len - - / * .buf - */ messages[0].addr = 0x50; messages[0].flags = 0; messages[0].len = 1; messages[0].buf = write_buf; messages[1].addr = 0x50; messages[1].flags = 1; messages[1].len = 1; messages[1].buf = read_buf; data.msgs = messages; data.nmsgs = 2; if (ioctl(fd, I2C_RDWR, &data) < 0) printf("Cant send data!\n"); else printf("ID = 0x%x\n", read_buf[0]); } int main(int argc, char **argv) { int fd; /* * Open I2C file descriptor. */ fd = open(I2C_ADAPTER, O_RDWR); if (fd < 0) { printf("Unable to open i2c file\n"); return 0; } read_buffer(fd); return 0; }
Il devient clair que le module du noyau reçoit des données sous la forme de champs de message i2c_rdwr_ioctl_data. La structure contient des champs tels que i2c_msg et nmsgs, qui sont utilisés pour la transmission:
- .addr - adresses des périphériques;
- .flags - type d'opération (lecture ou écriture);
- .len - la longueur du message actuel;
- .buf- presse-papiers.
DeuxiÚme étape
Maintenant, je ne plonge pas dans les entrailles, je me familiarise avec une version du pilote I2C.
Comme déjà établi, le module noyau reçoit des messages sous forme de structure. Par exemple, considérez l'algorithme du pilote lors de l'exécution d'une opération d'écriture (partie dépendante du matériel):
- Le premier TX FIFO est rempli: l'adresse de l'appareil vient en premier, puis les données restantes sont transmises;
- Le registre d'état d'interruption ISR est effacé et les interruptions dans le registre IER sont activées (dans ce cas, une interruption se produit lorsqu'il n'y a pas de données dans TX FIFO);
- La transmission de données est autorisée et le bit de démarrage est défini sur le bus.
Tous les échanges de données ultérieurs auront lieu dans le gestionnaire d'interruption.
Les pilotes qui fonctionnent sur cet algorithme peuvent ĂȘtre trouvĂ©s
ici . De plus, le contrÎleur peut ne pas avoir FIFO, mais seulement un seul registre de transfert, mais c'est un cas spécial avec une taille FIFO de un.
Ătape trois
Ajoutez le module du noyau à l'assembly et décrivez le matériel des périphériques dans l'arborescence des périphériques:
1. Créez un fichier source dans le répertoire suivant:
cd drivers/i2c/busses/ vim i2c-skel.c :wq
En conséquence, le fichier apparaßt:
drivers/i2c/busses/i2c-skel.c
2. Ajoutez la configuration du pilote Ă
drivers / i2c / busses / Kconfig :
config I2C_SKEL tristate "I2C adapter" help If you say yes to this option, support will be included for the I2C interface.
3. Ajoutez le
pilote drivers / i2c / busses / Makefile Ă l'assembly:
obj-$(CONFIG_I2C_SKEL) += i2c-skel.o
4. Ajoutez une description du bloc I2C à devicetree (* .dts) et prenez également immédiatement en charge le périphérique eeprom:
i2c: i2c@f8f01d00 { compatible = "skel,skel-i2c"; #address-cells = <1>; #size-cells = <0>; reg = <0x43c00000 0x100>; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; clock-names = "skel-i2c"; clocks = <&clkc 38>; clock-frequency = <100000>; 24c64@50 { compatible = "at,24c64"; pagesize = <32>; reg = <0x50>; }; } ;
Les Ă©tapes ci-dessus ne seront pas examinĂ©es en dĂ©tail, mais les lecteurs curieux peuvent jeter un Ćil
ici .
QuatriÚme étape
AprĂšs vous ĂȘtre familiarisĂ© avec le principe du pilote, procĂ©dons Ă l'implĂ©mentation.
Tout d'abord, connectez les fichiers d'en-tĂȘte, dĂ©crivez la carte de registre «virtuelle», ainsi que la prĂ©sentation du pilote I2C.
#include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/io.h> #include <linux/clk.h> #include <linux/interrupt.h> #include <linux/time.h> #include <linux/delay.h> #include <linux/device.h> /* * Registers description. */ #define SKEL_I2C_ID 0x00 /* Core Identifier register */ #define SKEL_I2C_ISR 0x14 /* Interrupt Status Register */ #define SKEL_I2C_ISR_DNE BIT(0) /* One byte transaction done */ #define SKEL_I2C_ISR_ARB BIT(1) /* Arbitration lost */ #define SKEL_I2C_ISR_TXE BIT(2) /* RX FIFO nearly full */ #define SKEL_I2C_ISR_NACK BIT(3) /* No ACK */ #define SKEL_I2C_IER 0x18 /* Interrupt Enable Register */ #define SKEL_I2C_IER_DNE BIT(0) /* Enable DNE IRQ */ #define SKEL_I2C_IER_ARB BIT(1) /* Enable ARB LOSR IRQ */ #define SKEL_I2C_IER_TXE BIT(2) /* Enable TX FIFO EPMTY IRQ */ #define SKEL_I2C_IER_NACK BIT(3) /* Enable NACK IRQ */ #define SKEL_I2C_CTRL 0x1C /* Control Register */ #define SKEL_I2C_CTRL_EN BIT(0) /* Enable I2C controller */ #define SKEL_I2C_CTRL_START BIT(1) /* Send START condition */ #define SKEL_I2C_CTRL_R BIT(2) /* Read command */ #define SKEL_I2C_CTRL_W BIT(3) /* Write command */ #define SKEL_I2C_CTRL_STOP BIT(4) /* Send STOP cindition */ #define SKEL_I2C_TX 0x20 /* TX FIFO */ #define SKEL_I2C_RX 0x24 /* RX FIFO */ #define SKEL_I2C_CLK 0x28 /* Clock Prescale Register*/ #define SKEL_I2C_TIMEOUT 100000 #define SKEL_I2C_XFER_TIMEOUT (msecs_to_jiffies(500)) #define FIFO_SIZE_TX 1024 #define FIFO_SIZE_RX 1024 int presc = -1; module_param(presc, int, S_IRUGO | S_IWUSR); /* * skel_i2c - I2C device context * @base: pointer to register struct * @msg: pointer to current message * @mlen: number of bytes transferred in msg * @dev: device reference * @adap: i2c core abstraction * @msg_complete: xfer completion object * @clk: reference for i2c input clock * @err: error occured * @buf: ptr to msg buffer * @bus_clock: current i2c bus clock rate * @lock: spinlock for IRQ synchronization */ struct skel_i2c { void __iomem *base; struct i2c_msg *msg; size_t mlen; struct device *dev; struct i2c_adapter adap; struct completion msg_complete; struct clk *clk; u32 bus_clock; int err; u32 addr; u8 *buf; spinlock_t lock; };
Les principaux registres de contrĂŽle du contrĂŽleur sont:
- Registre de contrĂŽle (CTRL) - registre de contrĂŽle;
- Registre d'état d'interruption (ISR) - registre d'état d'interruption;
- Registre d'activation d'interruption (IER) - Registre de masque d'interruption.
Le cĆur du pilote est la structure skel_i2c, qui contient des champs tels que:
- .base - pointeur vers le début de la carte d'enregistrement;
- .msg - pointeur vers le message actuel;
- .adap - abstraction I2C (cliquez sur) .
Passons à la partie la plus pratique, décrivons les types de périphériques pris en charge par le pilote,
Fonctionnalité de l'adaptateur I2C et interface de messagerie I2C:
static const struct of_device_id skel_i2c_match[] = { { .compatible = "skel,skel-i2c", }, { .compatible = "at,24c64", }, {}, }; static u32 skel_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm skel_i2c_algo = { .master_xfer = skel_i2c_xfer, .functionality = skel_i2c_func, }; static struct platform_driver skel_i2c_driver = { .probe = skel_i2c_probe, .remove = skel_i2c_remove, .driver = { .name = "skel-i2c", .of_match_table = skel_i2c_match, }, }; module_platform_driver(skel_i2c_driver); MODULE_AUTHOR("Name Surname"); MODULE_DESCRIPTION("I2C bus driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:skel-i2c");
à partir des noms des structures et des fonctions, leur objectif est évident, nous décrivons uniquement la structure principale à partir de ce qui précÚde:
- skel_i2c_driver - décrit le nom du pilote, un tableau des périphériques et fonctions pris en charge qui sont appelés lorsque le module du noyau est chargé ou supprimé du systÚme.
Il est temps d'enregistrer le pilote dans le systÚme, ce qui signifie implémenter la fonction d'initialisation du contrÎleur, et également décrire skel_i2c_probe (appelé lorsque le pilote est chargé dans le systÚme) et skel_i2c_remove (appelé lorsque le pilote est supprimé du systÚme).
static int skel_i2c_init(struct skel_i2c *rdev) { u32 bus_clk_khz = rdev->bus_clock / 1000; u32 clk_khz = clk_get_rate(rdev->clk) / 1000; int prescale; int diff; prescale = clk_khz / (5 * bus_clk_khz) - 1; prescale = clamp(prescale, 0, 0xFFFF); diff = clk_khz / (5 * (prescale 1)) - bus_clk_khz; if (abs(diff) > bus_clk_khz / 10) { dev_err(rdev->dev, "Unsupported clock settings: clk: %d KHz, bus: %d KHz\n", clk_khz, bus_clk_khz); return -EINVAL; } if (presc != -1) i2c_write(presc, rdev->base, SKEL_I2C_CLK); else i2c_write(prescale, rdev->base, SKEL_I2C_CLK); return 0; } static int skel_i2c_probe(struct platform_device *pdev) { struct skel_i2c *rdev = NULL; struct resource *res; int irq, ret; u32 val; rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL); if (!rdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rdev->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(rdev->base)) return PTR_ERR(rdev->base); irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "Missing interrupt resource\n"); return irq; } rdev->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(rdev->clk)) { dev_err(&pdev->dev, "Missing clock\n"); return PTR_ERR(rdev->clk); } rdev->dev = &pdev->dev; init_completion(&rdev->msg_complete); spin_lock_init(&rdev->lock); val = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &rdev->bus_clock); if (val) { dev_err(&pdev->dev, "Default to 100kHz\n"); rdev->bus_clock = 100000; } if (rdev->bus_clock > 400000) { dev_err(&pdev->dev, "Invalid clock-frequency %d\n", rdev->bus_clock); return -EINVAL; } ret = devm_request_irq(&pdev->dev, irq, skel_i2c_isr, 0, pdev->name, rdev); if (ret) { dev_err(&pdev->dev, "Failed to claim IRQ %d\n", irq); return ret; } ret = clk_prepare_enable(rdev->clk); if (ret) { dev_err(&pdev->dev, "Failed to enable clock\n"); return ret; } skel_i2c_init(rdev); i2c_set_adapdata(&rdev->adap, rdev); strlcpy(rdev->adap.name, pdev->name, sizeof(rdev->adap.name)); rdev->adap.owner = THIS_MODULE; rdev->adap.algo = &skel_i2c_algo; rdev->adap.dev.parent = &pdev->dev; rdev->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, rdev); ret = i2c_add_adapter(&rdev->adap); if (ret) { clk_disable_unprepare(rdev->clk); return ret; } dev_info(&pdev->dev, "I2C probe complete\n"); return 0; } static int skel_i2c_remove(struct platform_device *pdev) { struct skel_i2c *rdev = platform_get_drvdata(pdev); clk_disable_unprepare(rdev->clk); i2c_del_adapter(&rdev->adap); return 0; }
La fonction la plus simple est skel_i2c_remove, qui désactive la source d'horloge et libÚre la mémoire utilisée. La fonction skel_i2c_init effectue l'initialisation du contrÎleur I2C.
Comme mentionnĂ© prĂ©cĂ©demment, skel_i2c_probe enregistre le pilote dans le systĂšme. La sĂ©quence d'actions, conditionnellement, peut ĂȘtre divisĂ©e en deux Ă©tapes:
- Obtention des ressources systĂšme et enregistrement d'un gestionnaire d'interruption skel_i2c_isr;
- Remplir les champs de structure et appeler la procédure d'ajout d'un nouvel adaptateur I2C.
Une fois le pilote enregistré dans le systÚme, vous pouvez implémenter la logique de transfert des messages sur l'interface:
static inline void i2c_write(uint32_t value, void *base, uint32_t addr) { writel(value, base addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base 0x%x);\n", value, addr); #endif } static inline uint32_t i2c_read(void *base, uint32_t addr) { uint32_t reg = readl(base addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static irqreturn_t skel_i2c_isr(int irq, void *dev) { if (unlikely(int_stat & skel_I2C_ISR_ARB)) { } else if (unlikely(int_stat & skel_I2C_ISR_NACK)) { } if (read) fill_rx_fifo(rdev); else fill_tx_fifo(rdev); complete(&rdev->msg_complete); return IRQ_HANDLED; } static int skel_i2c_xfer_msg(struct skel_i2c *rdev, struct i2c_msg *msg) { unsigned long time; rdev->msg = msg; rdev->mlen = msg->len; rdev->addr = msg->addr; rdev->buf = msg->buf; rdev->err = 0; reinit_completion(&rdev->msg_complete); skel_i2c_start_trans(rdev, msg); time = wait_for_completion_timeout(&rdev->msg_complete, skel_I2C_XFER_TIMEOUT); if (time == 0) rdev->err = -ETIMEDOUT; rdev->curr; return rdev->err; } static int skel_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct skel_i2c *rdev = i2c_get_adapdata(adap); int i, ret = 0; for (i = 0; (ret == 0) && (i < num); i) ret = skel_i2c_xfer_msg(rdev, msgs); skel_i2c_snd_stop(rdev); return ret ? : num; }
La premiÚre étape a décrit l'interaction de l'application de l'espace utilisateur avec le module du noyau systÚme. AprÚs avoir implémenté les internes du pilote, il est facile de voir l'interface à travers laquelle l'échange a lieu. En général, la transmission des messages est la suivante:
- skel_i2c_xfer - la fonction reçoit directement les messages pour la transmission et transfĂšre sĂ©quentiellement chaque message Ă skel_i2c_xfer_msg. Si une erreur s'est produite pendant le transfert de donnĂ©es, le transfert de donnĂ©es s'arrĂȘte;
- skel_i2c_xfer_msg - la fonction définit tous les champs de pilote nécessaires et initie le début de la transmission du message
- skel_i2c_isr - interrompre la routine de traitement. C'est là que la gestion des erreurs et la communication par bus ont lieu. Si toutes les données sont envoyées / reçues, l'indicateur done est défini en appelant la fonction complete, qui signale la fin de la transmission du message.
L'article ne dĂ©crit pas certaines des subtilitĂ©s de l'Ćuvre. Par exemple, la sĂ©quence d'actions pour l'envoi de messages, car la mise en Ćuvre de cet algorithme dĂ©pend du matĂ©riel. Nous nous sommes concentrĂ©s sur la mise en Ćuvre de la partie gĂ©nĂ©rale du pilote, quelles que soient les caractĂ©ristiques matĂ©rielles du contrĂŽleur.
Le squelette complet du pilote est joint ci-dessous. S'il vous plaßt, si vous trouvez des erreurs / inexactitudes, ou si vous avez quelque chose à ajouter, écrivez dans le MP ou dans les commentaires.
Squelette conducteur #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/io.h> #include <linux/clk.h> #include <linux/interrupt.h> #include <linux/time.h> #include <linux/delay.h> #include <linux/device.h> /* * Registers description. */ #define SKEL_I2C_ID 0x00 /* Core Identifier register */ #define SKEL_I2C_ISR 0x14 /* Interrupt Status Register */ #define SKEL_I2C_ISR_DNE BIT(0) /* One byte transaction done */ #define SKEL_I2C_ISR_ARB BIT(1) /* Arbitration lost */ #define SKEL_I2C_ISR_TXE BIT(2) /* RX FIFO nearly full */ #define SKEL_I2C_ISR_NACK BIT(3) /* No ACK */ #define SKEL_I2C_IER 0x18 /* Interrupt Enable Register */ #define SKEL_I2C_IER_DNE BIT(0) /* Enable DNE IRQ */ #define SKEL_I2C_IER_ARB BIT(1) /* Enable ARB LOSR IRQ */ #define SKEL_I2C_IER_TXE BIT(2) /* Enable TX FIFO EPMTY IRQ */ #define SKEL_I2C_IER_NACK BIT(3) /* Enable NACK IRQ */ #define SKEL_I2C_CTRL 0x1C /* Control Register */ #define SKEL_I2C_CTRL_EN BIT(0) /* Enable I2C controller */ #define SKEL_I2C_CTRL_START BIT(1) /* Send START condition */ #define SKEL_I2C_CTRL_R BIT(2) /* Read command */ #define SKEL_I2C_CTRL_W BIT(3) /* Write command */ #define SKEL_I2C_CTRL_STOP BIT(4) /* Send STOP cindition */ #define SKEL_I2C_TX 0x20 /* TX FIFO */ #define SKEL_I2C_RX 0x24 /* RX FIFO */ #define SKEL_I2C_CLK 0x28 /* Clock Prescale Register*/ #define SKEL_I2C_TIMEOUT 100000 #define SKEL_I2C_XFER_TIMEOUT (msecs_to_jiffies(500)) #define FIFO_SIZE_TX 1024 #define FIFO_SIZE_RX 1024 int presc = -1; module_param(presc, int, S_IRUGO | S_IWUSR); /* * skel_i2c - I2C device context * @base: pointer to register struct * @msg: pointer to current message * @mlen: number of bytes transferred in msg * @dev: device reference * @adap: i2c core abstraction * @msg_complete: xfer completion object * @clk: reference for i2c input clock * @err: error occured * @buf: ptr to msg buffer * @bus_clock: current i2c bus clock rate * @lock: spinlock for IRQ synchronization */ struct skel_i2c { void __iomem *base; struct i2c_msg *msg; size_t mlen; struct device *dev; struct i2c_adapter adap; struct completion msg_complete; struct clk *clk; u32 bus_clock; int err;; u32 addr; u8 *buf; spinlock_t lock; }; static const struct of_device_id skel_i2c_match[] = { { .compatible = "skel,skel-i2c", }, { .compatible = "at,24c64", }, {}, }; static inline void i2c_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base 0x%x);\n", value, addr); #endif } static inline uint32_t i2c_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static void skel_i2c_transfer(struct skel_i2c *rdev, u32 data) { i2c_write(data, rdev->base, SKEL_I2C_TX); } static void fill_tx_fifo(struct skel_i2c *rdev) { size_t tx_fifo_avail = FIFO_SIZE_TX; int bytes_to_transfer = min(tx_fifo_avail, rdev->mlen); while (bytes_to_transfer-- > 0) { skel_i2c_transfer(rdev, *rdev->buf); rdev->mlen--; } } static void fill_rx_fifo(struct skel_i2c *rdev) { size_t rx_fifo_avail = FIFO_SIZE_RX; int receive = min(rx_fifo_avail, rdev->mlen); while (receive-- > 0) { *rdev->buf = i2c_read(rdev->base, SKEL_I2C_RX); rdev->mlen--; } } void skel_i2c_snd_stop(struct skel_i2c *rdev) { u32 control = i2c_read(rdev->base, SKEL_I2C_CTRL); i2c_write(control | SKEL_I2C_CTRL_STOP, rdev->base, SKEL_I2C_CTRL); } static irqreturn_t skel_i2c_isr(int irq, void *dev) { struct skel_i2c *rdev = dev; u32 int_stat, read; int_stat = i2c_read(rdev->base, SKEL_I2C_ISR); read = rdev->msg->flags & I2C_M_RD; if (unlikely(int_stat & SKEL_I2C_ISR_ARB)) { } else if (unlikely(int_stat & SKEL_I2C_ISR_NACK)) { } if (read) fill_rx_fifo(rdev); else fill_tx_fifo(rdev); complete(&rdev->msg_complete); return IRQ_HANDLED; } static void skel_i2c_start_trans(struct skel_i2c *rdev, struct i2c_msg *msg) { } static int skel_i2c_xfer_msg(struct skel_i2c *rdev, struct i2c_msg *msg) { unsigned long time; rdev->msg = msg; rdev->mlen = msg->len; rdev->addr = msg->addr; rdev->buf = msg->buf; rdev->err = 0; reinit_completion(&rdev->msg_complete); skel_i2c_start_trans(rdev, msg); time = wait_for_completion_timeout(&rdev->msg_complete, SKEL_I2C_XFER_TIMEOUT); if (time == 0) rdev->err = -ETIMEDOUT; return rdev->err; } static int skel_i2c_init(struct skel_i2c *rdev) { u32 bus_clk_khz = rdev->bus_clock / 1000; u32 clk_khz = clk_get_rate(rdev->clk) / 1000; int prescale; int diff; prescale = clk_khz / (5 * bus_clk_khz) - 1; prescale = clamp(prescale, 0, 0xFFFF); diff = clk_khz / (5 * (prescale - 1)) - bus_clk_khz; if (abs(diff) > bus_clk_khz / 10) { dev_err(rdev->dev, "Unsupported clock settings: clk: %d KHz, bus: %d KHz\n", clk_khz, bus_clk_khz); return -EINVAL; } if (presc != -1) i2c_write(presc, rdev->base, SKEL_I2C_CLK); else i2c_write(prescale, rdev->base, SKEL_I2C_CLK); return 0; } static int skel_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct skel_i2c *rdev = i2c_get_adapdata(adap); int i, ret = 0; for (i = 0; (ret == 0) && (i < num); i++) ret = skel_i2c_xfer_msg(rdev, msgs); skel_i2c_snd_stop(rdev); return ret ? : num; } static u32 skel_i2c_func(struct i2c_adapter *adap) { return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; } static const struct i2c_algorithm skel_i2c_algo = { .master_xfer = skel_i2c_xfer, .functionality = skel_i2c_func, }; static int skel_i2c_probe(struct platform_device *pdev) { struct skel_i2c *rdev = NULL; struct resource *res; int irq, ret; u32 val; rdev = devm_kzalloc(&pdev->dev, sizeof(*rdev), GFP_KERNEL); if (!rdev) return -ENOMEM; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); rdev->base = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(rdev->base)) return PTR_ERR(rdev->base); irq = platform_get_irq(pdev, 0); if (irq < 0) { dev_err(&pdev->dev, "Missing interrupt resource\n"); return irq; } rdev->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(rdev->clk)) { dev_err(&pdev->dev, "Missing clock\n"); return PTR_ERR(rdev->clk); } rdev->dev = &pdev->dev; init_completion(&rdev->msg_complete); spin_lock_init(&rdev->lock); val = of_property_read_u32(pdev->dev.of_node, "clock-frequency", &rdev->bus_clock); if (val) { dev_err(&pdev->dev, "Default to 100kHz\n"); rdev->bus_clock = 100000; /* default clock rate */ } if (rdev->bus_clock > 400000) { dev_err(&pdev->dev, "Invalid clock-frequency %d\n", rdev->bus_clock); return -EINVAL; } ret = devm_request_irq(&pdev->dev, irq, skel_i2c_isr, 0, pdev->name, rdev); if (ret) { dev_err(&pdev->dev, "Failed to claim IRQ %d\n", irq); return ret; } ret = clk_prepare_enable(rdev->clk); if (ret) { dev_err(&pdev->dev, "Failed to enable clock\n"); return ret; } skel_i2c_init(rdev); i2c_set_adapdata(&rdev->adap, rdev); strlcpy(rdev->adap.name, pdev->name, sizeof(rdev->adap.name)); rdev->adap.owner = THIS_MODULE; rdev->adap.algo = &skel_i2c_algo; rdev->adap.dev.parent = &pdev->dev; rdev->adap.dev.of_node = pdev->dev.of_node; platform_set_drvdata(pdev, rdev); ret = i2c_add_adapter(&rdev->adap); if (ret) { clk_disable_unprepare(rdev->clk); return ret; } dev_info(&pdev->dev, "I2C probe complete\n"); return 0; } static int skel_i2c_remove(struct platform_device *pdev) { struct skel_i2c *rdev = platform_get_drvdata(pdev); clk_disable_unprepare(rdev->clk); i2c_del_adapter(&rdev->adap); return 0; } static struct platform_driver skel_i2c_driver = { .probe = skel_i2c_probe, .remove = skel_i2c_remove, .driver = { .name = "skel-i2c", .of_match_table = skel_i2c_match, }, }; module_platform_driver(skel_i2c_driver); MODULE_AUTHOR("Name Surname"); MODULE_DESCRIPTION("I2C bus driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:skel-i2c");
Merci de votre attention!