NVMEM Cells and Provider Registration-Free Linux Device Drivers Course

PREV_LEC | NEXT_LEC

NVMEM Cells and Provider Registration
A free Linux kernel development course lecture on the NVMEM framework’s cell structures and provider driver registration APIs
Chapter 12 · Lecture 2
NVMEM Framework
Kernel 6.x

In the previous lecture of this free Linux device drivers course, we introduced the NVMEM framework’s producer/consumer model along with struct nvmem_device and struct nvmem_config. In this lecture we go one level deeper: how an individual region of non-volatile memory is represented as a cell, and how a provider driver actually registers itself with the NVMEM core. By the end of this lecture you will be able to declare cells with bit-level precision and bring up a working NVMEM provider on the latest stable kernel.

free linux kernel development course free linux device drivers course NVMEM cells nvmem_register EEPROM driver devm_nvmem_register

What You Will Learn

  • Why NVMEM splits a cell into two structures instead of one
  • Every field of struct nvmem_cell and struct nvmem_cell_info, including bit-level offsets
  • How to register and unregister an NVMEM provider on a modern kernel using the managed API
  • Building a minimal, original EEPROM-style provider driver and reading its sysfs entry
  • How RTC drivers piggy-back on NVMEM to expose their battery-backed storage

Prerequisites

  • Lecture 1 of this chapter — the NVMEM producer/consumer model and struct nvmem_config
  • Comfort building and loading out-of-tree kernel modules
  • Basic familiarity with the devres (devm_*) managed-resource pattern

Why a Cell Needs Two Structures

A cell is simply a named, addressable slice of a larger NVMEM-backed region — a serial number, a calibration byte, a MAC address, or a single configuration bit buried inside an EEPROM image. The NVMEM core deliberately keeps two separate representations of a cell rather than one, because the provider and the consumer care about different things at different points in time.

The provider only ever describes cells as static, compile-time or device-tree-time configuration — it never needs a live object. The consumer, on the other hand, needs a fully resolved, linkable object once a cell has actually been requested and bound to a real NVMEM device. Splitting these two concerns keeps the provider-side API tiny while letting the core do the heavier bookkeeping internally.

Cell Lifecycle: Config to Live Object
[Provider driver] [NVMEM core] [Consumer driver] struct nvmem_cell_info —-> nvmem_cell_info_to_nvmem_cell() —-> struct nvmem_cell (static config, no link) (allocates, links into (live object, ready for global nvmem_cells list, nvmem_cell_read() / protected by nvmem_cells_mutex) nvmem_cell_write())

struct nvmem_cell_info — the Provider View

This is the structure a provider driver fills in to describe a cell before registration. Every field is an unsigned int because, from the provider’s point of view, a cell is pure configuration — there is nothing to point to yet.

struct nvmem_cell_info {
    const char      *name;
    unsigned int    offset;
    unsigned int    bytes;
    unsigned int    bit_offset;
    unsigned int    nbits;
};

struct nvmem_cell — the Consumer View

Once the core has processed a nvmem_cell_info entry, it produces a live struct nvmem_cell. Notice the two extra fields that only make sense once the cell is bound to a real device.

struct nvmem_cell {
    const char           *name;
    int                  offset;
    int                  bytes;
    int                  bit_offset;
    int                  nbits;
    struct nvmem_device  *nvmem;
    struct list_head     node;
};
FieldMeaningPresent in cell_info?
nameHuman-readable identifier for the cellYes
offsetByte offset of the cell inside the NVMEM regionYes
bytesSize of the cell in bytes, starting at offsetYes
bit_offset / nbitsSub-byte granularity — which bits inside the cell actually matterYes
nvmemPointer back to the owning struct nvmem_deviceNo — resolved by the core
nodeLinks the cell into the system-wide nvmem_cells list, guarded by nvmem_cells_mutexNo — resolved by the core

Declaring a Bit-Level Cell

Most cells simply consume whole bytes, in which case bit_offset and nbits stay at zero. But some hardware packs several unrelated flags into a single byte — a common pattern for factory calibration trims or feature-enable fuses. Suppose a fictional sensor stores a 3-bit gain-trim value inside bits 4-6 of the byte at offset 0x20 of its EEPROM. The provider would declare it like this:

