what is Linux MFD Dummy I2C Client in Linux Kernel

Linux MFD Dummy I2C Client-Free Linux Device Drivers Course 
Free Linux Device Drivers Course — MFD Subsystem, Lecture 5
Lecture 5
Kernel 6.x
~14 min read

free linux device drivers course
free linux kernel development course
free embedded linux course
linux mfd dummy i2c client
i2c_new_dummy_device

Anyone building a linux mfd dummy i2c client driver eventually hits the same wall: a single physical I2C chip that internally behaves like several separate chips, each answering to its own I2C address. The MFD core alone cannot register a subdevice as an I2C client — it only creates platform devices. This free linux device drivers course lecture explains, step by step, how the dummy I2C client trick solves that exact problem on a modern kernel.

What You Will Learn

  • Why some MFD subdevices need their own I2C client instead of a plain platform device
  • How i2c_new_dummy_device() creates a virtual client at a derived address
  • How to attach a private regmap to that dummy client
  • How to clean up dummy clients safely on driver removal
  • A complete, original demo driver you can build and test on a Raspberry Pi or QEMU

Prerequisites

Why Some MFD Chips Need a Dummy I2C Client

Most multi-function devices expose every internal block through the same register map, at the same I2C address, so the MFD core can hand each subdevice a plain platform_device and let it share one parent regmap. But a handful of real-world PMICs and audio codecs are wired differently: the RTC block, the GPIO block, or the charger block responds on a completely separate I2C address, usually the parent address plus a fixed offset.

A platform_device has no I2C address of its own, so it cannot talk on the bus directly. The fix used throughout the kernel (for example in drivers/mfd/max8925-i2c.c) is to register a second, “dummy” I2C client at that extra address, purely so a regmap can be built on top of it. No real driver ever probes on this dummy client — the parent MFD driver owns it for the lifetime of the chip.

One Physical Chip, Two I2C Addresses
i2c_client (real) @ 0x58
↓↓
dummy client @ 0x59 (RTC)
dummy client @ 0x5A (GPIO)
↓↓
subdev1_regmap
subdev2_regmap

Private Context Structure

Every dummy client, and the regmap built on it, needs somewhere to live. The MFD driver keeps them all in a single private structure allocated once at probe time:

struct ep_mfd_chip {
    struct device *dev;
    struct regmap *regmap;

    /* the real I2C client for the chip itself */
    struct i2c_client *client;

    /* dummy clients created purely to host a regmap */
    struct i2c_client *rtc_client;
    struct i2c_client *gpio_client;

    struct regmap *rtc_regmap;
    struct regmap *gpio_regmap;

    unsigned short rtc_addr;
    unsigned short gpio_addr;
};

Creating the Dummy Clients in probe()

On a current 6.x kernel, the dummy-client helper is i2c_new_dummy_device(). Unlike the older i2c_new_dummy(), it returns a proper error pointer instead of NULL on failure, so every call must be checked with IS_ERR().

static int ep_mfd_probe(struct i2c_client *client)
{
    struct ep_mfd_chip *chip;
    struct device *dev = &client->dev;

    chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
    if (!chip)
        return -ENOMEM;

    chip->dev = dev;
    chip->client = client;
    chip->regmap = devm_regmap_init_i2c(client, &ep_chip_regmap_config);
    if (IS_ERR(chip->regmap))
        return PTR_ERR(chip->regmap);

    dev_set_drvdata(dev, chip);
    i2c_set_clientdata(client, chip);

    /* derive the two extra addresses from the base chip address */
    chip->rtc_addr  = client->addr + 1;
    chip->gpio_addr = client->addr + 2;

    /* RTC subdevice lives at its own I2C address */
    chip->rtc_client = i2c_new_dummy_device(client->adapter, chip->rtc_addr);
    if (IS_ERR(chip->rtc_client))
        return PTR_ERR(chip->rtc_client);

    chip->rtc_regmap = devm_regmap_init_i2c(chip->rtc_client,
                                             &ep_rtc_regmap_config);
    if (IS_ERR(chip->rtc_regmap)) {
        i2c_unregister_device(chip->rtc_client);
        return PTR_ERR(chip->rtc_regmap);
    }
    i2c_set_clientdata(chip->rtc_client, chip);

    /* GPIO subdevice, same pattern, different address */
    chip->gpio_client = i2c_new_dummy_device(client->adapter, chip->gpio_addr);
    if (IS_ERR(chip->gpio_client)) {
        i2c_unregister_device(chip->rtc_client);
        return PTR_ERR(chip->gpio_client);
    }

    chip->gpio_regmap = devm_regmap_init_i2c(chip->gpio_client,
                                              &ep_gpio_regmap_config);
    if (IS_ERR(chip->gpio_regmap)) {
        i2c_unregister_device(chip->gpio_client);
        i2c_unregister_device(chip->rtc_client);
        return PTR_ERR(chip->gpio_regmap);
    }
    i2c_set_clientdata(chip->gpio_client, chip);

    return devm_mfd_add_devices(dev, PLATFORM_DEVID_AUTO,
                                 ep_mfd_cells, ARRAY_SIZE(ep_mfd_cells),
                                 NULL, 0, NULL);
}

