Subsistema Regmap em Drivers Linux

  1. Visão Geral do Regmap

No desenvolvimento de drivers para Linux, a maioria dos dispositivos exige operação direta em seus registradores internos. Isto é verdade tanto para dispositivos I2C quanto para SPI. Por exemplo, a inicialização de periféricos como PWM e TIM em um SoC como o STM32MP157, em última análise, envolve a configuração desses registradores. Tradicionalmente, funções como i2c_transfer ou spi_write/spi_read são usadas para ler e escrever nos registradores de um dispositivo via suas respectivas interfaces. Como existem muitos chips I2C/SPI, o kernel Linux acaba contendo uma quantidade significativa de código redundante e específico de interface, o que reduz a reutilização do código. Imagine um chip como o icm20608 que suporta tanto I2C quanto SPI. Se em um projeto ele fosse inicialmente conectado via SPI, mas depois fosse necessário mudar para I2C (por escassez de pinos ou interfaces, por exemplo), o driver precisaria de modificações extensas, substituindo todas as chamadas de API SPI por I2C.

Para resolver esse problema e promover a reutilização de código, o kernel Linux introduziu o modelo regmap. O regmap abstrai a lógica comum de acesso a registradores, permitindo que os desenvolvedores de drivers ignorem se a interface subjacente é SPI ou I2C, usando uma API unificada. Isso reduz a redundância de código e melhora a portabilidade dos drivers. O regmap também pode ser utilizado para acessar registradores de hardware internos ao SoC.

1.1 Vantagens e Desvantagens do Regmap

O regmap é uma interface genérica no kernel Linux projetada para operar registradores de hardware, visando reduzir a sobrecarga de drivers em operações de I/O lentas. Ele introduz uma camada de cache entre o driver e o hardware, diminuindo a frequência de acessos a I/O de baixa velocidade e, consequentemente, aumentando a eficiência. A principal desvantagem é a potencial redução da atualidade dos dados (real-time), devido a essa camada de cache.

1.2 Cenários de Uso do Regmap

Os principais cenários de aplicação do regmap são:

  1. Operação em registradores de hardware, seja via interfaces externas (I2C, SPI) ou em periféricos internos do SoC.

  2. Melhoria na reutilização de código e consistência do driver, simplificando o processo de desenvolvimento.

  3. Redução do número de operações de I/O de baixo nível, aumentando a eficiência de acesso.

  4. Arquitetura do Framework Regmap


2.1 Estrutura do Framework

O framework regmap é organizado em três camadas principais:

  1. Camada de Barramento Físico Inferior: O regmap encapsula diferentes tipos de barramento físico. Atualmente (kernel 5.4.31), os barramentos suportados incluem i2c, i3c, spi, mmio, sccb, sdw, slimbus, irq, spmi e w1.
  2. Camada Central (Core) do Regmap: Responsável pela implementação do regmap, ocultando os detalhes específicos da camada inferior.
  3. Camada de Abstração da API: Fornece a interface API que os desenvolvedores de drivers utilizam para interagir com os dispositivos.

2.2 Estrutura de Dados Regmap

O framework é abstraído pela estrutura regmap, definida em drivers/base/regmap/internal.h. Esta estrutura contém múltiplos campos, incluindo ponteiros para funções de callback e tabelas de acesso (writeable_reg, readable_reg, volatile_reg, etc.), que devem ser inicializados seletivamente pelo desenvolvedor conforme as necessidades do dispositivo.

2.3 Estrutura de Configuração (regmap_config)

A estrutura regmap_config (definida em drivers/base/regmap/internal.h) é usada para configurar uma instância do regmap. Os campos mais importantes incluem:

  • reg_bits e val_bits: Campos obrigatórios que definem a largura em bits do endereço do registrador e do valor, respectivamente.
  • reg_stride: O incremento entre endereços consecutivos de registradores.
  • pad_bits: Bits de preenchimento entre o endereço e o valor no pacote de comunicação.
  • writeable_reg / readable_reg: Callbacks opcionais para verificar a permissão de escrita/leitura em um registrador específico.
  • volatile_reg: Callback opcional para identificar registradores cujos valores mudam sem controle do driver (e, portanto, não devem ser ccaheados).
  • max_register: Define o endereço máximo de registrador válido, útil para validação.
  • reg_defaults: Um array de estruturas reg_default que contém os valores padrão de inicialização para certos registradores.
  • read_flag_mask e write_flag_mask: Máscaras aplicadas ao byte de endereço/comando durante operações de leitura e escrita, específicas do protocolo do dispositivo.
  1. Funções da API do Regmap

