Exemplo de Device Driver I2C para Linux Embarcado

Driver I2C
Este post faz parte da série Device Drivers para Linux Embarcado

Depois de um longo tempo, iremos agora retomar a série sobre Device Drivers com um exemplo de device driver I2C para Linux Embarcado. Caso não tenha lido os artigos anteriores, sugiro que os leia para obter um conhecimento básico.

Em especial, leia o artigo que explica as diferenças entre device drivers e drivers de plataforma. Outro pré-requisito muito importante é o uso de ponteiros. Eles são muito usados no kernel Linux e nós veremos ponteiros por todo o código do driver.

Aqui será apresentado um exemplo de device driver I2C baseado em um driver já existente, mas reduzido ao máximo para torná-lo mais simples. O driver em que foi baseado o exemplo é o driver at24.c, que é usado para ler memórias I2C da família at24c como, por exemplo, a at24c512. Ele usa a abordagem de sysfs e cria entradas no diretório /sys para representar a memória como um arquivo e, assim, permitir que o usuário facilmente leia e escreva no dispositivo. O driver pode ser testado em qualquer placa com Linux embarcado que tenha os pinos I2C disponíveis.

Começaremos mostrando o código do device driver, seguido de seu Makefile, que é usado para compilá-lo. E será apresentado um exemplo de software para testar o device driver. Iniciemos baixando o código do device driver com o seguinte comando:

$ git clone https://github.com/vinifr/drivers.git

E entre na pasta do driver I2C dentro do kernel Linux com o comando:

$ cd drivers/i2c_sys/

O código do driver pode ser acessado aqui, mas é exibido abaixo para referência.

/*
 * at24.c - handle most I2C EEPROMs
 *
 * Copyright (C) 2005-2007 David Brownell
 * Copyright (C) 2008 Wolfram Sang, Pengutronix
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include <linux/mod_devicetable.h>
#include <linux/log2.h>
#include <linux/bitops.h>
#include <linux/jiffies.h>
#include <linux/of.h>
#include <linux/i2c.h>
#include <linux/platform_data/at24.h>


struct at24_data {
	struct at24_platform_data chip;
	int use_smbus;

	/*
	 * Lock protects against activities from other Linux tasks,
	 * but not from changes by other I2C masters.
	 */
	struct mutex lock;
	struct bin_attribute bin;

	u8 *writebuf;
	unsigned write_max;
	unsigned num_addresses;

	/*
	 * Some chips tie up multiple I2C addresses; dummy devices reserve
	 * them for us, and we'll use them with SMBus calls.
	 */
	struct i2c_client *client[];
};

/*
 * This parameter is to help this driver avoid blocking other drivers out
 * of I2C for potentially troublesome amounts of time. With a 100 kHz I2C
 * clock, one 256 byte read takes about 1/43 second which is excessive;
 * but the 1/170 second it takes at 400 kHz may be quite reasonable; and
 * at 1 MHz (Fm+) a 1/430 second delay could easily be invisible.
 *
 * This value is forced to be a power of two so that writes align on pages.
 */
static unsigned io_limit = 128;
module_param(io_limit, uint, 0);
MODULE_PARM_DESC(io_limit, "Maximum bytes per I/O (default 128)");

/*
 * Specs often allow 5 msec for a page write, sometimes 20 msec;
 * it's important to recover from write timeouts.
 */
static unsigned write_timeout = 25;
module_param(write_timeout, uint, 0);
MODULE_PARM_DESC(write_timeout, "Time (in ms) to try writes (default 25)");

#define AT24_SIZE_BYTELEN 5
#define AT24_SIZE_FLAGS 8

#define AT24_BITMASK(x) (BIT(x) - 1)

/* create non-zero magic value for given eeprom parameters */
#define AT24_DEVICE_MAGIC(_len, _flags) 		\
	((1 << AT24_SIZE_FLAGS | (_flags)) 		\
	    << AT24_SIZE_BYTELEN | ilog2(_len))

