Il bus I2C

di Alessandro Rubini

Riprodotto con il permesso di Linux Magazine, Edizioni Master.

In questo articolo viene presentato il bus i2c e l'infrastruttura del kernel per la gestione dei controllori e delle periferiche che vivono su questa interfaccia

Normalmente quando si parla di bus oggi si pensa a cose come PCI: un corpus di semplici protocolli associati a complesse specifiche elettriche e meccaniche su come instradare un centinaio di segnali elettrici ad alta velocità dal processore verso le unità periferiche. Un altro bus molto noto è USB; in questo caso la riduzione del numero di fili comporta un aumento notevole della complessità dei protocolli di comunicazione.

I2C è un bus di tutt'altro tipo: si tratta di un canale di comunicazione molto semplice e relativamente lento, le cui specifiche constano di poche decine di pagine, che possono benissimo essere condensate in un paio di pagine al massimo in quanto non ci sono requisiti meccanici di interfaccia, i requisiti elettrici sono banali e il protocollo quasi.

Nonostante si tratti un canale semplificato e lento, le applicazioni di I2C sono numerose e il kernel offre una buona infrastruttura software per l'integrazione di questo bus nel proprio sistema di elaborazione dati. La discussione e il codice presentati si riferiscono a Linux-2.6.7, ma le differenze rispetto a Linux-2.4 sono molto limitate.

Riquadro 1 - Definizione di bus

Il termine bus è l'abbreviazione di autobus, cioè un mezzo di trasporto usato da più persone contemporaneamente. In effetti autobus è la modernizzazione di omnibus, cioè per tutti. In ambito informatico si definisce bus un canale di comunicazione che può essere usato da più di due attori.

In questo senso è corretto usare bus per indicare un canale I2C proprio come lo è per indicare USB (Universal Serial Bus, appunto) o PCI. D'altronde anche nella descrizione dei microprocessori si parla di bus indirizzi, bus dati e bus di controllo riferendosi ai segnali collegati al processore.

Riquadro 2 - CSMA/CA

L'acronimo CSMA/CA significa Carrier-Sense Multiple-Access with Collision Avoidance. Indica un protocollo di comunicazione ad accesso multiplo (Multiple-Access), cioè un bus, in cui ogni stazione del canale attende che il canale sia inattivo prima di incominciare a trasmettere (Carrier-Sense). Quando la trasmissione inizia contemporaneamente per due stazioni, però, viene usato un meccanismo di priorità per cui una delle stazioni in gioco smette di trasmettere senza che il messaggio trasmesso dall'altra stazione venga danneggiato (Collision Avoidance). La priorità nel caso di I2C è data ai pacchetti diretti verso lo slave con indirizzo minore. Questa tecnica viene usata anche in molte altre situazioni, come per esempio il bus CAN (Computer-Area Network); Ethernet, invece usa un algoritmo CSMA/CD (Collision Detection), in cui la trasmissione concomitante di due stazioni porta alla perdita di entrambi i pacchetti, anche se l'errore viene rilevato da tutte le stazioni.

Cos'è il bus I2C.

"I2C" sta per "IIC", ovvero "Inter Integrated Circuit" e in italiano si legge i-quadro-ci. Come dice il nome, si tratta di un meccanismo pensato per far comunicare diversi circuiti integrati che risiedono sullo stesso circuito stampato.

Tipicamente, gli integrati che convivono su una scheda elettronica devono scambiarsi informazioni di controllo senza particolari requisiti di velocità di risposta: un convertitore video per esempio avrà dei parametri di luminosità e contrasto che devono poter essere modificati senza interrompere il flusso dei dati; un termometro verrà interrogato ogni manciata di secondi o ancora più raramente; una batteria di spie sul pannello frontale può tollerare latenze anche superiori alle decine di millisecondi. Per questi usi, l'adozione di un canale seriale condiviso come I2C permette di limitare notevolmente il numero di segnali elettrici che bisogna filare sullo stampato rispetto all'approccio in voga precedentemente, secondo il quale ogni integrato aveva un bus indirizzi, un bus dati e un chip select dedicato.

Per semplificare l'interfacciamento hardware, questi bus seriali interni al sistema sono sincroni, cioè dotati di un segnale di clock pilotato dall'integrato master. Nel caso di I2C, il bus è composto da due segnali, chiamati SDA e SCL, per serial data e serial clock. I segnali sono normalmente alti e vengono abbassati da uno degli componenti sul bus (si tratta di uscite open drain in un collegamente wired or). Viene implementato un protocollo CSMA/CA (si veda il riquadro 2), anche se nella maggior parte dei casi si usa un solo master per cui non sono possibili collisioni.

Si noti come al momento il kernel permetta il controllo dei dispositivi master ma non di eventuali dispositivi slave raggiungibili dalla CPU.

Ogni periferica sul bus risponde a due indirizzi consecutivi lunghi 8 bit, un indirizzo per la scrittura e uno per la lettura; si parla quindi di indirizzi a 7 bit cui viene aggiunto un bit di lettura/scrittura. Nel caso più semplice per scrivere un registro il master invia un pacchetto di tre byte: l'indirizzo di scrittura della periferica, il numero del registro (detto subaddress) e il valore da scrivere. In generale però il significato dei dati inviati dopo il byte di indirizzo viene definito dal costruttore della periferica slave, quindi è possibile avere più di 256 registri o registro a 16 o più bit. Una periferica molto diffusa per esempio è la memoria non volatile, disponibile tipicamente in tagli da 32KiB.

Dopo ogni byte ricevuto lo slave deve trasmettere un bit di conferma (acknowledgement o ack); in mancanza di ack il master sospende la trasmissione.

Per leggere invece il master invia due pacchetti: tramite il primo si seleziona il subaddress della periferica, col secondo pacchetto si leggono uno o più byte di informazione, pilotando il segnale SCL e leggendo SDA che viene ora pilotato dallo slave; dopo la lettura di ogni byte il master invierà un bit di acknowledgement.

Secondo il protocollo il segnale SDA viene modificato solo quando il clock è basso, con l'eccezione dei segnali di start e di stop (che possono così essere identificati da tutte le stazioni sul bus anche senza seguire le sequenze di ack). in figura 1, per esempio, è rappresentata una scrittura verso l'integrato con indirizzo 0x20, senza mostrare i dettagli della trasmissione dei byte successivi al primo.

La velocità di trasmissione di I2C, inizialmente limitata a 100Kib/s è stata poi elevata a 400Kib/s e successivamente a 3.4Mib/s; si tratta comunque solo dei valori massimi consentiti, mentre non ci sono limiti minimi imposti e il master può pilotare i segnali molto più lentamente.

Naturalmente negli anni sono state introdotte sia estensioni che semplificazioni alle specifiche del bus. Le estensioni sono per esempio un modo per specificare indirizzi a 10 bit invece che 7; la semplificazione invece si chiama SMBus (System Management Bus), un bus quasi completamente compatibile con I2C ma in cui i pacchetti devono rispondere ad alcune forme predefinite. SMBus è stato definito da un consorzio di aziende al quale non ha partecipato Philips, ma non mi è chiaro lo status di SMBus relativamente ai brevetti.



Figura 1
Una scrittura su I2C

La figura è anche disponibile in PostScript

Riquadro 3 - Liberatoria (disclaimer)

La distribuzione di queste informazioni non comporta né implica i diritti sui brevetti I2C di Royal Philips Electronics N.V. riguardo alla realizzazione, all'uso o alla vendita di prodotti che usano i brevetti. È richiesta una licenza sui brevetti I2C per ogni uso dei diritti da essi coperti.

Riquadro 4 - Sui brevetti I2C

La liberatoria nel riquadro 3 è l'adattamento al contesto "articolo" del messaggio ufficiale di Philips che appare per esempio in http://www.opencores.org/projects.cgi/web/i2c/faq, dove si dice tra l'altro che i brevetti ad oggi dovrebbero essere tutti scaduti.