3.1 Inicialização e Liberação do Regmap

O regmap fornece funções de inicialização específicas para diferentes interfaces de barramento. Por exemplo, regmap_init_spi() para dispositivos SPI e regmap_init_i2c() para dispositivos I2C. Geralmente, a configuração é feita na função probe do driver. A estrutura regmap resultante deve ser liberada chamando regmap_exit() durante a remoção do driver.

3.2 Funções de Acesso a Registradores

A API central do regmap fornece funções simples para ler e escrever em registradores individuais:

  • regmap_read(map, reg, &val): Lê o valor do registrador reg e armazena em val.
  • regmap_write(map, reg, val): Escreve o valor val no registrador reg.

3.3 Funções Derivadas da API

Com base nas funções básicas, o regmap oferece funções de alto nível para operações mais complexas:

  • regmap_update_bits(map, reg, mask, val): Atualiza apenas os bits definidos pela mask no registrador reg com os valores correspondentes de val. Os bits fora da máscara permanecem inalterados.
  • regmap_bulk_read(map, reg, buf, count): Lê um bloco de count valores de registradores consecutivos a partir do endereço reg, armazenando-os em buf.
  • regmap_bulk_write(map, reg, buf, count): Escreve um bloco de count valores a partir de buf em registradores consecutivos a partir do endereço reg.
  1. Exemplo Prático: Driver para ICM20608

O exemplo a seguir demonstra como implementar um driver de caractere para o sensor IMU ICM20608 usando a interface SPI, aproveitando o subsistema regmap para o acesso a registradores.

4.1 Arquivo de Cabeçalho dos Registradores (icm20608_reg.h)

Este arquivo define os endereços dos registradores do dispositivo.

#ifndef ICM20608_REG_H
#define ICM20608_REG_H

/* IDs do dispositivo */
#define ICM20608G_DEVICE_ID     0XAF
#define ICM20608D_DEVICE_ID     0XAE

/* Endereços de Registradores */
#define ICM20_REG_SELF_TEST_X_GYRO   0x00
#define ICM20_REG_SELF_TEST_Y_GYRO   0x01
#define ICM20_REG_SELF_TEST_Z_GYRO   0x02
#define ICM20_REG_SMPLRT_DIV         0x19
#define ICM20_REG_CONFIG             0x1A
#define ICM20_REG_GYRO_CONFIG        0x1B
#define ICM20_REG_ACCEL_CONFIG       0x1C
#define ICM20_REG_ACCEL_CONFIG_2     0x1D
#define ICM20_REG_FIFO_EN            0x23
#define ICM20_REG_INT_PIN_CFG        0x37
#define ICM20_REG_INT_ENABLE         0x38
#define ICM20_REG_INT_STATUS         0x3A
#define ICM20_REG_ACCEL_XOUT_H       0x3B
#define ICM20_REG_TEMP_OUT_H         0x41
#define ICM20_REG_GYRO_XOUT_H        0x43
#define ICM20_REG_SIGNAL_PATH_RESET  0x68
#define ICM20_REG_USER_CTRL          0x6A
#define ICM20_REG_PWR_MGMT_1         0x6B
#define ICM20_REG_PWR_MGMT_2         0x6C
#define ICM20_REG_FIFO_COUNTH        0x72
#define ICM20_REG_FIFO_R_W           0x74
#define ICM20_REG_WHO_AM_I           0x75

#endif /* ICM20608_REG_H */

4.2 Implementação do Driver (icm20608_driver.c)

O driver configura o regmap e implementa as operações de arquivo open, read e release.

#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/regmap.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include "icm20608_reg.h"

#define DRIVER_NAME      "imu_sensor"
#define NUM_DEVICES      1

struct sensor_data {
    s16 gyro_x, gyro_y, gyro_z;
    s16 accel_x, accel_y, accel_z;
    s16 temperature;
};

struct imu_priv {
    struct spi_device *spi_dev;
    struct regmap *rmap;
    struct regmap_config rmap_cfg;
    dev_t dev_num;
    struct cdev char_dev;
    struct class *dev_class;
    struct device *device;
    struct sensor_data readings;
};

