Supporting Multi-Function Devices in the Linux Kernel: A Tour of the mfd, regmap and syscon APIs – Alexandre Belloni, Free Electrons

Multi-function devices are external peripherals or on-SoC hardware blocks that expose functionalities that are handled by separate subsystems in the kernel. PMICs are a typical example, they often have e.g. an RTC, LED controller in addition to the regulators.
The MFD subsystem handles such devices. It allows you to have 1 device and register it in multiple subsystems. It will also handle multiplexing the accesses to the bus, since the different subsystems will talk to the device concurrently. It could also handle clocks, configure it, handle variants, etc.
The advantage of using an MFD with separate functions has the advantage that you can reuse the individual function drivers for other MFD devices.
API: mfd_add_devices(parent, id, cells, n_devs, mem_base, irq_base, irq_domain), and mfd_remove_devices(dev)
struct mfd_cell is a huge struct with a few device-specific entries, here are the common ones. It has a few of the usual device members, like name and of_compatible. The MFD driver first registers itself as a normal I2C driver, then creates each of its cells, then calls mfd_add_devices.
There is a device-specific header file, e.g. include/linux/mfd/tps6507x.h, that is used by all the individual function drivers. The function drivers are spread over the tree in their individual subsystems, e.g. tps6507x_pmic under regulator.
The cells can refer to pdata which is defined in the MFD driver and then used by the function drivers. But of course this is a bit the return of the board file, i.e. not using DT. In the DT, you have a definition of the MFD itself and a child node for each function.
Multiplexing register access: there are some registers that are shared between different functions, e.g. watchdog and rtc enable bits could be in the same register. So these have to be accessed atomically. An easy way to do this is to use the regmap API (which was created for ASoC). You create it in the MFD driver and use it from the functions. regmap can use I2C, SPI, MMIO, SPMI, or you can pass your own accessors. Handles locking, can cache registers, can do endianness conversion, checks out-of-bound accesses, handles IRQs in addition to registers. Register types: read only, write only, volatile, precious (= resets itself so you have to cache the value if you still want to have it later).
regmap_init{_i2c,_spi}() with devm_ and _clk variants. To use: regmap_read(), regmap_write(), regmap_update_bits().
Sometimes an MFD supports only one simultaneous function. E.g. Atmel Flexcom can do either UART or SPI or I2C, but not all at the same time. So the mode is specified in the DT. The MFD driver then configures the hardware and populates the function driver. The function drivers can be reused because Atmel uses the same kind of IP blocks as in their previous chips.
SoCs sometimes have a set of registers that have miscellaneous features that don’t relate to a specific IP. Clearly, there can’t be a functional driver for this. The syscon MFD driver handles this kind of situation. This driver uses the regmap API. When you request access to syscon, the regmap is created if it doesn’t exist yet. E.g. syscon_regmap_lookup_by_compatible(). To avoid writing an MFD driver, you can use simple-mfd as the DT binding, that will just make sure that also all children are registered. Then you just have to add a header file that defines the registers.
There has been some pushback against patches using syscon, are there guidelines for when it’s appropriate? Alexandre would use it anytime that there are registers used by several drivers.
What is the support for run-time power management? You have to implement it both in the MFD driver (e.g. turn off global clock) and the function drivers (taking function-specific steps).
Could you have a regmap type that calls into userspace?


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s