Skip to content

Expose a Custom API

Updated: 7/10/26

Use a custom API when the device does not fit Zephyr's standard Sensor channels (modems, actuators, mission-specific peripherals). The template scaffolds this path by default with struct <chip>_driver_api and DEVICE_DT_INST_DEFINE.

For sensors that map to Zephyr channels, see Expose a Sensor API.

1. Declare the API struct

In src/<chip>.h, fill in the template's API struct with the function pointers application code will call:

struct <chip>_driver_api {
    int (*read_sensor)(const struct device *dev, uint8_t *val);
    /* additional operations… */
};

2. Implement with a public-mock branch

In src/<chip>.c, implement each function. Library-backed backends call into lib/; public mock returns hardcoded data:

static int read_sensor(const struct device *dev, uint8_t *val)
{
#if !defined(CONFIG_PEROVSAT_<CHIP>_BACKEND_PUBLIC_MOCK)
    return <chip>_lib_read_sensor(<chip>_transfer, (void *)dev, val);
#else
    ARG_UNUSED(dev);
    *val = 0x01;
    return 0;
#endif
}

const struct <chip>_driver_api <chip>_api = {
    .read_sensor = read_sensor,
};

The template's init() already calls transfer_init then lib_init for non–public-mock backends, and returns 0 for public mock. Leave that wiring alone unless your library needs extra arguments.

3. Register with DEVICE_DT_INST_DEFINE

The template already uses this macro. Confirm the API pointer is passed through:

DEVICE_DT_INST_DEFINE(inst, <chip>_init, NULL, &<chip>_data_##inst,
              &<chip>_config_##inst, BOOT_STAGE, BOOT_PRIORITY,
              &<chip>_api);

4. Provide an accessor

Application code should not cast dev->api directly. Add an inline helper in the public header:

static inline const struct <chip>_driver_api *<chip>_get_api(const struct device *dev)
{
    return (const struct <chip>_driver_api *)dev->api;
}

5. Application usage

const struct device *dev = DEVICE_DT_GET(DT_ALIAS(my_device));
const struct <chip>_driver_api *api = <chip>_get_api(dev);

api->read_sensor(dev, &val);

Thin-driver alternative

For very thin drivers, you can skip the API struct and expose ordinary public functions that take const struct device * (Eyestar does this and passes NULL as the API pointer to DEVICE_DT_INST_DEFINE). Prefer the struct pattern above unless the surface is a single call with no need for polymorphism.