static void read_sensor_block(struct imu_priv *priv)
{
    u8 data_buf[14];
    /* Lê 14 bytes consecutivos a partir do registrador ACCEL_XOUT_H */
    regmap_bulk_read(priv->rmap, ICM20_REG_ACCEL_XOUT_H, data_buf, 14);

    priv->readings.accel_x = (s16)((data_buf[0] << 8) | data_buf[1]);
    priv->readings.accel_y = (s16)((data_buf[2] << 8) | data_buf[3]);
    priv->readings.accel_z = (s16)((data_buf[4] << 8) | data_buf[5]);
    priv->readings.temperature = (s16)((data_buf[6] << 8) | data_buf[7]);
    priv->readings.gyro_x = (s16)((data_buf[8] << 8) | data_buf[9]);
    priv->readings.gyro_y = (s16)((data_buf[10] << 8) | data_buf[11]);
    priv->readings.gyro_z = (s16)((data_buf[12] << 8) | data_buf[13]);
}

static void initialize_sensor_registers(struct imu_priv *priv)
{
    /* Reset do dispositivo */
    regmap_write(priv->rmap, ICM20_REG_PWR_MGMT_1, 0x80);
    mdelay(100);
    regmap_write(priv->rmap, ICM20_REG_PWR_MGMT_1, 0x01);
    mdelay(10);

    /* Verificação de ID */
    unsigned int chip_id;
    regmap_read(priv->rmap, ICM20_REG_WHO_AM_I, &chip_id);
    dev_info(&priv->spi_dev->dev, "Sensor ID detected: 0x%02X\n", chip_id);

    /* Configuração dos sensores e taxa de amostragem */
    regmap_write(priv->rmap, ICM20_REG_SMPLRT_DIV, 0x00);       /* Taxa máxima */
    regmap_write(priv->rmap, ICM20_REG_GYRO_CONFIG, 0x18);      /* Faixa ±2000 dps */
    regmap_write(priv->rmap, ICM20_REG_ACCEL_CONFIG, 0x18);     /* Faixa ±16g */
    regmap_write(priv->rmap, ICM20_REG_CONFIG, 0x04);           /* Filtro passa-baixa */
    regmap_write(priv->rmap, ICM20_REG_ACCEL_CONFIG_2, 0x04);
    regmap_write(priv->rmap, ICM20_REG_PWR_MGMT_2, 0x00);      /* Habilita todos os eixos */
    regmap_write(priv->rmap, ICM20_REG_FIFO_EN, 0x00);         /* Desabilita FIFO */
}

static int imu_dev_open(struct inode *inode, struct file *filp)
{
    struct imu_priv *priv = container_of(inode->i_cdev, struct imu_priv, char_dev);
    filp->private_data = priv;
    return 0;
}

static ssize_t imu_dev_read(struct file *filp, char __user *ubuf, size_t count, loff_t *f_pos)
{
    struct imu_priv *priv = filp->private_data;
    s16 data_out[7];

    read_sensor_block(priv);
    data_out[0] = priv->readings.gyro_x;
    data_out[1] = priv->readings.gyro_y;
    data_out[2] = priv->readings.gyro_z;
    data_out[3] = priv->readings.accel_x;
    data_out[4] = priv->readings.accel_y;
    data_out[5] = priv->readings.accel_z;
    data_out[6] = priv->readings.temperature;

    if (copy_to_user(ubuf, data_out, sizeof(data_out)))
        return -EFAULT;

    return sizeof(data_out);
}

static int imu_dev_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static const struct file_operations imu_fops = {
    .owner = THIS_MODULE,
    .open = imu_dev_open,
    .read = imu_dev_read,
    .release = imu_dev_release,
};

static int imu_spi_probe(struct spi_device *spi)
{
    int ret;
    struct imu_priv *priv;

    priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    spi_set_drvdata(spi, priv);
    priv->spi_dev = spi;

    /* Configuração do regmap para SPI de 8 bits */
    priv->rmap_cfg.reg_bits = 8;
    priv->rmap_cfg.val_bits = 8;
    priv->rmap_cfg.read_flag_mask = 0x80;

    priv->rmap = regmap_init_spi(spi, &priv->rmap_cfg);
    if (IS_ERR(priv->rmap)) {
        dev_err(&spi->dev, "Failed to initialize regmap\n");
        return PTR_ERR(priv->rmap);
    }

    /* Configuração do SPI */
    spi->mode = SPI_MODE_0;
    spi_setup(spi);

    /* Inicialização do hardware */
    initialize_sensor_registers(priv);

    /* Registro do dispositivo de caractere */
    ret = alloc_chrdev_region(&priv->dev_num, 0, NUM_DEVICES, DRIVER_NAME);
    if (ret) goto err_regmap;

    cdev_init(&priv->char_dev, &imu_fops);
    priv->char_dev.owner = THIS_MODULE;
    ret = cdev_add(&priv->char_dev, priv->dev_num, NUM_DEVICES);
    if (ret) goto err_chrdev;

    priv->dev_class = class_create(THIS_MODULE, DRIVER_NAME);
    if (IS_ERR(priv->dev_class)) {
        ret = PTR_ERR(priv->dev_class);
        goto err_cdev;
    }

    priv->device = device_create(priv->dev_class, &spi->dev, priv->dev_num, NULL, DRIVER_NAME);
    if (IS_ERR(priv->device)) {
        ret = PTR_ERR(priv->device);
        goto err_class;
    }

    dev_info(&spi->dev, "IMU sensor driver probed successfully.\n");
    return 0;

err_class:
    class_destroy(priv->dev_class);
err_cdev:
    cdev_del(&priv->char_dev);
err_chrdev:
    unregister_chrdev_region(priv->dev_num, NUM_DEVICES);
err_regmap:
    regmap_exit(priv->rmap);
    return ret;
}

static int imu_spi_remove(struct spi_device *spi)
{
    struct imu_priv *priv = spi_get_drvdata(spi);

    device_destroy(priv->dev_class, priv->dev_num);
    class_destroy(priv->dev_class);
    cdev_del(&priv->char_dev);
    unregister_chrdev_region(priv->dev_num, NUM_DEVICES);
    regmap_exit(priv->rmap);

    dev_info(&spi->dev, "IMU sensor driver removed.\n");
    return 0;
}

static const struct spi_device_id imu_spi_ids[] = {
    {"generic,icm20608", 0},
    { }
};
MODULE_DEVICE_TABLE(spi, imu_spi_ids);

static const struct of_device_id imu_of_match[] = {
    { .compatible = "generic,icm20608" },
    { }
};
MODULE_DEVICE_TABLE(of, imu_of_match);

static struct spi_driver imu_spi_driver = {
    .probe = imu_spi_probe,
    .remove = imu_spi_remove,
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = imu_of_match,
    },
    .id_table = imu_spi_ids,
};

module_spi_driver(imu_spi_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ICM20608 IMU driver using regmap");

4.3 Programa de Espaço do Usuário (test_sensor.c)

Um simples programa em espaço de usuário para ler e exibir os dados do sensor.

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char *argv[])
{
    int fd;
    ssize_t bytes_read;
    s16 sensor_raw[7];
    float gyro[3], accel[3], temp_c;

    if (argc != 2) {
        fprintf(stderr, "Usage: %s <device_path>\n", argv[0]);
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("Failed to open device");
        return 1;
    }

    while (1) {
        bytes_read = read(fd, sensor_raw, sizeof(sensor_raw));
        if (bytes_read == sizeof(sensor_raw)) {
            /* Conversão para unidades físicas (exemplos) */
            gyro[0] = (float)sensor_raw[0] / 16.4f;
            gyro[1] = (float)sensor_raw[1] / 16.4f;
            gyro[2] = (float)sensor_raw[2] / 16.4f;
            accel[0] = (float)sensor_raw[3] / 2048.0f;
            accel[1] = (float)sensor_raw[4] / 2048.0f;
            accel[2] = (float)sensor_raw[5] / 2048.0f;
            temp_c = ((float)sensor_raw[6] - 25.0f) / 326.8f + 25.0f;

            printf("Gyro (dps): X=%6.2f, Y=%6.2f, Z=%6.2f\n", gyro[0], gyro[1], gyro[2]);
            printf("Accel (g): X=%6.2f, Y=%6.2f, Z=%6.2f\n", accel[0], accel[1], accel[2]);
            printf("Temp (°C): %6.2f\n", temp_c);
            printf("---\n");
        } else {
            perror("Read error");
            break;
        }
        usleep(200000); /* 200ms de intervalo */
    }

    close(fd);
    return 0;
}

Tags: Linux Kernel Device Drivers Regmap I2C SPI

Publicado em 6-8 01:25 por Thomas