static const struct nvmem_cell_info ep_gain_trim_cell = {
    .name       = "ep_gain_trim",
    .offset     = 0x20,
    .bytes      = 1,
    .bit_offset = 4,
    .nbits      = 3,
};

Reading this cell later returns only bits 4 through 6 of the byte at offset 0x20, already shifted down to start at bit 0 — the consumer driver never has to do its own bit-masking.

Bit-Level Cell Inside a Byte
Byte at offset 0x20: [ 7 ][ 6 ][ 5 ][ 4 ][ 3 ][ 2 ][ 1 ][ 0 ] gain_trim (nbits=3, bit_offset=4) ^—-^—-^ bits 6,5,4 make up the 3-bit value

Important: hand-writing arrays of nvmem_cell_info like the one above is fine for a quick demo, but production drivers should describe cells through Device Tree nvmem-cells bindings instead, which we cover in a later lecture. Static declarations are easy to get wrong and don’t survive a board revision.

One more rule worth internalizing early: neither the provider nor the consumer ever hand-constructs a struct nvmem_cell directly. That object only ever comes from the NVMEM core itself — either when it walks a provider’s array of nvmem_cell_info entries, or when a consumer calls nvmem_cell_get().

Writing the NVMEM Provider Driver

A provider driver’s job is deliberately small. Its three responsibilities are:

  • Filling in a struct nvmem_config that matches the device’s datasheet, including the read/write callbacks
  • Registering the device with the NVMEM core
  • Documenting the Device Tree binding so board files or overlays can reference it

Everything else — sysfs exposure, cell resolution, consumer lookups — is handled internally by the framework, which is why NVMEM providers tend to be short files.

Registering and Unregistering

Registration happens through one of these four functions, declared in <linux/nvmem-provider.h>:

struct nvmem_device *nvmem_register(const struct nvmem_config *config);
struct nvmem_device *devm_nvmem_register(struct device *dev,
                                          const struct nvmem_config *config);
int nvmem_unregister(struct nvmem_device *nvmem);
int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem);
FunctionNotes
nvmem_register()Manual lifetime — you must call nvmem_unregister() yourself on teardown
devm_nvmem_register()Managed lifetime — automatically unregistered when dev is released; almost always the right choice
Return valueA valid struct nvmem_device * on success, or ERR_PTR() on failure — always check with IS_ERR()

On a successful call, the core creates a binary sysfs entry at /sys/bus/nvmem/devices/<dev-name>/nvmem, which is how user space can read the raw memory region directly, independent of any cell.

Original Demo: a Minimal EEPROM Provider

The following is an original, from-scratch example — not taken from any book — of a fake I2C-style EEPROM provider that backs its storage with a plain in-kernel buffer. It registers one whole-byte cell and one bit-level cell, then exposes both through the framework.

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/nvmem-provider.h>
#include <linux/slab.h>

#define EP_EEPROM_SIZE 64

struct ep_eeprom_priv {
    u8 storage[EP_EEPROM_SIZE];
};

static int ep_eeprom_read(void *priv, unsigned int offset,
                           void *val, size_t bytes)
{
    struct ep_eeprom_priv *p = priv;

    memcpy(val, p->storage + offset, bytes);
    return 0;
}

static int ep_eeprom_write(void *priv, unsigned int offset,
                            void *val, size_t bytes)
{
    struct ep_eeprom_priv *p = priv;

    memcpy(p->storage + offset, val, bytes);
    return 0;
}

static const struct nvmem_cell_info ep_eeprom_cells[] = {
    { .name = "ep_serial_number", .offset = 0x00, .bytes = 8 },
    { .name = "ep_gain_trim",     .offset = 0x20, .bytes = 1,
      .bit_offset = 4, .nbits = 3 },
};