static const struct i2c_device_id at24_ids[] = {
	/* needs 8 addresses as A0-A2 are ignored */
	{ "24c00", AT24_DEVICE_MAGIC(128 / 8, AT24_FLAG_TAKE8ADDR) },
	/* old variants can't be handled with this generic entry! */
	{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },
	{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },
	/* spd is a 24c02 in memory DIMMs */
	{ "spd", AT24_DEVICE_MAGIC(2048 / 8,
		AT24_FLAG_READONLY | AT24_FLAG_IRUGO) },
	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },
	/* 24rf08 quirk is handled at i2c-core */
	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },
	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },
	{ "24c32", AT24_DEVICE_MAGIC(32768 / 8, AT24_FLAG_ADDR16) },
	{ "24c64", AT24_DEVICE_MAGIC(65536 / 8, AT24_FLAG_ADDR16) },
	{ "24c128", AT24_DEVICE_MAGIC(131072 / 8, AT24_FLAG_ADDR16) },
	{ "24c256", AT24_DEVICE_MAGIC(262144 / 8, AT24_FLAG_ADDR16) },
	{ "24c512", AT24_DEVICE_MAGIC(524288 / 8, AT24_FLAG_ADDR16) },
	{ "24c1024", AT24_DEVICE_MAGIC(1048576 / 8, AT24_FLAG_ADDR16) },
	{ "at24", 0 },
	{ /* END OF LIST */ }
};
MODULE_DEVICE_TABLE(i2c, at24_ids);

/*-------------------------------------------------------------------------*/

/*
 * This routine supports chips which consume multiple I2C addresses. It
 * computes the addressing information to be used for a given r/w request.
 * Assumes that sanity checks for offset happened at sysfs-layer.
 */
static struct i2c_client *at24_translate_offset(struct at24_data *at24,
		unsigned *offset)
{
	unsigned i;

	if (at24->chip.flags & AT24_FLAG_ADDR16) {
		i = *offset >> 16;
		*offset &= 0xffff;
	} else {
		i = *offset >> 8;
		*offset &= 0xff;
	}

	return at24->client[i];
}

static ssize_t at24_eeprom_read(struct at24_data *at24, char *buf,
		unsigned offset, size_t count)
{
	struct i2c_msg msg[2];
	u8 msgbuf[2];
	struct i2c_client *client;
	unsigned long timeout, read_time;
	int status, i;

	memset(msg, 0, sizeof(msg));

	client = at24_translate_offset(at24, &offset);

	if (count > io_limit)
		count = io_limit;

	i = 0;
	if (at24->chip.flags & AT24_FLAG_ADDR16)
		msgbuf[i++] = offset >> 8;
	msgbuf[i++] = offset;

	msg[0].addr = client->addr;
	msg[0].buf = msgbuf;
	msg[0].len = i;

	msg[1].addr = client->addr;
	msg[1].flags = I2C_M_RD;
	msg[1].buf = buf;
	msg[1].len = count;

	/*
	 * Reads fail if the previous write didn't complete yet. We may
	 * loop a few times until this one succeeds, waiting at least
	 * long enough for one entire page write to work.
	 */
	timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		read_time = jiffies;
		
		status = i2c_transfer(client->adapter, msg, 2);
		if (status == 2)
			status = count;
		dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
				count, offset, status, jiffies);

		if (status == count)
			return count;

		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(read_time, timeout));

	return -ETIMEDOUT;
}

static ssize_t at24_read(struct at24_data *at24,
		char *buf, loff_t off, size_t count)
{
	ssize_t retval = 0;	

	/*
	 * Read data from chip, protecting against concurrent updates
	 * from this host, but not from other I2C masters.
	 */
	mutex_lock(&at24->lock);

	while (count) {
		ssize_t	status;

		status = at24_eeprom_read(at24, buf, off, count);
		if (status <= 0) {
			if (retval == 0)
				retval = status;
			break;
		}
		buf += status;
		off += status;
		count -= status;
		retval += status;
	}

	mutex_unlock(&at24->lock);

	return retval;
}

static ssize_t at24_bin_read(struct file *filp, struct kobject *kobj,
		struct bin_attribute *attr,
		char *buf, loff_t off, size_t count)
{
	struct at24_data *at24;

	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
	return at24_read(at24, buf, off, count);
}


/*
 * Note that if the hardware write-protect pin is pulled high, the whole
 * chip is normally write protected. But there are plenty of product
 * variants here, including OTP fuses and partial chip protect.
 *
 * We only use page mode writes; the alternative is sloooow. This routine
 * writes at most one page.
 */