Note: only the dummy clients need manual unregistration in remove(). The regmaps built with devm_regmap_init_i2c() and the platform devices added with devm_mfd_add_devices() are already resource-managed and unwind automatically.

Cleaning Up: remove()

Because dummy clients are created with the plain (non-devm) i2c_new_dummy_device(), they must be unregistered explicitly, in the reverse order they were created:

static void ep_mfd_remove(struct i2c_client *client)
{
    struct ep_mfd_chip *chip = i2c_get_clientdata(client);

    i2c_unregister_device(chip->gpio_client);
    i2c_unregister_device(chip->rtc_client);
}

Notice that mfd_remove_devices() is not called manually here — since the subdevices were added with the devm_ variant, the device-managed core removes them automatically when the parent device is unbound, before remove() even runs.

Reading the Regmap Back in a Subdevice Driver

A subdevice driver never talks to I2C directly. It simply asks its parent device for the regmap that was stashed there during probe:

static int ep_rtc_probe(struct platform_device *pdev)
{
    struct ep_mfd_chip *chip = dev_get_drvdata(pdev->dev.parent);
    struct regmap *rtc_regmap = chip->rtc_regmap;

    if (!rtc_regmap) {
        dev_err(&pdev->dev, "no rtc regmap found\n");
        return -EINVAL;
    }

    /* rtc_regmap is now ready for regmap_read()/regmap_write() */
    return 0;
}

Build and Run the Demo

The full demo (parent MFD driver + one RTC-style subdevice) can be built as an out-of-tree module:

$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_mfd_demo.ko
$ sudo insmod ep_rtc_demo.ko
$ dmesg | tail -n 8

Expected output:

[ 812.001122] ep_mfd_demo: probing chip at 0x58
[ 812.001210] ep_mfd_demo: dummy rtc client registered at 0x59
[ 812.001267] ep_mfd_demo: dummy gpio client registered at 0x5a
[ 812.001299] ep_mfd_demo: mfd subdevices added
[ 812.014402] ep_rtc_demo: probed, regmap ready
$ sudo rmmod ep_rtc_demo ep_mfd_demo
[ 900.220011] ep_mfd_demo: unregistering dummy gpio client
[ 900.220054] ep_mfd_demo: unregistering dummy rtc client

Real Client vs Dummy Client

Aspect Real i2c_client Dummy i2c_client
Created by i2c_new_client_device() / DT+bus scan i2c_new_dummy_device()
Has a driver bound to it Yes No — never probes
Purpose Normal device communication Host a regmap at an extra address
Removed with Bus/device model teardown i2c_unregister_device() explicitly

Common Mistakes

  • Using the removed i2c_new_dummy() API name from older kernels instead of i2c_new_dummy_device()
  • Forgetting to unregister dummy clients in remove(), leaking an I2C address on every unbind/rebind cycle
  • Checking a dummy client’s return value with if (!chip->rtc_client) instead of IS_ERR()
  • Building a regmap on the real client’s own address when the subdevice actually needs the dummy client instead

Best Practices

  • Always unregister dummy clients in the exact reverse order of creation
  • Store dummy clients and their regmaps together in one private struct, never as loose globals
  • Prefer devm_regmap_init_i2c() for the regmap even though the client itself is not devm-managed

Summary

When one physical chip answers on more than one I2C address, the MFD core cannot help directly — a plain platform device has no address to speak on. Creating a lightweight dummy I2C client with i2c_new_dummy_device() and building a regmap on top of it lets each subdevice keep talking over I2C while still fitting cleanly into the standard MFD cell model. This is exactly the pattern used by drivers like max8925-i2c.c in the mainline kernel.

FAQ

Why can’t the MFD core just create an I2C client for a subdevice?

The MFD core only knows how to instantiate platform_device objects from an mfd_cell. It has no concept of I2C addresses, so any subdevice that needs its own address must get an I2C client created manually by the parent driver.

Is i2c_new_dummy() still available in kernel 6.x?

The old i2c_new_dummy() was removed years ago. Current kernels use i2c_new_dummy_device(), which returns an error-pointer on failure instead of NULL.

Does a dummy client ever call a probe function?

No. A dummy client is created purely to hold a struct i2c_client so a regmap can be attached; no driver is bound to it and no probe runs.

What happens if I forget to unregister a dummy client?

The I2C address stays reserved after module unload, and re-inserting the module will fail with an “address already in use” style error on the next probe.

Can a dummy client’s regmap use a different regmap_config than the parent?

Yes, and in practice it usually should — each subdevice’s regmap_config typically has its own register range and cache setup matching that internal block.

Where in the kernel source can I see this pattern used for real?

drivers/mfd/max8925-i2c.c is a good real-world reference for the dummy-client-plus-regmap pattern described in this lecture.

 

Continue the Free Linux Device Drivers Course

Leave a Reply

Your email address will not be published. Required fields are marked *