static int ep_eeprom_probe(struct platform_device *pdev)
{
    struct ep_eeprom_priv *priv;
    struct nvmem_config config = { 0 };
    struct nvmem_device *nvmem;

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

    config.dev          = &pdev->dev;
    config.name          = "ep_eeprom";
    config.size          = EP_EEPROM_SIZE;
    config.reg_read      = ep_eeprom_read;
    config.reg_write     = ep_eeprom_write;
    config.priv          = priv;
    config.cells          = ep_eeprom_cells;
    config.ncells         = ARRAY_SIZE(ep_eeprom_cells);

    nvmem = devm_nvmem_register(&pdev->dev, &config);
    if (IS_ERR(nvmem))
        return dev_err_probe(&pdev->dev, PTR_ERR(nvmem),
                              "failed to register nvmem\n");

    dev_info(&pdev->dev, "ep_eeprom provider ready\n");
    return 0;
}

static struct platform_driver ep_eeprom_driver = {
    .driver = { .name = "ep_eeprom" },
    .probe  = ep_eeprom_probe,
};
module_platform_driver(ep_eeprom_driver);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("EmbeddedPathashala demo NVMEM provider");

Building and Verifying the Demo

Build it as an out-of-tree module against your running kernel headers, register a platform device for it (or bind it via Device Tree once you’ve covered that lecture), then check the results:

$ make -C /lib/modules/$(uname -r)/build M=$PWD modules
$ sudo insmod ep_eeprom.ko
$ dmesg | tail -n 2
[  812.552013] ep_eeprom ep_eeprom.0: ep_eeprom provider ready

$ ls /sys/bus/nvmem/devices/
ep_eeprom0

$ sudo xxd /sys/bus/nvmem/devices/ep_eeprom0/nvmem | head -n 1
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................

The 64-byte region reads back as zeroes because our demo buffer is never pre-seeded — in a real driver, reg_read() would talk to actual hardware (I2C, SPI, or an on-chip register block) instead of a plain array.

NVMEM Storage in RTC Devices

Many Real-Time Clock chips embed a small amount of non-volatile storage alongside the clock/calendar registers — either EEPROM or battery-backed RAM. Rather than inventing a separate framework, the RTC subsystem simply embeds NVMEM support directly in struct rtc_device (include/linux/rtc.h):

struct rtc_device {
    /* ... */
    struct nvmem_device   *nvmem;
    bool                   nvram_old_abi;
    struct bin_attribute  *nvram;
    /* ... */
};
FieldPurpose
nvmemThe registered NVMEM device backing this RTC’s storage
nvram_old_abiSet only for legacy drivers that must keep exposing the deprecated /sys/class/rtc/rtcX/device/nvram path; new drivers should leave this false
nvramBinary sysfs attribute used solely to support the old ABI when nvram_old_abi is set

We’ll build a full RTC-with-NVMEM provider in the next lecture — for now, the key takeaway is that RTC drivers are just another NVMEM provider under the hood, using the exact same nvmem_register() family you learned above.

Common Mistakes and Troubleshooting

  • Forgetting IS_ERR() checks — both registration functions return an error pointer, not NULL, on failure.
  • Mixing up bit_offset direction — bit_offset counts from bit 0 (LSB) of the byte at offset, not from the MSB.
  • Setting nvram_old_abi = true on a brand-new driver — this only exists for backward compatibility; new RTC drivers should never set it.
  • Hand-building a struct nvmem_cell — this object is core-owned; drivers only ever populate nvmem_cell_info.
  • Missing sysfs entry after registration — usually means config.dev was left unset, or the platform device never actually probed.

Best Practices

  • Prefer devm_nvmem_register() over the manual variant in nearly every driver.
  • Describe cells through Device Tree wherever possible instead of static C arrays.
  • Keep reg_read()/reg_write() callbacks free of sleeping-incompatible assumptions if your provider might be read from atomic context by a consumer.
  • Security consideration: the raw nvmem sysfs binary file can expose sensitive calibration or key material to any process with read access — set config.read_only and appropriate file permissions deliberately rather than relying on defaults.

Interview Questions

Why does NVMEM use two different cell structures instead of one?

Because the provider only describes static configuration, while the consumer needs a fully resolved object linked to a real device — separating them keeps the provider API minimal and lets the core own all the bookkeeping.

What happens if you call nvmem_register() instead of the managed variant?