static ssize_t at24_eeprom_write(struct at24_data *at24, const char *buf,
		unsigned offset, size_t count)
{
	struct i2c_client *client;
	struct i2c_msg msg;
	ssize_t status;
	unsigned long timeout, write_time;
	unsigned next_page;
	int i;

	/* Get corresponding I2C address and adjust offset */
	client = at24_translate_offset(at24, &offset);

	/* write_max is at most a page */
	if (count > at24->write_max)
		count = at24->write_max;

	/* Never roll over backwards, to the start of this page */
	next_page = roundup(offset + 1, at24->chip.page_size);
	if (offset + count > next_page)
		count = next_page - offset;

	i = 0;
	msg.addr = client->addr;
	msg.flags = 0;

	/* msg.buf is u8 and casts will mask the values */
	msg.buf = at24->writebuf;
	if (at24->chip.flags & AT24_FLAG_ADDR16)
		msg.buf[i++] = offset >> 8;

	msg.buf[i++] = offset;
	memcpy(&msg.buf[i], buf, count);
	msg.len = i + count;

	/*
	 * Writes fail if the previous one didn't complete yet. We may
	 * loop a few times until this one succeeds, waiting at least
	 * long enough for one entire page write to work.
	 */
	timeout = jiffies + msecs_to_jiffies(write_timeout);
	do {
		write_time = jiffies;		
		
		status = i2c_transfer(client->adapter, &msg, 1);
		if (status == 1)
			status = count;
		
		dev_dbg(&client->dev, "write %zu@%d --> %zd (%ld)\n",
				count, offset, status, jiffies);

		if (status == count)
			return count;

		/* REVISIT: at HZ=100, this is sloooow */
		msleep(1);
	} while (time_before(write_time, timeout));

	return -ETIMEDOUT;
}

static ssize_t at24_write(struct at24_data *at24, const char *buf, loff_t off,
			  size_t count)
{
	ssize_t retval = 0;	

	/*
	 * Write data to chip, protecting against concurrent updates
	 * from this host, but not from other I2C masters.
	 */
	mutex_lock(&at24->lock);

	while (count) {
		ssize_t	status;

		status = at24_eeprom_write(at24, buf, off, count);
		if (status <= 0) {
			if (retval == 0)
				retval = status;
			break;
		}
		buf += status;
		off += status;
		count -= status;
		retval += status;
	}

	mutex_unlock(&at24->lock);

	return retval;
}

static ssize_t at24_bin_write(struct file *filp, struct kobject *kobj,
		struct bin_attribute *attr,
		char *buf, loff_t off, size_t count)
{
	struct at24_data *at24;

	if (unlikely(off >= attr->size))
		return -EFBIG;

	at24 = dev_get_drvdata(container_of(kobj, struct device, kobj));
	return at24_write(at24, buf, off, count);
}

#ifdef CONFIG_OF
static void at24_get_ofdata(struct i2c_client *client,
		struct at24_platform_data *chip)
{
	const __be32 *val;
	struct device_node *node = client->dev.of_node;

	if (node) {
		if (of_get_property(node, "read-only", NULL))
			chip->flags |= AT24_FLAG_READONLY;
		val = of_get_property(node, "pagesize", NULL);
		if (val)
			chip->page_size = be32_to_cpup(val);
	}
}
#else
static void at24_get_ofdata(struct i2c_client *client,
		struct at24_platform_data *chip)
{ }
#endif /* CONFIG_OF */

