regmap: Reducing the Redundancy in Linux Code

1
23251

tux

This article gives an overview of regmap—how useful it is in factoring out common code from the Linux sub-system and how to effectively use it to write drivers in Linux.

Linux is divided into many sub-systems in order to factor out common code in different parts and to simplify driver development, which helps in code maintenance.

Linux has sub-systems such as I2C and SPI, which are used to connect to devices that reside on these buses. Both these buses have the common function of reading and writing registers from the devices connected to them. So the code to read and write these registers, cache the value of the registers, etc, will have to be present in both these sub-systems. This causes redundant code to be present in all sub-systems that have this register read and write functionality.

To avoid this and to factor out common code, as well as for easy driver maintenance and development, Linux developers introduced a new kernel API from version 3.1, which is called regmap. This infrastructure was previously present in the Linux AsoC (ALSA) sub-system, but has now been made available to entire Linux through the regmap API.

Earlier, if a driver was written for a device residing on the SPI bus, then the driver directly used the SPI bus read and write calls from the SPI sub-system to talk to the device. Now, it uses the regmap API to do so. The regmap sub-system takes care of calling the relevant calls of the SPI sub-system. So two devices—one residing on the I2C bus and one on the SPI bus—will have the same regmap read and write calls to talk to their respective devices on the bus.

This sub-system was first introduced in Linux 3.1 and later, the following different features were introduced:

  • Support for SPMI, MMIO
  • Spinlock and custom lock mechanism
  • Cache support
  • Endianness conversion
  • Register range check
  • IRQ support
  • Read only and write only register
  • Precious register and volatile register
  • Register pages

Implementing regmap
regmap in Linux provides APIs that are declared in include/linux/regmap.h and implemented in drivers/base/regmap/.

Two important data structures to understand in Linux regmap are struct regmap_config and struct regmap.

figure-1-i2c-and-spi-driver-before-regmap
Figure 1: I2C and SPI driver before regmap

struct regmap_config
This is a per device configuration structure used by the regmap sub-system to talk to the device. It is defined by driver code, and contains all the information related to the registers of the device. Descriptions of its important fields are listed below.

reg_bits: This is the number of bits in the registers of the device, e.g., in case of 1 byte registers it will be set to the value 8.

val_bits: This is the number of bits in the value that will be set in the device register.
writeable_reg: This is a user-defined function written in driver code which is called whenever a register is to be written. Whenever the driver calls the regmap sub-system to write to a register, this driver function is called; it will return ‘false’ if this register is not writeable and the write operation will return an error to the driver. This is a ‘per register’ write operation callback and is optional.
wr_table: If the driver does not provide the writeable_reg callback, then wr_table is checked by regmap before doing the write operation. If the register address lies in the range provided by the wr_table, then the write operation is performed. This is also optional, and the driver can omit its definition and can set it to NULL.

readable_reg: This is a user defined function written in driver code, which is called whenever a register is to be read. Whenever the driver calls the regmap sub-system to read a register, this driver function is called to ensure the register is readable. The driver function will return ‘false’ if this register is not readable and the read operation will return an error to the driver. This is a ‘per register’ read operation callback and is optional.

rd_table: If a driver does not provide a readable_reg callback, then the rd_table is checked by regmap before doing the read operation. If the register address lies in the range provided by rd_table, then the read operation is performed. This is also optional, and the driver can omit its definition and can set it to NULL
volatile_reg: This is a user defined function written in driver code, which is called whenever a register is written or read through the cache. Whenever a driver reads or writes a register through the regmap cache, this function is called first, and if it returns ‘false’ only then is the cache method used; else, the registers are written or read directly, since the register is volatile and caching is not to be used. This is a ‘per register’ operation callback and is optional.

volatile_table: If a driver does not provide a volatile_reg callback, then the volatile_table is checked by regmap to see if the register is volatile or not. If the register address lies in the range provided by the volatile_table then the cache operation is not used. This is also optional, and the driver can omit its definition and can set it to NULL.

lock: This is a user defined callback written in driver code, which is called before starting any read or write operation. The function should take a lock and return it. This is an optional function—if it is not provided, regmap will provide its own locking mechanism.

unlock: This is user defined callback written in driver code for unlocking the lock, which is created by the lock routine. This is optional and, if it’s not provided, will be replaced by the regmap internal locking mechanism.