You take on responsibility for calling nvmem_unregister() yourself at the right point in your driver’s teardown path — get it wrong and you leak the registration or unregister too early.

How is bit-level granularity expressed in a cell?

Through bit_offset and nbits in nvmem_cell_info; the core automatically masks and shifts so the consumer receives just the requested bits, already aligned to bit 0.

Where does the system-wide list of cells live, and how is it protected?

In the nvmem_cells list, guarded by nvmem_cells_mutex, both statically defined in drivers/nvmem/core.c.

Summary and Key Takeaways

  • A cell is described by the provider as nvmem_cell_info (pure config) and consumed as nvmem_cell (a core-owned, linked object).
  • Bit-level cells use bit_offset/nbits to pull a sub-byte field out cleanly, without manual masking in the consumer.
  • Provider registration is a three-item job: fill nvmem_config, call devm_nvmem_register(), document the DT binding.
  • RTC devices are ordinary NVMEM providers under the hood, exposed through struct rtc_device‘s nvmem field.

Conclusion

Cells are the piece that makes NVMEM genuinely useful — without them you’d only ever be able to read or write a whole memory blob at once. Once you’re comfortable with nvmem_cell_info, the registration API, and how a provider like an RTC chip plugs into the same framework, you’re ready to build real-world providers backed by Device Tree, which is exactly where this free Linux kernel development course heads next.

FAQ

Do I need Device Tree to use NVMEM cells?

No — static nvmem_cell_info arrays work for demos and bring-up, but production drivers should use DT nvmem-cells bindings for maintainability.

What’s the difference between nvmem_register() and devm_nvmem_register()?

Only lifetime management — the devm_ version automatically unregisters when the owning device is released, avoiding manual cleanup code.

Can a single NVMEM device have more than one cell?

Yes — pass an array via config.cells/config.ncells, exactly as shown in the demo driver above.

Is struct nvmem_cell ever created directly by a driver?

No, it’s always produced internally by the NVMEM core from a provider’s nvmem_cell_info entries or when a consumer requests a cell.

Why would an RTC driver set nvram_old_abi to true?

Only to avoid breaking existing user-space applications that still read the legacy /sys/class/rtc/rtcX/device/nvram path — new drivers should leave it false.

What does the sysfs nvmem binary file expose?

The raw NVMEM region for the device, created automatically at /sys/bus/nvmem/devices/<dev-name>/nvmem once registration succeeds.

Is this course free?

Yes — this lecture is part of EmbeddedPathashala’s free Linux kernel development course, freely available alongside its free Linux device drivers course and free embedded systems course material.

{ “@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [ {“@type”: “Question”, “name”: “Do I need Device Tree to use NVMEM cells?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, static nvmem_cell_info arrays work for demos and bring-up, but production drivers should use DT nvmem-cells bindings for maintainability.”}}, {“@type”: “Question”, “name”: “What’s the difference between nvmem_register() and devm_nvmem_register()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Only lifetime management – the devm_ version automatically unregisters when the owning device is released, avoiding manual cleanup code.”}}, {“@type”: “Question”, “name”: “Can a single NVMEM device have more than one cell?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes, pass an array via config.cells and config.ncells.”}}, {“@type”: “Question”, “name”: “Is struct nvmem_cell ever created directly by a driver?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, it is always produced internally by the NVMEM core.”}}, {“@type”: “Question”, “name”: “Why would an RTC driver set nvram_old_abi to true?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Only to avoid breaking existing user-space applications reading the legacy nvram sysfs path.”}}, {“@type”: “Question”, “name”: “What does the sysfs nvmem binary file expose?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “The raw NVMEM region for the device, created automatically at /sys/bus/nvmem/devices//nvmem.”}}, {“@type”: “Question”, “name”: “Is this course free?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Yes, this lecture is part of EmbeddedPathashala’s free Linux kernel development course.”}} ] }

Continue the Free Linux Kernel Development Course

Next up: Device Tree bindings for NVMEM providers, and a full RTC provider driver.

Next Lecture Back to Course Index

PREV_LEC | NEXT_LEC

Leave a Reply

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