static int at24_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct at24_platform_data chip;
	//bool writable;
	int use_smbus = 0;
	struct at24_data *at24;
	int err;
	unsigned i, num_addresses;
	kernel_ulong_t magic;
	unsigned write_max;

	if (client->dev.platform_data) {
		chip = *(struct at24_platform_data *)client->dev.platform_data;
	} else {
		if (!id->driver_data)
			return -ENODEV;

		magic = id->driver_data;
		chip.byte_len = BIT(magic & AT24_BITMASK(AT24_SIZE_BYTELEN));
		magic >>= AT24_SIZE_BYTELEN;
		chip.flags = magic & AT24_BITMASK(AT24_SIZE_FLAGS);
		/*
		 * This is slow, but we can't know all eeproms, so we better
		 * play safe. Specifying custom eeprom-types via platform_data
		 * is recommended anyhow.
		 */
		chip.page_size = 1;

		/* update chipdata if OF is present */
		at24_get_ofdata(client, &chip);

		chip.setup = NULL;
		chip.context = NULL;
	}
	

	/* Use I2C operations unless we're stuck with SMBus extensions. */	
	if (i2c_check_functionality(client->adapter,
		I2C_FUNC_SMBUS_BYTE_DATA)) {
		use_smbus = I2C_SMBUS_BYTE_DATA;
	} else {
		return -EPFNOSUPPORT;
	}
	

	if (chip.flags & AT24_FLAG_TAKE8ADDR)
		num_addresses = 8;
	else
		num_addresses =	DIV_ROUND_UP(chip.byte_len,
			(chip.flags & AT24_FLAG_ADDR16) ? 65536 : 256);

	at24 = devm_kzalloc(&client->dev, sizeof(struct at24_data) +
		num_addresses * sizeof(struct i2c_client *), GFP_KERNEL);
	if (!at24)
		return -ENOMEM;

	mutex_init(&at24->lock);
	at24->use_smbus = use_smbus;
	at24->chip = chip;
	at24->num_addresses = num_addresses;

	/*
	 * Export the EEPROM bytes through sysfs, since that's convenient.
	 * By default, only root should see the data (maybe passwords etc)
	 */
	sysfs_bin_attr_init(&at24->bin);
	at24->bin.attr.name = "eeprom";
	at24->bin.attr.mode = chip.flags & AT24_FLAG_IRUGO ? S_IRUGO : S_IRUSR;
	at24->bin.read = at24_bin_read;
	at24->bin.size = chip.byte_len;
	at24->bin.write = at24_bin_write;
	at24->bin.attr.mode |= S_IWUSR;
	
	write_max = chip.page_size;
	if (write_max > io_limit)
		write_max = io_limit;
	at24->write_max = write_max;
	/* buffer (data + address at the beginning) */
	at24->writebuf = devm_kzalloc(&client->dev,
		write_max + 2, GFP_KERNEL);
	if (!at24->writebuf)
		return -ENOMEM;
			
	at24->client[0] = client;

	err = sysfs_create_bin_file(&client->dev.kobj, &at24->bin);
	if (err)
		goto err_clients;

	i2c_set_clientdata(client, at24);

	dev_info(&client->dev, "%zu byte %s EEPROM, %u bytes/write\n",
		at24->bin.size, client->name, at24->write_max);
	if (use_smbus == I2C_SMBUS_WORD_DATA ||
	    use_smbus == I2C_SMBUS_BYTE_DATA) {
		dev_notice(&client->dev, "Falling back to %s reads, "
			   "performance will suffer\n", use_smbus ==
			   I2C_SMBUS_WORD_DATA ? "word" : "byte");
	}	

	return 0;

err_clients:
	for (i = 1; i < num_addresses; i++)
		if (at24->client[i])
			i2c_unregister_device(at24->client[i]);

	return err;
}

static int at24_remove(struct i2c_client *client)
{
	struct at24_data *at24;
	int i;

	at24 = i2c_get_clientdata(client);
	sysfs_remove_bin_file(&client->dev.kobj, &at24->bin);

	for (i = 1; i < at24->num_addresses; i++)
		i2c_unregister_device(at24->client[i]);

	return 0;
}

/*-------------------------------------------------------------------------*/

static struct i2c_driver at24_driver = {
	.driver = {
		.name = "at24c",
		.owner = THIS_MODULE,
	},
	.probe = at24_probe,
	.remove = at24_remove,
	.id_table = at24_ids,
};

static int __init at24_init(void)
{
	if (!io_limit) {
		pr_err("at24: io_limit must not be 0!\n");
		return -EINVAL;
	}

	io_limit = rounddown_pow_of_two(io_limit);
	return i2c_add_driver(&at24_driver);
}
module_init(at24_init);

static void __exit at24_exit(void)
{
	i2c_del_driver(&at24_driver);
}
module_exit(at24_exit);

MODULE_DESCRIPTION("Driver for I2C EEPROMs");
MODULE_AUTHOR("David Brownell and Wolfram Sang");
MODULE_LICENSE("GPL");