lock_arg: This is the parameter that is passed to the lock and unlock callback routine.
fast_io: regmap internally uses mutex to lock and unlock, if a custom lock and unlock mechanism is not provided. If the driver wants regmap to use the spinlock, then fast_io should be set to ‘true’; else, regmap will use the mutex based lock.

max_register: Whenever any read or write operation is to be performed, regmap checks whether the register address is less than max_register first, and only if it is, is the operation performed. max_register is ignored if it is set to 0.

read_flag_mask: Normally, in SPI or I2C, a write or read will have the highest bit set in the top byte to differentiate write and read operations. This mask is set in the higher byte of the register value.
write_flag_mask: This mask is also set in the higher byte of the register value.

These are the fields of the struct regmap_config, and this configuration is passed to regmap_init, which creates the struct regmap and returns what can be used in the read and write operation.
wr_table, rd_table and volatile_table are optional tables (used only if the respective callback is not provided) used by regmap for range-checking in case of write and read operations. These are implemented as struct regmap_access_table. The following are its fields.
yes_ranges: These are address ranges that are considered as valid ranges.
n_yes_ranges: This is the number of entries in yes_ranges.
no_ranges: These are address ranges that are considered as invalid ranges.
n_no_ranges: This is the number of entries in no_ranges.

regmap APIs
regmap APIs are declared in include/linux/regmap.h. The following are the details of important APIs.
Initialisation routines: The following routine initialises regmap data structures based on the SPI configuration:

struct regmap * devm_regmap_init_spi(struct spi_device *spi, const struct regmap_config);

The following routine initialises regmap data structures based on the I2C configuration:

struct regmap * devm_regmap_init_i2c(struct i2c_client *i2c, const struct regmap_config);

In the regmap initialisation routine, the regmap_config configuration is taken; then the regmap structure is allocated and the configuration is copied to it. The read/write functions of the respective bus are also copied in the regmap structure. For example, in the case of the SPI bus, the regmap read and write function pointer will point to the SPI read and write functions.
After regmap initialisation, the driver can talk to the device using the following routine:

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val); 
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);

regmap_write: This function is used to write data to the device. It takes in the regmap structure returned during initialisation, registers the address and the value to be set. The following are the steps performed by the regmap_write routine.

First, regmap_write takes the lock, which will be spinlock if fast_io in regmap_config was set; else, it will be mutex.

Next, if max_register is set in regmap_config, then it will check if the register address passed is less than max_register. If it is less than max_register, then only the write operation is performed; else, -EIO (invalid I/O) is returned.

After that, if the writeable_reg callback is set in regmap_config, then that callback is called. If that callback returns ‘true’, then further operations are done; if it returns ‘false’, then an error -EIO is returned. This step is only performed if writeable_reg is set.

If writeable_reg is not set, but wr_table is set, then there’s a check on whether the register address lies in no_ranges, in which case an -EIO error is returned; else, it is checked whether it lies in the yes_ranges. If it is not present there, then an -EIO error is returned and the operation is terminated. If it lies in the yes_ranges, then further operations are performed. This step is only performed if wr_table is set; else, it is skipped.

Whether caching is permitted is now checked. If it is permitted, then the register value is cached instead of writing directly to hardware, and the operation finishes at this step. If caching is not permitted, it goes to the next step. Caching will be discussed later.

After this hardware write routine is called to write the value in the hardware register, this function writes the write_flag_mask to the first byte of the value and the value is written to the device.
After completing the write operation, the lock that was taken before writing is released and the function returns.

regmap_read: This function is used to read data from the device. It takes in regmap structure returned during initialisation, and registers the address and value pointer in which data is to be read. The following steps are performed during register reading.
First, the read function will take a lock before performing the read operation. This will be a spinlock if fast_io is set in regmap_config; else, regmap will use mutex.
Next, it will check whether the passed register address is less than max_register; if it is not, then -EIO is returned. This step is only done if max_register is set greater than zero.
Then, it will check if the readable_reg callback is set. If it is, then that callback is called, and if this callback returns ‘false’ then the read operation is terminated returning an -EIO error. If this callback returns ‘true’ then further operations are performed.

Note: This step is only performed if readable_reg is set. 

