Habr, hallo!
Dieser Artikel konzentriert sich auf die Entwicklung des Linux-Kernelmoduls I2C (Inter-Integrated Circuit). Im Folgenden wird der Prozess der Implementierung der Grundstruktur des I2C-Treibers beschrieben, in den Sie problemlos die Implementierung der erforderlichen Funktionen einfĂŒgen können.
Wir beschreiben die Eingabedaten: I2C-Block fĂŒr den neuen Prozessor, der mit dem FPGA âverkabeltâ ist und Linux Version 3.18.19 und PeripheriegerĂ€te (EEPROM AT24C64 und BME280) ausfĂŒhrt.
Das Funktionsprinzip von I2C ist recht einfach, aber wenn Sie Ihr Wissen auffrischen mĂŒssen, können Sie es
hier lesen.
Abbildung 1. Zeitdiagramm der I2C-BussignaleBevor Sie mit der Entwicklung eines Treibers beginnen, sehen wir uns dazu an, wie User Space-Anwendungen mit dem Kernelmodul interagieren:
- Wir implementieren eine kleine User-Space-Anwendung, deren Zweck darin besteht, die eindeutige I2C-Register-ID des GerĂ€ts zu lesen. Mit diesem Schritt können Sie die Schnittstelle verstehen, ĂŒber die der Austausch zwischen dem Kernelmodul und der Benutzeranwendung erfolgt.
- Machen wir uns mit der Möglichkeit vertraut, I2C-Nachrichten vom Kernelmodul zu ĂŒbertragen.
- FĂŒgen Sie das Kernelmodul zur Assembly hinzu und beschreiben Sie die Hardware der GerĂ€te im GerĂ€tebaum.
- Wir implementieren die allgemeine Struktur (Skelett) des I2C-Treibers mit einigen ErklÀrungen.
Leider ist es nicht möglich, die realen Quellen des entwickelten Treibers anzuhĂ€ngen. AuĂerdem möchte ich darauf hinweisen, dass alle Namen, Namen und Registerkarten des Controllers geĂ€ndert werden. Nicht einmal die HĂ€lfte der entwickelten FunktionalitĂ€t war im Skelett des Fahrers enthalten, die Treiberstruktur ist jedoch ein guter Ausgangspunkt fĂŒr die Entwicklung. Beispiele fĂŒr I2C-Treiber finden Sie
hier .
Erster Schritt
Machen wir uns zunÀchst mit dem Dienstprogramm i2cdetect vertraut. Das Ergebnis von i2cdetect ist wie folgt:
./i2cdetect -y 0 0 1 2 3 4 5 6 7 8 9 abcdef 00: â â â â â â â â â â â â â 10: â â â â â â â â â â â â â â â â 20: â â â â â â â â â â â â â â â â 30: â â â â â â â â â â â â â â â â 40: â â â â â â â â â â â â â â â â 50: 50 â â â â â â â â â â â â â â â 60: â â â â â â â â â â â â â â â â 70: â â â â â â â â
Das Dienstprogramm macht den GerĂ€teadressbus auf dem I2C nacheinander verfĂŒgbar und zeigt nach Erhalt einer positiven Antwort (in diesem Fall lautet die positive Antwort ACK) die GerĂ€teadressnummer auf dem Bus zur Konsole an.
Wir werden ein kleines Programm schreiben, das die eindeutige ID des Temperatursensors liest und das Ergebnis seiner Arbeit in der Konsole anzeigt. Es sieht sehr einfach aus:
#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; }
Es wird deutlich, dass das Kernelmodul Daten in Form von i2c_rdwr_ioctl_data-Nachrichtenfeldern empfĂ€ngt. Die Struktur enthĂ€lt Felder wie i2c_msg und nmsgs, die fĂŒr die Ăbertragung verwendet werden:
- .addr - GerÀteadressen;
- .flags - Art der Operation (Lesen oder Schreiben);
- .len - die LĂ€nge der aktuellen Nachricht;
- .buf- Zwischenablage.
Zweiter Schritt
Jetzt gehe ich nicht mehr ins Innere, sondern lerne eine Version des I2C-Treibers kennen.
Wie bereits erwĂ€hnt, empfĂ€ngt das Kernelmodul Nachrichten als Struktur. Betrachten Sie beispielsweise den Algorithmus des Treibers, wenn Sie eine Schreiboperation ausfĂŒhren (hardwareabhĂ€ngiger Teil):
- Das erste TX-FIFO wird gefĂŒllt: Die Adresse des GerĂ€ts kommt zuerst, und dann werden die verbleibenden Daten ĂŒbertragen.
- Das ISR-Interrupt-Statusregister wird gelöscht und Interrupts im IER-Register werden aktiviert (in diesem Fall tritt ein Interrupt auf, wenn keine Daten im TX-FIFO vorhanden sind).
- Die DatenĂŒbertragung ist zulĂ€ssig und das Startbit wird auf dem Bus gesetzt.
Der gesamte nachfolgende Datenaustausch erfolgt im Interrupt-Handler.
Treiber, die an diesem Algorithmus arbeiten, finden Sie
hier . Der Controller verfĂŒgt möglicherweise nicht ĂŒber einen FIFO, sondern nur ĂŒber ein einziges Ăbertragungsregister. Dies ist jedoch ein Sonderfall, bei dem die FIFO-GröĂe gleich eins ist.
Schritt drei
FĂŒgen Sie das Kernelmodul zur Assembly hinzu und beschreiben Sie die Hardware der GerĂ€te in der GerĂ€testruktur:
1. Erstellen Sie eine Quelldatei im folgenden Verzeichnis:
cd drivers/i2c/busses/ vim i2c-skel.c :wq
Als Ergebnis erscheint die Datei:
drivers/i2c/busses/i2c-skel.c
2. FĂŒgen Sie die Treiberkonfiguration zu
drivers / i2c / busses / Kconfig hinzu :
config I2C_SKEL tristate "I2C adapter" help If you say yes to this option, support will be included for the I2C interface.
3. FĂŒgen Sie der
Assembly den
Treiber drivers / i2c / busses / Makefile hinzu :
obj-$(CONFIG_I2C_SKEL) += i2c-skel.o
4. FĂŒgen Sie devicetree (* .dts) eine Beschreibung des I2C-Blocks hinzu und unterstĂŒtzen Sie das eeprom-GerĂ€t sofort:
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>; }; } ;
Die oben genannten Schritte werden nicht im Detail betrachtet, aber neugierige Leser können hier einen Blick darauf werfen.
Vierter Schritt
Nachdem Sie sich mit dem Prinzip des Treibers vertraut gemacht haben, fahren wir mit der Implementierung fort.
Verbinden Sie zunĂ€chst die Header-Dateien, beschreiben Sie die âvirtuelleâ Registrierungskarte sowie die Darstellung des I2C-Treibers.
#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; };
Die Hauptsteuerregister der Steuerung sind:
- Steuerregister (CTRL) - Steuerregister;
- Interrupt Status Register (ISR) - Interrupt Status Register;
- Interrupt Enable Register (IER) - Interrupt-Maskenregister.
Das HerzstĂŒck des Treibers ist die skel_i2c-Struktur, die folgende Felder enthĂ€lt:
- .base - Zeiger auf den Anfang der Registrierkarte;
- .msg - Zeiger auf die aktuelle Nachricht;
- .adap - I2C-Abstraktion (Klick) .
Kommen wir zum praktischeren Teil und beschreiben die vom Treiber unterstĂŒtzten GerĂ€tetypen.
I2C-AdapterfunktionalitÀt und I2C-Messaging-Schnittstelle:
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");
Aus den Namen der Strukturen und Funktionen ist ihr Zweck offensichtlich, wir beschreiben nur die Hauptstruktur aus dem Obigen:
- skel_i2c_driver - beschreibt den Namen des Treibers, eine Tabelle der unterstĂŒtzten GerĂ€te und Funktionen, die aufgerufen werden, wenn das Kernelmodul geladen oder aus dem System entfernt wird.
Es ist Zeit, den Treiber im System zu registrieren, dh die Controller-Initialisierungsfunktion zu implementieren und auch skel_i2c_probe (aufgerufen, wenn der Treiber in das System geladen wird) und skel_i2c_remove (aufgerufen, wenn der Treiber aus dem System entfernt wird) zu beschreiben.
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; }
Die einfachste Funktion ist skel_i2c_remove, mit der die Taktquelle ausgeschaltet und der verwendete Speicher freigegeben wird. Die Funktion skel_i2c_init fĂŒhrt die Initialisierung des I2C-Controllers durch.
Wie bereits erwÀhnt, registriert skel_i2c_probe den Treiber im System. Die Abfolge der Aktionen kann bedingt in zwei Stufen unterteilt werden:
- Abrufen von Systemressourcen und Registrieren eines Interrupt-Handlers skel_i2c_isr;
- FĂŒllen Sie die Strukturfelder und rufen Sie die Prozedur zum HinzufĂŒgen eines neuen I2C-Adapters auf
Nachdem der Treiber im System registriert wurde, können Sie die NachrichtenĂŒbertragungslogik auf der Schnittstelle implementieren:
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; }
Im ersten Schritt wurde die Interaktion der User Space-Anwendung mit dem Systemkernelmodul beschrieben. Nachdem wir die Treiberinternale implementiert haben, ist die Schnittstelle, ĂŒber die der Austausch stattfindet, leicht zu erkennen. Im Allgemeinen lautet die NachrichtenĂŒbermittlung wie folgt:
- skel_i2c_xfer - Die Funktion empfĂ€ngt Nachrichten direkt zur Ăbertragung und ĂŒbertrĂ€gt jede Nachricht nacheinander an skel_i2c_xfer_msg. Wenn wĂ€hrend der DatenĂŒbertragung ein Fehler aufgetreten ist, wird die DatenĂŒbertragung gestoppt.
- skel_i2c_xfer_msg - Die Funktion setzt alle erforderlichen Treiberfelder und leitet den Start der NachrichtenĂŒbertragung ein.
- skel_i2c_isr - Interrupt-Verarbeitungsroutine. Hier findet die Fehlerbehandlung und Buskommunikation statt. Wenn alle Daten gesendet / empfangen wurden, wird das Fertig-Flag gesetzt, indem die vollstĂ€ndige Funktion aufgerufen wird, die den Abschluss der NachrichtenĂŒbertragung signalisiert.
Der Artikel beschreibt einige Feinheiten der Arbeit nicht. Zum Beispiel die Reihenfolge der Aktionen zum Senden von Nachrichten, da die Implementierung dieses Algorithmus hardwareabhÀngig ist. Wir haben uns auf die Implementierung des allgemeinen Teils des Treibers konzentriert, unabhÀngig von den Hardwarefunktionen des Controllers.
Das vollstĂ€ndige Fahrerskelett ist unten angebracht. Wenn Sie Fehler / Ungenauigkeiten finden oder etwas hinzufĂŒgen möchten, schreiben Sie bitte in die PM oder in die Kommentare.
Fahrerskelett #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");
Vielen Dank fĂŒr Ihre Aufmerksamkeit!