Visão geral do driver

Visão geral do driver I2C para Linux Embarcado.
Visão geral do driver I2C para Linux Embarcado.

Explicação do código do driver

Geralmente os códigos de device drivers para Linux Embarcado seguem uma lógica que começa de baixo para cima. Assim, normalmente encontramos as funções de inicialização e de probe()/remove() nas últimas linhas do código. Então passemos agora para a explicação das principais linhas do código do driver.

Nas linhas 513, 514 e 515 temos as macros de metadados já explicadas no artigo Exemplo de driver para Linux Embarcado. E também nas linhas 505 e 511 temos as funções module_init()/module_exit(), também já explicadas no referido artigo.

1. i2c_add_driver(): linha 503

Essa função é responsável por registrar um dispositivo I2C no subsistema I2C do kernel Linux, que irá se comunicar com um dispositivo I2C slave. Ela recebe como parâmetro um ponteiro para estrutura struct i2c_driver. Essa estrutura guarda informações sobre o driver no membro driver e ponteiros para as funções de callback probe() e remove() nos respectivos membros da estrutura. O membro id_table guarda a lista de dispositivos I2C suportados por esse driver.

Essa é a forma de dizer ao sistema que existe um driver chamado at24, que é capaz de se comunicar com os dispositivos listados em id_table e que tem como funções de callback at24_probe() e at24_remove().

2. i2c_del_driver(): linha 509

Essa função remove um dispositivo I2C no subsistema I2C do kernel. Assim como i2c_add_driver(), ela recebe como parâmetro um ponteiro para struct i2c_driver.

3. at24_probe(): linha 359

Todo device driver precisa fornecer ao menos a definição das funções de probe() e remove(). A função probe() é algo obscuro e difícil de depurar. Ela é chamada quando registramos o driver I2C usando i2c_add_driver(). Aqui está a sequência de chamadas para o caso do barramento I2C:

  • i2c_add_driver()
  • i2c_register_driver()
  • driver_register()
  • bus_add_driver()
  • driver_attach()
  • __driver_attach()
  • driver_probe_device()
  • really_probe() – aqui parece um beco sem saída, mas seguimos adiante
  • i2c_device_probe() – chamada por dev->bus->probe dentro de really_probe
  • at24_probe() – chamada por driver->probe() dentro de i2c_device_probe

Observe que a função i2c_device_probe() do barramento I2C, que está dentro de i2c-core.c, é definida na estrutura i2c_bus_type:

784 struct bus_type i2c_bus_type = {
785         .name           = "i2c",
786         .match          = i2c_device_match,
787         .probe          = i2c_device_probe,
788         .remove         = i2c_device_remove,
789         .shutdown       = i2c_device_shutdown,
790 };
791 EXPORT_SYMBOL_GPL(i2c_bus_type);

Quando a instrução driver->probe() é executada, é a função i2c_device_probe() que será chamada, uma vez que estamos usando o barramento I2C e esta função foi definida como callback de .probe, como mostra o código acima.

O ponteiro struct i2c_client *client, o primeiro parâmetro da função at24_probe(), provém da função i2c_new_device(), que é chamada em arquivos de board. Dentro da função at24_probe(), temos uma série de outras funções importantes.

3.1. at24_get_ofdata(): linhas 339, 354 e 388

Essa função recebe como parâmetro um ponteiro para struct i2c_client e outro ponteiro para struct at24_platform_data. Ela só executa algo na definição da linha 339, e serve para atualizar os parâmetros relacionados a modos de acesso (leitura/escrita) e o tamanho da página da memória. A sua existência, na linha 339, está condicionada ao teste de pré-processador #ifdef CONFIG_OF, o qual verifica se o kernel está usando Device Tree. Essas propriedades são lidas no Device Tree usando a função of_get_property(). As propriedades do nó são recuperadas por meio do ponteiro client->dev.of_node, atribuido na linha 343.

Obtenha mais informação sobre pré-processador e Device Tree.

3.2. i2c_check_functionality(): linha 396