What is checked next is whether the register address lies in the no_ranges of the rd_table in config. If it does, then an –EIO error is returned. If it doesn’t lie either in the no_ranges or in the yes_ranges, then too an -EIO error is returned. Only if it lies in the yes_ranges can further operations be performed. This step is only performed if the rd_table is set.
Now, if caching is permitted, then the register value is read from the cache and the function returns the value being read. If caching is set to bypass, then the next step is performed.
After the above steps have been taken, the hardware read operation is called to read the register value, and the value of the variable which was passed is updated with the value returned.
The lock that was taken before starting this operation is now released and the function returns.

figure-2-i2c-and-spi-driver-after-regmap
Figure 2: I2C and SPI driver after regmap

Writing a regmap based driver 
Let us try to write a driver based on the regmap framework.
Let us assume that there is a device X connected on an SPI bus, which has the following properties:

  • 8-bit register address
  • 8-bit register values
  • 0x80 as write mask
  • It’s a fast I/O device so the spinlock should be used
  • Valid address range:
    1. 0x20 to 0x4F
    2. 0x60 to 0x7F
    The driver can be written as follows:
//include other include files
#include <linux/regmap.h>
 
static struct custom_drv_private_struct
{
            //other fields relevant to device
            struct regmap *map;
};
 
static const struct regmap_range wr_rd_range[] = 
{
            {
                        .range_min = 0x20,
                        .range_max = 0x4F,
            }, {
                        .range_min = 0x60,
                        .range_max = 0x7F
            },
}; 
struct regmap_access_table drv_wr_table = 
{
            .yes_ranges =   wr_rd_range,
.n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
 
struct regmap_access_table drv_rd_table = 
{
         .yes_ranges =   wr_rd_range,
         .n_yes_ranges = ARRAY_SIZE(wr_rd_range),
};
 
static bool writeable_reg(struct device *dev, unsigned int reg)
{
            if(reg >= 0x20 && reg <= 0x4F)
                        return true;
 
            if(reg >= 0x60 && reg <= 0x7F)
                        return true;
 
            return false;
}
 
static bool readable_reg(struct device *dev, unsigned int reg)
{
            if(reg >= 0x20 && reg <= 0x4F)
                        return true;
 
            if(reg >= 0x60 && reg <= 0x7F)
                        return true;
 
            return false;
}
static int custom_drv_probe(struct spi_device *dev)
{
     struct regmap_config config;
     struct custom_drv_private_struct *priv;
     unsigned int data; 
    //configure the regmap configuration
    memset(&config, 0, sizeof(config));
    config.reg_bits = 8;
    config.val_bits = 8;
    config.write_flag_mask = 0x80;
    config.max_register = 0x80;
    config.fast_io = true;
    config.writeable_reg = drv_writeable_reg;
    config.readable_reg = drv_readable_reg;
    //only set below two things if  
    //writeable_reg
    //and readable_reg is not set
    //config.wr_table = drv_wr_table;
    //config.rd_table = drv_rd_table;

    //allocate the private data structures as
    //priv = devm_kzalloc 
    //Init the regmap spi configuration
    priv->map = devm_regmap_init_spi(dev,             &config); 
  //devm_regmap_init_i2c in case of i2c bus 
//following operation will remain same in 
//case of both i2c and spi or other bus            
//read from the device, data variable will //contain device data
regmap_read(priv->map, 0x23, &data); 
data = 0x24;
//write to the device
regmap_write(priv->map, 0x23, data); 
if(regmap_read(priv->map, 0x85, &data)
< 0)
    {
            ///error since address is out of range
    } 
            return 0;
}

You can see in the above example how redundant code in the drivers based on different bus sub-systems becomes similar code, which makes driver writing and maintenance easier. Cache support is also available in the current regmap sub-system.

Caching avoids directly performing operations on the device. Instead, it caches the value transferring between the device and driver, and uses it as future reference. Initially, caches used just flat arrays, which were not good for 32-bit addresses. Later, this issue was solved with better cache types:

  • rbtree stores blocks of contiguous registers in a red/black tree
  • Compressed stores blocks of compressed data
    Both rely on the existing kernel library:
enum regcache_type cache_type;

Support for tracepoint is available in regmap. For more information, see debugfs/trace/events/regmap:

regmap_reg_write 0-001b reg=3b val=1a
regmap_reg_read 0-001b reg=1d val=1d

You can also define LOG_DEVICE for early init log

1 COMMENT

LEAVE A REPLY

Please enter your comment!
Please enter your name here