#if defined(__i386__) || defined(__amd64__) || defined(__aarch64__)
#include "acpica.h"
#endif
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: i2cmux.c,v 1.13 2025/09/21 15:02:25 thorpej Exp $");
#include <sys/types.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <dev/fdt/fdtvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/i2c/i2cmuxvar.h>
#if NACPICA > 0
#include <dev/acpi/acpivar.h>
#endif
static int
iicmux_acquire_bus(void * const v, int const flags)
{
struct iicmux_bus * const bus = v;
struct iicmux_softc * const sc = bus->mux;
int error;
error = iic_acquire_bus(sc->sc_i2c_parent, flags);
if (error) {
return error;
}
error = sc->sc_config->acquire_bus(bus, flags);
if (error) {
iic_release_bus(sc->sc_i2c_parent, flags);
}
return error;
}
static void
iicmux_release_bus(void * const v, int const flags)
{
struct iicmux_bus * const bus = v;
struct iicmux_softc * const sc = bus->mux;
sc->sc_config->release_bus(bus, flags);
iic_release_bus(sc->sc_i2c_parent, flags);
}
static int
iicmux_exec(void * const v, i2c_op_t const op, i2c_addr_t const addr,
const void * const cmdbuf, size_t const cmdlen, void * const databuf,
size_t const datalen, int const flags)
{
struct iicmux_bus * const bus = v;
struct iicmux_softc * const sc = bus->mux;
return iic_exec(sc->sc_i2c_parent, op, addr, cmdbuf, cmdlen,
databuf, datalen, flags);
}
static int
iicmux_count_children(struct iicmux_softc * const sc)
{
char name[32];
int child, count;
int mux_phandle = devhandle_to_of(sc->sc_i2c_mux_devhandle);
restart:
for (child = OF_child(mux_phandle), count = 0; child;
child = OF_peer(child)) {
if (OF_getprop(child, "name", name, sizeof(name)) <= 0) {
continue;
}
if (strcmp(name, "i2c-mux") == 0) {
mux_phandle = child;
goto restart;
}
count++;
}
sc->sc_i2c_mux_devhandle =
devhandle_from_of(sc->sc_i2c_mux_devhandle, mux_phandle);
return count;
}
static void
iicmux_attach_bus(struct iicmux_softc * const sc, devhandle_t bus_devhandle,
int const busidx)
{
struct iicmux_bus * const bus = &sc->sc_busses[busidx];
bus->mux = sc;
bus->busidx = busidx;
bus->devhandle = bus_devhandle;
bus->bus_data = sc->sc_config->get_bus_info(bus);
if (bus->bus_data == NULL) {
aprint_error_dev(sc->sc_dev,
"unable to get info for bus %d\n", busidx);
return;
}
iic_tag_init(&bus->controller);
bus->controller.ic_cookie = bus;
bus->controller.ic_channel = busidx;
bus->controller.ic_acquire_bus = iicmux_acquire_bus;
bus->controller.ic_release_bus = iicmux_release_bus;
bus->controller.ic_exec = iicmux_exec;
iicbus_attach_with_devhandle(sc->sc_dev, &bus->controller,
bus->devhandle);
}
static void
iicmux_attach_fdt(struct iicmux_softc * const sc)
{
sc->sc_nbusses = iicmux_count_children(sc);
if (sc->sc_nbusses == 0) {
return;
}
sc->sc_busses = kmem_zalloc(sizeof(*sc->sc_busses) * sc->sc_nbusses,
KM_SLEEP);
#define BUS_DEVHANDLE(sc, c) \
devhandle_from_of((sc)->sc_i2c_mux_devhandle, (c))
const int mux_phandle = devhandle_to_of(sc->sc_i2c_mux_devhandle);
int child, idx;
for (child = OF_child(mux_phandle), idx = 0; child;
child = OF_peer(child), idx++) {
KASSERT(idx < sc->sc_nbusses);
iicmux_attach_bus(sc, BUS_DEVHANDLE(sc, child), idx);
}
#undef BUS_DEVHANDLE
}
#if NACPICA > 0
static void
iicmux_attach_acpi(struct iicmux_softc * const sc)
{
ACPI_HANDLE hdl = devhandle_to_acpi(sc->sc_i2c_mux_devhandle);
struct acpi_devnode *devnode, *ad;
int idx;
devnode = acpi_match_node(hdl);
KASSERT(devnode != NULL);
sc->sc_nbusses = 0;
SIMPLEQ_FOREACH(ad, &devnode->ad_child_head, ad_child_list) {
if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE ||
!acpi_device_present(ad->ad_handle)) {
continue;
}
sc->sc_nbusses++;
}
sc->sc_busses = kmem_zalloc(sizeof(*sc->sc_busses) * sc->sc_nbusses,
KM_SLEEP);
#define BUS_DEVHANDLE(sc, c) \
devhandle_from_acpi((sc)->sc_i2c_mux_devhandle, (c))
idx = 0;
SIMPLEQ_FOREACH(ad, &devnode->ad_child_head, ad_child_list) {
if (ad->ad_devinfo->Type != ACPI_TYPE_DEVICE ||
!acpi_device_present(ad->ad_handle)) {
continue;
}
iicmux_attach_bus(sc, BUS_DEVHANDLE(sc, ad->ad_handle), idx);
idx++;
}
#undef BUS_DEVHANDLE
}
#endif
void
iicmux_attach(struct iicmux_softc * const sc)
{
KASSERT(sc->sc_config != NULL);
KASSERT(sc->sc_i2c_parent != NULL);
sc->sc_i2c_mux_devhandle = device_handle(sc->sc_dev);
sc->sc_mux_data = sc->sc_config->get_mux_info(sc);
if (sc->sc_mux_data == NULL) {
aprint_error_dev(sc->sc_dev, "unable to get info for mux\n");
return;
}
switch (devhandle_type(sc->sc_i2c_mux_devhandle)) {
case DEVHANDLE_TYPE_OF:
iicmux_attach_fdt(sc);
break;
#if NACPICA > 0
case DEVHANDLE_TYPE_ACPI:
iicmux_attach_acpi(sc);
break;
#endif
default:
aprint_error_dev(sc->sc_dev, "could not configure mux: "
"handle type 0x%08x not supported\n",
devhandle_type(sc->sc_i2c_mux_devhandle));
break;
}
}