Essa função verifica se o controlador I2C suporta determinada funcionalidade. No teste da linha 396 estamos verificando se há suporte para escrita e leitura de bytes por parte do controlador I2C. Então isso vai depender da placa que está sendo usada. Em geral, a maioria dos controladores I2C suportam leitura e escrita de bytes. Também é possivel verificar se há suporte para leitura e escrita de words ou operações em blocos. Veja a lista completa aqui.

3.3. devm_kzalloc(): linha 410

Essa função é usada para alocar memória. Porém, diferente da antiga kzalloc(), a memória é liberada automaticamente quando o driver é removido. Ela retorna um ponteiro para a área de memória alocada em caso de sucesso ou NULL em caso de falha.

3.4. sysfs_bin_attr_init(): linha 424

Inicializa uma estrutura bin_attribute alocada dinamicamente. Ela é necessária apenas quando o atributo lockdep está presente no kernel. Por sua vez, o lockdep irá imprimir uma descrição da situação e um traço da stack para o log do kernel quando ele encontra uma sequência de locking que pode potencialmente causar um deadlock. Nas linhas 425 a 430 temos a inicialização de alguns membros da estrutura bin_attribute. Entre eles temos bin.read e bin.write, que definem as funções para leitura e escrita, respectivamente, do arquivo binário criado sob o diretório /sys.

3.5. sysfs_create_bin_file(&client->dev.kobj, &at24->bin): linha 444

Cria um arquivo binário para o objeto passado como parâmetro sob o diretório /sys. Recebe um ponteiro para struct kobject e um ponteiro constante para struct bin_attribute. O nome do arquivo é indicado em bin.attr.name. Essa função é mais indicada quando um arquivo precisa transferir grandes quantidades de dados, como uma memória.

3.6. i2c_set_clientdata(client, at24): linha 448

Essa função é usada para guardar um ponteiro void *driver_data que armazena informações do driver. No nosso caso, estamos guardando as informações da estrutura at24 dentro da estrutura de client. Mais tarde, se for necessário, podemos recuperar esse ponteiro usando i2c_get_clientdata(). Também é possível recuperar esse ponteiro usando a função container_of() ou uma variante dela.

4. at24_remove: linha 469

Essa função é chamada quando o driver é removido do subsistema I2C do kernel, processo que é iniciado por i2c_del_driver(). Ela passa por várias funções, assim como probe(), e é chamada por dev->bus->remove(dev), dentro de __device_release_driver(). As funções dentro dela executam a liberação de recursos.

4.1. i2c_get_clientdata(): linha 474

Como dito anteriormente, recupera o ponteiro salvo por i2c_set_clientdata(). Ela recebe como parâmetro um ponteiro para struct i2c_client, e retorna um ponteiro para void.

4.2. sysfs_remove_bin_file(): 475

Remove o arquivo binário do objeto passado como parâmetro e também apaga a entrada no diretório /sys, criado por sysfs_create_bin_file().

4.3. i2c_unregister_device(): linha 478

Remove um dispositivo do subsistema I2C do kernel, que é o oposto ao que é feito pela função i2c_new_device().

5. at24_bin_write(): linha 325

Essa função é chamada pelo kernel quando uma operação de escrita é realizada sobre o arquivo binário criado por sysfs_create_bin_file(). Pode ser um comando echo ou a função de chamada de sistema write().

5.1. dev_get_drvdata(), container_of(): linha 334

Essa função retorna o ponteiro armazenado previamente por i2c_set_clientdata() em driver_data, executando a seguinte linha: return dev->driver_data. Ela precisa de um ponteiro para struct device. Como não temos nenhum ponteiro para struct device dentro de at24_bin_write(), precisamos de algum artifício para recuperar esse ponteiro. Isso é feito justamente por container_of(), que aproveita o ponteiro struct kobject *kobj vindo de at24_bin_write e recupera o ponteiro para struct device. Agora que temos um ponteiro válido na variável at24, chamamos at24_write(at24, buf, off, count).

5.2. at24_write(): linhas 335 e 294

Escreve os dados passados em buf na memória I2C, até atingir o número de bytes igual a count. Por enquanto, não iremos falar de mutex_lock() e mutex_unlock().

5.2.1. at24_eeprom_write(): linhas 308 e 233