Sta di fatto che questa storia dei brevetti su I2C viene citata quasi ossessivamente da Philips in tutta la sua documentazione tecnica, senza che venga mai dato un riferimento ai numeri identificativi di questi brevetti; risulta quindi pressochè impossibile verificare su cosa effettivamente Philips abbia ottenuto l'esclusiva, in quali nazioni tali brevetti siano validi e quando scadano, anche se qualche numero di brevetto di trova nelle cronache delle cause legali intentate da Philips contro altri famosi produttori di silicio.

L'effetto pratico di questo comportamento, peraltro tipico dei detentori di brevetto, è un prolungamento oltre i termini di legge dell'esclusiva sancita dall'ufficio brevetti e una situazione di incertezza legale per chi sia muova in ambiti limitrofi a quelli potenzialmente brevettati, a meno di effettuare costose ricerche presso i vari uffici brevetti mondiali.

Un altro effetto è la proliferazione di bus seriali incompatibili, in quanto i costruttori spesso preferiscono evitare di pagare i diritti brevettuali per mettere sul mercato un prodotto più economico, a discapito della standardizzazione delle interfaccie. Per esempio il real-time clock DS1302 usa un meccanismo seriale sincrono che non è I2C. In questo modo i progettisti devono instradare sullo stampato più bus del necessario e i driver devono muovere i bit "a mano" senza potersi appoggiare all'interfaccia "bit-banging" del kernel e al supporto sysfs associato.



Figura 2
I moduli i2c del kernel

La figura è anche disponibile in PostScript

La struttura del codice del kernel

Visto il numero elevato di componenti I2C presenti oggi sul mercato e considerato che lo stesso componente può essere installato in bus I2C differenti, i nostri programmatori di sistema hanno reso disponibile un'interfaccia unificata. Si tratta di una struttura simile a quella presentata nel numero di Maggio, con un modulo core cui si agganciano sia i controllori (master I2C) sia le periferiche (slave I2C). La struttura complessiva è mostrata in figura 2.

Caratteristico di questa struttura è il ruolo di i2c-algo-bit e i2c-sensors. Nel primo caso si tratta di un modulo che implementa un algoritmo generico basato sullo spostamento manuale di due bit (associati a SDA e SCL), ma definire quali siano i bit e come vadano mossi viene lasciato ad un altro livello software, poiché molte implementazioni usano il moviemento manuale dei segnali ma ciascuna usando registri di controllo diversi. Il modulo i2c-sensor, invece, offre un'interfaccia unificata per accedere ai vari sensori hardware che si trovano sulle macchine moderne di classe PC: termometri, contagiri per le ventole di raffreddamento, eccetera.

I file sorgente sono organizzati in diverse directory, per separare i ruoli dei vari moduli: in drivers/i2c troviamo le implementazioni core, dev (l'esportazione dei bus tramite file speciali a carattere), sensor (interfaccia unificata per il riconoscimento dei sensori, che non viene discussa i questa sede). Tutti gli altri sorgenti sono divisi nelle directory algos (algoritmi condivisi da diversi bus), busses (driver di adattatore) e chips (driver di periferica).

Nel codice sorgente, le strutture dati principali usate nella gestione del sottosistema sono le seguenti, definite in <linux/i2c.h> e raffigurate in figura 3, mostrando i puntatori con cui ogni struttura dati si riferisce alle altre, come pure le funzioni principali esportate da i2c-core verso gli altri moduli.



Figura 3
Le strutture dati

La figura è anche disponibile in PostScript

Registrazione di un bus

I bus controllati dal kernel possono essere pilotati direttamente dal processore (situazione comune nelle macchine embedded non-x86) oppure da una periferica PCI (come succede spesso per i sensori nei PC), oppure anche da un componente in una scheda periferica, quando il bus è usato per la comunicazione tra gli integrati montati sulla scheda. Quest'ultima situazione si presenta tipicamente nelle schede PCI di acquisizione video ma anche in alcune VGA, in cui alcuni componenti della scheda vengono configurati dall'integrato principale tramite un bus I2C locale.

Un modulo che voglia registrare un nuovo bus deve chiamare la funzione i2caddadapter se implementa il proprio algoritmo di accesso al bus, altrimenti invoca una funzione offerta dal modulo relativo all'algoritmo usato, come per esempio i2c_bit_add_bus, la quale si appoggia su i2c_add_adapter internamente.

Le due implementazioni prototipali, quella per un bus autonomo e quella che si appoggia su un algoritmo predefinito, sono rappresentate nei riquadri 5 e 6, ipotizzando che il modulo si chiami "c2c", sigrla che richiama "I2C" e si può leggere "(this) Code TO(be) Completed". Il protocollo (fittizio) cui si appoggia l'esempio del riquadro 6 si chiama invece "p2c" (this Protocol TO be Completed).

La rimozione del bus avviene in maniera estremamente simile.

Riquadro 5 - Registrazione di un bus autonomo

#include <linux/i2c.h>
#include <linux/init.h>
 
static struct i2c_algorithm c2c_algo = {
    .name = "c2c",
    /* ... function pointers ... */
};

static struct i2c_adapter c2c_adap = {
    .owner = THIS_MODULE,
    .algo = c2c_algo,
    /* ... other fields ... */
};

static int __devinit c2c_init(void) {
    return i2c_add_adapter(&c2c_adap);
}

static void __devexit c2c_exit(void) {
    i2c_del_adapter(&c2c_adap);
}

MODULE_INIT(c2c_init);
MODULE_EXIT(c2c_exit);

Riquadro 6 - Registrazione di un bus con un algoritmo condiviso

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/i2c-algo-p2c.h>

static struct i2c_algo_p2c_data c2c_data = {
    /* ... protocol-specific pointers ... */
};

static struct i2c_adapter c2c_adap = {
    .owner = THIS_MODULE,
    .algo_data = &c2c_data,
    /* ... other fields ... */
};

static int __devinit c2c_init(void) {
    return i2c_p2c_add_dus(&c2c_adap);
}

static void __devexit c2c_exit(void) {
    i2c_p2c_del_bus(&c2c_Adap);
}

MODULE_INIT(c2c_init);
MODULE_EXIT(c2c_exit);

L'algoritmo ``bit-banging''

Il meccanismo forse più usato per pilotare un bus I2C è quello cosiddetto di ``bit-banging'', cioè lo spostamento manuale dei due bit, sotto il controllo del modulo i2c-algo-bit. Tale modulo si occupa genericamente della realizzazione del protocollo, pilotando i segnali SDA e SCL come descritto nella manualistica, delegando però le reali operazioni di I/O allo specifico adattatore.

Le quattro operazioni che ogni adattatore deve definire sono setsda, getsda, setscl, e getscl, anche se getscl è al momento facoltativa, in quanto in tutti i bus supportati il kernel Linux controlla il componente master del canale e non cede mail il controllo di SCL ad altri componenti.

Il riquadro 7 mostra l'implementazione prototipale di un modulo di questo tipo, usando ancora una volta "c2c" come ipotetico nome. In questo caso, la struttura c2c_local_proto_data serve a specificare dettagli locali a c2c sul bus. Se per esempio il modulo registrasse due adattatori, occorrerebbe accedere all'argomento data per distinguerli.

Riquadro 7 - Registrazione di un bus di tipo "bit-banging"

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/i2c-algo-bit.h>

static struct c2c_local_proto_data {
    /* ... anything we need ... */
};

/*
 * The protocol
 */
static void c2c_setscl(void *data, int state)
{ /* set bit if state != 0, unset if state == 0 */ }

static void c2c_setsda(void *data, int state)
{ /* ensure sda is output, and set/unset according to state */ }

static void c2c_getsda(void *data, int state)
{ /* ensure sda is input and read it */ }

static struct i2c_algo_bit_data c2c_data = {
    .setsda = c2c_setsda,
    .getsda = c2c_getsda,
    .setscl = c2c_setscl,
    .udelay = C2C_DELAY_USEC,
    .timeout = C2C_TIMOUT,
    .data = &c2c_local_proto_data,
};

/*
 * The adapter
 */
static struct i2c_adapter c2c_adap = {
    .owner = THIS_MODULE,
    .algo_data = &c2c_data,
    /* ... other fields ... */
};

static int __devinit c2c_init(void) {
    return i2c_bit_add_dus(&c2c_adap);
}

static void __devexit c2c_exit(void) {
    i2c_bit_del_bus(&c2c_Adap);
}

MODULE_INIT(c2c_init);
MODULE_EXIT(c2c_exit);

Registrazione di un driver

Un driver (nel senso di i2c_driver) è il codice che gestisce un particolare tipo di periferica slave I2C. Come abbiamo visto, il driver è responsabile della creazione e della distrizione degli oggetti i2c_client associati ad ogni periferica.

Il codice del driver è notevolmente più lungo e strutturato del codice di un adattatore, in quanto il driver deve astrarre le funzionalità dell'integrato in modo da permettere l'accesso all'hardware in maniera trasparente.

Il riquadro 8 mostra la struttura tipica di un driver I2C, senza entrare nel dettaglio delle operazioni implementate in c2c_command, riguardo alle quali può essere utile riferirsi a qualcuno dei driver già presenti nel kernel ufficiale.

Riquadro 8 - Codice di un driver I2C

#include <linux/i2c.h>
#include <linux/init.h>

/*
 * This structure defines our address in a bus (only 0x20 is possible)
 */
static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x20, I2C_CLIENT_END };

static struct i2c_client_address_data addr_data = {
        .normal_i2c             = normal_addr,
        .normal_i2c_range       = ignore,
        .probe                  = ignore,
        .probe_range            = ignore,
        .ignore                 = ignore,
        .ignore_range           = ignore,
        .force                  = ignore,
};

/* The attach method must allocate a client */
static int c2c_attach(struct i2c_adapter *adap, int addr, int kind)
{
        struct i2c_client *new_client;

	new_client = kmalloc(sizeof(*new_client), GFP_KERNEL);
	memset(new_client, 0, sizeof(new_client));

	new_client->addr = addr;
        new_client->adapter = adap;
        new_client->driver = &c2c_driver;

	/* check it it's our device, free and return if not */

	return i2c_attach_client(new_client);
}

/* The detach method releases a client */
static int c2c_detach(struct i2c_client *client)
{
        i2c_detach_client(client);
        kfree(i2c_get_clientdata(client));
        return 0;
}

/* Command is for generalized operations */
static int
c2c_command(struct i2c_client *client, unsigned int cmd, void *arg)
{
	switch(cmd) {
		/* ... */
	}
}

/* Probe is called when a new bus appears */
static int c2c_probe(struct i2c_adapter *adap)
{
        return i2c_probe(adap, &addr_data, c2c_attach);
}


/* Driver structure */
static struct i2c_driver c2c_driver = {
        .owner          = THIS_MODULE,
        .name           = "C2C",
        .attach_adapter = c2c_probe,
        .detach_client  = c2c_detach,
        .command        = c2c_command
};

/* Init and exit: register and unregister the driver */
static int __devinit c2c_init(void)
{
        return i2c_add_driver(&c2c_driver);
}

static void __devexit c2c_exit(void)
{
        i2c_del_driver(&c2c_driver);
}

module_init(c2c_init);
module_exit(c2c_exit);

Approfondimenti

Sui bus I2C e affini ci sarebbe ancora molto da dire, come le modalità di accesso tramite sysfs o tramite file speciale a caratteri, gli indirizzi a 10 bite e le specificità di SMBus, o l'accesso ai sensori dallo spazio utente, altro ancora.

Poiché però si tratta di codice scritto molto bene, ritengo che le persone interessate possano approfondire autonomamente l'argomento con successo.

Per chi voglia andare alla fonte, le specifiche ufficiali di I2C sono disponibili presso Philips (http://www.semiconductors.philips.com/acrobat/literature/9398/39340011.pdf) mentre le specifiche di SMBus sono rese pubbliche in un dominio appostito, su http://www.smbus.org/specs/smbus20.pdf=.

Come ci si può aspettare, l'interfaccia del kernel è definita negli header il cui nome inizia per "i2c"; di particolare interesse sono include/linux/i2c.h e include/linux/i2c-algo-bit.h.

La directory Documentation/i2c/, sempre nei sorgenti del kernel, contiene come d'uso una buona panoramica del bus e di come scrivere il codice per aggiungere nuovi adattatori o nuovi integrati slave.