Un driver I2C: de la cadena compatible a la trama en el bus
Construir un struct i2c_driver con probe, remove, id_table y of_match_table, leer y escribir registros con la familia portable i2c_smbus_*, componer tramas arbitrarias con i2c_transfer y struct i2c_msg, y entender el emparejamiento por device tree y la inversión de control del modelo de dispositivos.
Un driver I2C es, en esencia, un traductor: convierte una cadena compatible del device tree en una secuencia de transacciones sobre el bus. Nunca toca un cable ni genera un pulso de reloj; solo describe qué bytes deben cruzar y en qué orden, y delega la electricidad al controlador del bus. El modelo de dispositivos de Linux invierte el control: no eres tú quien busca el hardware, es el núcleo quien llama a tu probe cuando aparece un dispositivo que encaja con tu tabla. Ese desacoplamiento entre protocolo y transporte es lo que hace que el mismo driver funcione idéntico en una Raspberry Pi, un portátil x86 y un teléfono.
- Construir un
struct i2c_driverconprobe,remove,id_tableyof_match_table. - Leer y escribir registros con la familia portable
i2c_smbus_*. - Componer tramas arbitrarias con
i2c_transferystruct i2c_msg. - Entender el emparejamiento por device tree y la inversión de control del bus.
El esqueleto: struct i2c_driver
Todo driver I2C es la misma plantilla: una función probe, una tabla de dispositivos del device tree y el registro con module_i2c_driver. Desde Linux 6.3 la firma de probe recibe un solo argumento —el i2c_client—, y desde 6.1 remove devuelve void. Tomemos el sensor de temperatura TMP117:
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#define TMP117_REG_TEMP 0x00
#define TMP117_REG_CONFIG 0x01
#define TMP117_REG_WHOAMI 0x0f
#define TMP117_DEVICE_ID 0x0117
struct tmp117 {
struct i2c_client *client;
};
static int tmp117_probe(struct i2c_client *client)
{
struct tmp117 *st;
s32 id;
st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
if (!st)
return -ENOMEM;
st->client = client;
i2c_set_clientdata(client, st);
/* el WHO_AM_I confirma que hay de verdad un TMP117 en esta direccion */
id = i2c_smbus_read_word_swapped(client, TMP117_REG_WHOAMI);
if (id < 0)
return dev_err_probe(&client->dev, id, "no responde el WHO_AM_I\n");
if (id != TMP117_DEVICE_ID)
return dev_err_probe(&client->dev, -ENODEV,
"id inesperado 0x%04x\n", id);
dev_info(&client->dev, "TMP117 en 0x%02x\n", client->addr);
return 0;
}
static void tmp117_remove(struct i2c_client *client)
{
/* devm_ libera lo asignado; aqui solo iria el apagado activo del chip */
}
El registro del driver enlaza las dos vías de emparejamiento —device tree y tabla de identificadores— con module_i2c_driver, que genera el module_init/module_exit por ti:
static const struct i2c_device_id tmp117_id[] = {
{ "tmp117" },
{ }
};
MODULE_DEVICE_TABLE(i2c, tmp117_id);
static const struct of_device_id tmp117_of_match[] = {
{ .compatible = "ti,tmp117" },
{ }
};
MODULE_DEVICE_TABLE(of, tmp117_of_match);
static struct i2c_driver tmp117_driver = {
.driver = {
.name = "tmp117",
.of_match_table = tmp117_of_match,
},
.probe = tmp117_probe,
.remove = tmp117_remove,
.id_table = tmp117_id,
};
module_i2c_driver(tmp117_driver);
MODULE_DESCRIPTION("Driver minimo del sensor TMP117 por I2C");
MODULE_LICENSE("GPL");
El uso de devm_kzalloc y dev_err_probe no es adorno: el primero ata la memoria al ciclo de vida del dispositivo y la libera sola al desconectar; el segundo registra el error con formato uniforme y, si el código es -EPROBE_DEFER, calla en vez de ensuciar dmesg mientras el núcleo reintenta más tarde.
SMBus: el vocabulario de alto nivel
La familia i2c_smbus_* cubre el 90 % de los sensores sin que armes una sola trama. Cada función devuelve un s32: negativo es un -errno; cero o positivo, el dato. Ojo con el endianismo: i2c_smbus_read_word_data asume el byte bajo primero, pero muchísimos sensores envían el alto primero, y para ellos existe la variante _swapped:
/* leer y escribir un registro de 8 bits */
s32 cfg = i2c_smbus_read_byte_data(client, TMP117_REG_CONFIG);
int ret = i2c_smbus_write_byte_data(client, TMP117_REG_CONFIG, 0x02);
/* leer 16 bits big-endian: el sensor manda el MSB primero */
s32 raw = i2c_smbus_read_word_swapped(client, TMP117_REG_TEMP);
/* leer un bloque contiguo de golpe */
u8 buf[6];
int n = i2c_smbus_read_i2c_block_data(client, 0x08, sizeof(buf), buf);
Antes de confiar en SMBus conviene comprobar que el controlador lo soporta, porque no todos los adaptadores implementan todas las transacciones:
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_WORD_DATA))
return -EOPNOTSUPP;
i2c_transfer: control total sobre las tramas
Cuando SMBus no basta —un dispositivo con un puntero de registro de dos bytes, o un marco no estándar— se baja un nivel a i2c_transfer, que ejecuta un array de struct i2c_msg como una sola transacción de bus. La lectura de un registro es el patrón canónico: un mensaje de escritura con el número de registro, seguido, sin condición de parada intermedia sino con un start repetido, de un mensaje de lectura:
static int tmp117_read_reg(struct i2c_client *client, u8 reg, u16 *val)
{
u8 rx[2];
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0, /* escritura: el puntero de registro */
.len = 1,
.buf = ®,
}, {
.addr = client->addr,
.flags = I2C_M_RD, /* lectura con start repetido implicito */
.len = 2,
.buf = rx,
},
};
int ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
if (ret != ARRAY_SIZE(msgs))
return ret < 0 ? ret : -EIO;
*val = (rx[0] << 8) | rx[1]; /* big-endian, MSB primero */
return 0;
}
i2c_transfer devuelve el número de mensajes transferidos, no cero: por eso se compara con ARRAY_SIZE. La bandera 0 marca escritura y I2C_M_RD lectura; al no emitirse parada entre ambos mensajes, el bus mantiene el start repetido que los chips con puntero de registro exigen para no perder la dirección entre la escritura y la lectura.
Emparejamiento por device tree
Aquí está la inversión de control. Al arrancar, el núcleo de OF recorre el device tree; por cada nodo hijo de un controlador I2C crea un i2c_client con su dirección y su cadena compatible. El núcleo del bus compara esa cadena contra el of_match_table de cada driver registrado y, al primer acierto, llama a tu probe con ese cliente. Tú no escaneas el bus ni sondeas direcciones: describes qué compatibles sabes manejar y esperas. La macro MODULE_DEVICE_TABLE(of, ...) incrusta esos compatibles como metadatos, para que udev cargue tu módulo automáticamente cuando aparezca el hardware.
flowchart LR DT[Nodo del device tree compatible ti tmp117] --> CORE[Nucleo I2C crea un i2c_client] CORE --> MATCH[Compara compatible con of_match_table de cada driver] MATCH --> PROBE[Encaja y llama a tu probe con el client] PROBE --> DRV[El driver habla el bus con smbus o i2c_transfer]
Que exista un nodo en el device tree solo garantiza que se llamará a tu probe, no que haya silicio vivo detrás. Un probe robusto verifica el hardware —el WHO_AM_I del ejemplo— y devuelve -ENODEV si la identidad no cuadra, en vez de dar por hecho que el dispositivo responde. Y jamás cachees el resultado de i2c_smbus_read_* sin mirar el signo: un s32 negativo es un error de bus, no un valor de registro plausible.
Alza la vista sobre las tres APIs que acabas de ver y fíjate en lo que tienen en común, porque ahí vive la filosofía entera del modelo de dispositivos de Linux. Tu driver no sabe nada de voltajes, de resistencias de pull-up, de pulsos de reloj ni de en qué placa corre. Sabe una única cosa: qué significan los registros del TMP117 y en qué orden tocarlos. Esa es su competencia —el protocolo— y termina justo donde empieza el cable. El controlador del bus sabe la otra mitad —el transporte— y no tiene ni idea de qué es un TMP117; solo mueve bytes entre una dirección y la memoria. Entre ambos media un contrato de una sola frase, la cadena compatible, que el device tree usa como llave para casar a un desconocido de hardware con el desconocido de software que sabe hablarle. La consecuencia es profunda: tu driver se vuelve una función casi pura, que dado un registro devuelve una secuencia de transacciones, y esa pureza es lo que lo hace portable sin cambiar una línea a través de arquitecturas, placas y décadas. El poder no está en i2c_transfer; está en que el núcleo, no tú, decide cuándo naces y a qué hardware sirves. Cuando internalizas que un driver es la mitad de protocolo de una conversación cuya mitad de transporte no le pertenece, dejas de escribir código que busca hardware y empiezas a escribir código que espera, declarado y descubrible, a ser invocado. Esa espera —la inversión de control— es la diferencia entre un script y un driver.
- Esboza un
i2c_drivercompleto para un sensor imaginario concompatible = acme,foo, conprobe,removey las dos tablas de emparejamiento. - Lee su registro de identidad con
i2c_smbus_read_byte_datay rechaza elprobecon-ENODEVsi no cuadra. - Reescribe esa lectura con
i2c_transfery dosi2c_msg, y explica por qué el start repetido es imprescindible. - Justifica por qué se compara el retorno de
i2c_transfercon el número de mensajes y no con cero. - Busca en el árbol un driver real de
drivers/iio/que usei2c_smbus_read_word_swappedy explica qué endianismo tiene su sensor.