Escreve o número máximo de bytes indicado por write_max ou count se for menor. Primeiro é recuperado o endereço do cliente por meio de at24_translate_offset(). Depois o valor de count é limitado por at24->write_max. A variável msg contém os parâmetros que serão passados para i2c_transfer(). O membro msg.buf recebe a memória alocada em at24_probe() por at24->writebuf. Na posição zero, msg.buf[0], é guardado o endereço de memória a ser escrito. E nas posições posteriores são guardados os dados a serem escritos, por meio de memcpy(). E msg.len guarda o número de bytes a serem escritos, incluindo o endereço mais os dados (i + count).

5.2.2. msecs_to_jiffies(): linha 273

Converte o valor passado em milissegundos para jiffies. A variável global jiffies mantém o número de ticks que ocorreram desde que o sistema foi inicializado. Tick, por sua vez, refere-se a uma interrupção do timer do sistema que, por padrão em processadores ARM, ocorre a cada 1 ms. Considerando que a variável write_timeout é por padrão 25, quando fazemos

timeout = jiffies + msecs_to_jiffies(write_timeout);

significa um tempo de 25 ms a partir do momento em que a função está sendo chamada.

5.2.3. i2c_transfer(): linha 277

Essa função é quem de fato faz a transferência de bytes no barramento I2C, tanto escrita como leitura. A operação de escrita ou leitura é indicada no membro msg.flags, sendo que zero indica escrita. Veja na linha 257 (msg.flags = 0). Ela precisa de um ponteiro para struct i2c_adapter o qual indica o driver do controlador I2C, um ponteiro para struct i2c_msg que indica a mensagem para transferir e um inteiro para indicar o número de mensagens para transferir.

5.2.4. time_before(t1, t2): linha 289

Testa se o tempo atual t1 alcançou um tempo posterior t2. Se o tempo posterior não foi atingido, retorna verdadeiro. O primeiro parâmetro é o jiffie atual e o segundo parâmetro, o jiffie posterior.

6. at24_bin_read(): linha 214

Essa função é chamada pelo kernel quando uma operação de leitura é realizada sobre o arquivo binário criado por sysfs_create_bin_file(). Pode ser um comando cat ou a função de chamada de sistema read(). As funções chamadas em at24_bin_read() são as mesmas chamadas em at24_bin_write() com pequenas diferenças. Vejamos então apenas as funções em que há diferenças.

6.1. at24_eeprom_read(): linhas 197 e 128

Ler o número de bytes máximo indicado por io_limit ou count se menor. Nessa função temos um array struct i2c_msg msg[2], que irá guardar duas mensagens. Na primeira mensagem, indicada por msg[0], temos a operação de escrita, pois msg[0].flag foi omitido e é inicializado com zero. E na segunda mensagem, que é indicada por msg[1], temos a operação de leitura. Veja na linha 154 (msg[1].flags = I2C_M_RD). As duas mensagens são transferidas quando indicamos 2 no último parametro de i2c_transfer(). Veja na linha 167 (i2c_transfer(client->adapter, msg, 2)). Isso é necessário pois a operação de leitura de uma memória requer que primeiro se escreva o endereço que irá ser lido, para então começarmos a ler. As outras funções são as mesmas de at24_eeprom_write().

7. module_param(): linhas 60 e 68

Essa macro é usada para que o usuário possa passar parâmetros para o módulo quando este é carregado no sistema. Para mais informações veja aqui. Associado a essa macro, temos MODULE_PARM_DESC(), que fornece uma descrição do parâmetro. Os parametros são armazenados em variavéis globais.

Código do Makefile do device driver

O código do Makefile é praticamente o mesmo do que foi usado neste artigo. Porém esse driver foi testado na placa Raspberry Pi 2, e por este motivo, aponta para o código fonte do kernel da Broadcom. Mas como dito antes, pode ser testado em qualquer placa.

Para compilar o driver digite:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

Agora estamos prontos para testar driver!

Exemplo de uso do driver I2C

A fim de que o driver funcione, é necessário adicionar informações para que o driver “saiba” que memória será usada. Isso pode ser feito de duas formas: através da estrutura i2c_board_info e a função i2c_register_board_info() para registrar as informações necessárias; ou através de um simples código de Device Tree.

Para a primeira abordagem, veja este exemplo.

Vejamos como proceder para o caso do Device Tree. Se você está usando a Raspberry 2, abra o arquivo bcm2709-rpi-2-b.dts, e dentro de bloco da i2c1, coloque:

at24@51 {
	    compatible = "at24,24c512";
	    pagesize = <512>;
	    reg = <0x50>;
	};

Assim, o código completo do bloco i2c1 deve ficar assim:

&i2c1 {
	pinctrl-names = "default";
	pinctrl-0 = <&i2c1_pins>;
	clock-frequency = <100000>;
	
	at24@51 {
	    compatible = "at24,24c512";
	    pagesize = <512>;
	    reg = <0x50>;
	};
};

Este código é para a antiga memória at24c512 da Atmel. Mude conforme a memória que você for testar. Você pode usar esse códido para praticamente qualquer placa com suporte a Device Tree, como a Beaglebone Black e outras. Agora recompile o Device Tree usando:

$ make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

Depois transfira o arquivo de Device Tree compilado para a placa. No caso da Raspberry 2, será bcm2709-rpi-2-b.dtb, que deve ser colocado na pasta /boot do SD-Card.

Agora finalmente podemos testar a leitura e escrita da mémoria I2C. Digite os seguintes comandos, como root:

# cd /sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0050
# echo "Teste de memoria I2C na Raspberry" > eeprom
# hexdump -C -n 100 eeprom

O resultado deveria ser algo como:

00000000  54 65 73 74 65 20 64 65  20 6d 65 6d 6f 72 69 61  |Teste de memoria|
00000010  20 49 32 43 20 6e 61 20  52 61 73 70 62 65 72 72  | I2C na Raspberr|
00000020  79 0a ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |y...............|
00000030  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

Exemplo de software

Agora vejamos um exemplo de software para testar a leitura e escrita da memória através do driver I2C.

Código:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>


#define DEV_I2C		"/sys/devices/platform/soc/3f804000.i2c/i2c-1/1-0050/eeprom"

#define LEN		9


int main()
{
    int fd, i;
    char dado_wr[10];
    char dado_rd[10];
    
    fd = open (DEV_I2C, O_RDWR);	// abri o driver para operacoes de w/r
    if (fd == -1)
    {
        printf("Erro ao abrir eeprom0\n");
        return fd;
        /* error */
    }
    else
        printf("Driver aberto!\n");
	
    for (i=0; i < LEN; i++)
        dado_wr[i] = '1' + i;		// preenche memoria com valores de 1 a LEN
    write(fd, dado_wr, LEN);		// escreve os dados na memoria
    usleep(1000*10);    
    pread(fd, dado_rd, LEN, 0); 	// ler o dado escrito anteriormente
    for (i=0; i < LEN; i++)
        printf("%c", dado_rd[i]);	// imprime dados lidos da memoria
    putchar('\n');
    
    close(fd);				// fecha o driver
    printf("Driver fechado!\n");    
    
    return 0;
}

Salve o código fonte como i2c_teste.c. Para fazer a compilação na própria Raspberry, digite:

$ gcc i2c_teste.c -o i2c_teste

Para compilar no PC, fazendo uma compilação cruzada, digite:

$ arm-linux-gnueabihf-gcc i2c_teste.c -o i2c_teste

Observe que as funções de chamada de sistema open(), read(), write() e close(), mostradas na figura acima (ações do usuário), são usadas no software de exemplo. Mas elas também são usadas nos comandos echo e hexdump, basta verificar no código fonte de cada um.

E assim concluímos mais um artigo sobre device drivers para linux embarcado. Para o próximo artigo, veremos um driver SPI.

Referências

[1] Linux Device Drivers – Diferenças entre Drivers de Plataforma e de Dispositivo

[2] Driver at24.c

[3] Exemplo de driver para Linux Embarcado

[4] Pré-processador C – Parte 1

[5] Introdução ao uso de Device Tree e Device Tree Overlay em Sistemas Linux Embarcado

[6] The Linux Kernel Module Programming Guide

[7] Raspberry Pi I2C 256K EEPROM Tutorial

Fonte da imagem destacada: https://openelectronicsproject.blogspot.com.br/2015/08/i2c-protocol.html

Device Drivers para Linux Embarcado

Comunicação SPI em Linux
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Home » Linux Embarcado » Exemplo de Device Driver I2C para Linux Embarcado

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: