root/sys/dev/ofw/ofw_misc.c
/*      $OpenBSD: ofw_misc.c,v 1.44 2026/01/25 11:56:57 kettenis Exp $  */
/*
 * Copyright (c) 2017-2021 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <net/if.h>
#include <net/if_media.h>

#include <machine/bus.h>

#include <dev/mii/mii.h>
#include <dev/mii/miivar.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_regulator.h>

/*
 * Register maps.
 */

struct regmap {
        int                     rm_node;
        uint32_t                rm_phandle;
        bus_space_tag_t         rm_tag;
        bus_space_handle_t      rm_handle;
        bus_size_t              rm_size;
        
        LIST_ENTRY(regmap)      rm_list;
};

LIST_HEAD(, regmap) regmaps = LIST_HEAD_INITIALIZER(regmap);

void
regmap_register(int node, bus_space_tag_t tag, bus_space_handle_t handle,
    bus_size_t size)
{
        struct regmap *rm;

        rm = malloc(sizeof(struct regmap), M_DEVBUF, M_WAITOK);
        rm->rm_node = node;
        rm->rm_phandle = OF_getpropint(node, "phandle", 0);
        rm->rm_tag = tag;
        rm->rm_handle = handle;
        rm->rm_size = size;
        LIST_INSERT_HEAD(&regmaps, rm, rm_list);
}

struct regmap *
regmap_bycompatible(char *compatible)
{
        struct regmap *rm;

        LIST_FOREACH(rm, &regmaps, rm_list) {
                if (OF_is_compatible(rm->rm_node, compatible))
                        return rm;
        }

        return NULL;
}

struct regmap *
regmap_bynode(int node)
{
        struct regmap *rm;

        LIST_FOREACH(rm, &regmaps, rm_list) {
                if (rm->rm_node == node)
                        return rm;
        }

        return NULL;
}

struct regmap *
regmap_byphandle(uint32_t phandle)
{
        struct regmap *rm;

        if (phandle == 0)
                return NULL;

        LIST_FOREACH(rm, &regmaps, rm_list) {
                if (rm->rm_phandle == phandle)
                        return rm;
        }

        return NULL;
}

void
regmap_write_4(struct regmap *rm, bus_size_t offset, uint32_t value)
{
        KASSERT(offset <= rm->rm_size - sizeof(uint32_t));
        bus_space_write_4(rm->rm_tag, rm->rm_handle, offset, value);
}

uint32_t
regmap_read_4(struct regmap *rm, bus_size_t offset)
{
        KASSERT(offset <= rm->rm_size - sizeof(uint32_t));
        return bus_space_read_4(rm->rm_tag, rm->rm_handle, offset);
}

/*
 * Network interface support.
 */

LIST_HEAD(, if_device) if_devices =
        LIST_HEAD_INITIALIZER(if_devices);

void
if_register(struct if_device *ifd)
{
        ifd->if_phandle = OF_getpropint(ifd->if_node, "phandle", 0);

        LIST_INSERT_HEAD(&if_devices, ifd, if_list);
}

struct ifnet *
if_bynode(int node)
{
        struct if_device *ifd;

        LIST_FOREACH(ifd, &if_devices, if_list) {
                if (ifd->if_node == node)
                        return (ifd->if_ifp);
        }

        return (NULL);
}

struct ifnet *
if_byphandle(uint32_t phandle)
{
        struct if_device *ifd;

        if (phandle == 0)
                return (NULL);

        LIST_FOREACH(ifd, &if_devices, if_list) {
                if (ifd->if_phandle == phandle)
                        return (ifd->if_ifp);
        }

        return (NULL);
}

/*
 * PHY support.
 */

LIST_HEAD(, phy_device) phy_devices =
        LIST_HEAD_INITIALIZER(phy_devices);

void
phy_register(struct phy_device *pd)
{
        pd->pd_cells = OF_getpropint(pd->pd_node, "#phy-cells", 0);
        pd->pd_phandle = OF_getpropint(pd->pd_node, "phandle", 0);
        if (pd->pd_phandle == 0)
                return;

        LIST_INSERT_HEAD(&phy_devices, pd, pd_list);
}

int
phy_usb_nop_enable(int node)
{
        uint32_t vcc_supply;
        uint32_t *gpio;
        int len;

        vcc_supply = OF_getpropint(node, "vcc-supply", 0);
        if (vcc_supply)
                regulator_enable(vcc_supply);

        len = OF_getproplen(node, "reset-gpios");
        if (len <= 0)
                return 0;

        /* There should only be a single GPIO pin. */
        gpio = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "reset-gpios", gpio, len);

        gpio_controller_config_pin(gpio, GPIO_CONFIG_OUTPUT);
        gpio_controller_set_pin(gpio, 1);
        delay(10000);
        gpio_controller_set_pin(gpio, 0);

        free(gpio, M_TEMP, len);

        return 0;
}

int
phy_enable_cells(uint32_t *cells)
{
        struct phy_device *pd;
        uint32_t phandle = cells[0];
        int node;

        LIST_FOREACH(pd, &phy_devices, pd_list) {
                if (pd->pd_phandle == phandle)
                        break;
        }

        if (pd && pd->pd_enable)
                return pd->pd_enable(pd->pd_cookie, &cells[1]);

        node = OF_getnodebyphandle(phandle);
        if (node == 0)
                return ENXIO;

        if (OF_is_compatible(node, "usb-nop-xceiv"))
                return phy_usb_nop_enable(node);

        return ENXIO;
}

uint32_t *
phy_next_phy(uint32_t *cells)
{
        uint32_t phandle = cells[0];
        int node, ncells;

        node = OF_getnodebyphandle(phandle);
        if (node == 0)
                return NULL;

        ncells = OF_getpropint(node, "#phy-cells", 0);
        return cells + ncells + 1;
}

int
phy_enable_prop_idx(int node, char *prop, int idx)
{
        uint32_t *phys;
        uint32_t *phy;
        int rv = -1;
        int len;

        len = OF_getproplen(node, prop);
        if (len <= 0)
                return -1;

        phys = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, prop, phys, len);

        phy = phys;
        while (phy && phy < phys + (len / sizeof(uint32_t))) {
                if (idx <= 0)
                        rv = phy_enable_cells(phy);
                if (idx == 0)
                        break;
                phy = phy_next_phy(phy);
                idx--;
        }

        free(phys, M_TEMP, len);
        return rv;
}

int
phy_enable_idx(int node, int idx)
{
        return (phy_enable_prop_idx(node, "phys", idx));
}

int
phy_enable(int node, const char *name)
{
        int idx;

        idx = OF_getindex(node, name, "phy-names");
        if (idx == -1)
                return -1;

        return phy_enable_idx(node, idx);
}

/*
 * I2C support.
 */

LIST_HEAD(, i2c_bus) i2c_busses =
        LIST_HEAD_INITIALIZER(i2c_bus);

void
i2c_register(struct i2c_bus *ib)
{
        ib->ib_phandle = OF_getpropint(ib->ib_node, "phandle", 0);
        if (ib->ib_phandle == 0)
                return;

        LIST_INSERT_HEAD(&i2c_busses, ib, ib_list);
}

struct i2c_controller *
i2c_bynode(int node)
{
        struct i2c_bus *ib;

        LIST_FOREACH(ib, &i2c_busses, ib_list) {
                if (ib->ib_node == node)
                        return ib->ib_ic;
        }

        return NULL;
}

struct i2c_controller *
i2c_byphandle(uint32_t phandle)
{
        struct i2c_bus *ib;

        if (phandle == 0)
                return NULL;

        LIST_FOREACH(ib, &i2c_busses, ib_list) {
                if (ib->ib_phandle == phandle)
                        return ib->ib_ic;
        }

        return NULL;
}

/*
 * SFP support.
 */

LIST_HEAD(, sfp_device) sfp_devices =
        LIST_HEAD_INITIALIZER(sfp_devices);

void
sfp_register(struct sfp_device *sd)
{
        sd->sd_phandle = OF_getpropint(sd->sd_node, "phandle", 0);
        if (sd->sd_phandle == 0)
                return;

        LIST_INSERT_HEAD(&sfp_devices, sd, sd_list);
}

int
sfp_do_enable(uint32_t phandle, int enable)
{
        struct sfp_device *sd;

        if (phandle == 0)
                return ENXIO;

        LIST_FOREACH(sd, &sfp_devices, sd_list) {
                if (sd->sd_phandle == phandle)
                        return sd->sd_enable(sd->sd_cookie, enable);
        }

        return ENXIO;
}

int
sfp_enable(uint32_t phandle)
{
        return sfp_do_enable(phandle, 1);
}

int
sfp_disable(uint32_t phandle)
{
        return sfp_do_enable(phandle, 0);
}

int
sfp_get_sffpage(uint32_t phandle, struct if_sffpage *sff)
{
        struct sfp_device *sd;

        if (phandle == 0)
                return ENXIO;

        LIST_FOREACH(sd, &sfp_devices, sd_list) {
                if (sd->sd_phandle == phandle)
                        return sd->sd_get_sffpage(sd->sd_cookie, sff);
        }

        return ENXIO;
}

#define SFF8472_TCC_XCC                 3 /* 10G Ethernet Compliance Codes */
#define SFF8472_TCC_XCC_10G_SR          (1 << 4)
#define SFF8472_TCC_XCC_10G_LR          (1 << 5)
#define SFF8472_TCC_XCC_10G_LRM         (1 << 6)
#define SFF8472_TCC_XCC_10G_ER          (1 << 7)
#define SFF8472_TCC_ECC                 6 /* Ethernet Compliance Codes */
#define SFF8472_TCC_ECC_1000_SX         (1 << 0)
#define SFF8472_TCC_ECC_1000_LX         (1 << 1)
#define SFF8472_TCC_ECC_1000_CX         (1 << 2)
#define SFF8472_TCC_ECC_1000_T          (1 << 3)
#define SFF8472_TCC_SCT                 8 /* SFP+ Cable Technology */
#define SFF8472_TCC_SCT_PASSIVE         (1 << 2)
#define SFF8472_TCC_SCT_ACTIVE          (1 << 3)

int
sfp_add_media(uint32_t phandle, struct mii_data *mii)
{
        struct if_sffpage sff;
        int error;

        memset(&sff, 0, sizeof(sff));
        sff.sff_addr = IFSFF_ADDR_EEPROM;
        sff.sff_page = 0;

        error = sfp_get_sffpage(phandle, &sff);
        if (error)
                return error;

        /* SFP */
        if (sff.sff_data[SFF8472_TCC_ECC] & SFF8472_TCC_ECC_1000_SX) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_1000_SX, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_1000_SX | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_ECC] & SFF8472_TCC_ECC_1000_LX) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_1000_LX, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_1000_LX | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_ECC] & SFF8472_TCC_ECC_1000_CX) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_1000_CX, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_1000_CX | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_ECC] & SFF8472_TCC_ECC_1000_T) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_1000_T, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_1000_T | IFM_FDX;
        }

        /* SFP+ */
        if (sff.sff_data[SFF8472_TCC_XCC] & SFF8472_TCC_XCC_10G_SR) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_10G_SR, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_10G_SR | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_XCC] & SFF8472_TCC_XCC_10G_LR) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_10G_LR, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_10G_LR | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_XCC] & SFF8472_TCC_XCC_10G_LRM) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_10G_LRM, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_10G_LRM | IFM_FDX;
        }
        if (sff.sff_data[SFF8472_TCC_XCC] & SFF8472_TCC_XCC_10G_ER) {
                ifmedia_add(&mii->mii_media, IFM_ETHER | IFM_10G_ER, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_10G_ER | IFM_FDX;
        }

        /* SFP+ DAC */
        if (sff.sff_data[SFF8472_TCC_SCT] & SFF8472_TCC_SCT_PASSIVE ||
            sff.sff_data[SFF8472_TCC_SCT] & SFF8472_TCC_SCT_ACTIVE) {
                ifmedia_add(&mii->mii_media,
                    IFM_ETHER | IFM_10G_SFP_CU, 0, NULL);
                mii->mii_media_active = IFM_ETHER | IFM_10G_SFP_CU | IFM_FDX;
        }

        return 0;
}

/*
 * PWM support.
 */

LIST_HEAD(, pwm_device) pwm_devices =
        LIST_HEAD_INITIALIZER(pwm_devices);

void
pwm_register(struct pwm_device *pd)
{
        pd->pd_cells = OF_getpropint(pd->pd_node, "#pwm-cells", 0);
        pd->pd_phandle = OF_getpropint(pd->pd_node, "phandle", 0);
        if (pd->pd_phandle == 0)
                return;

        LIST_INSERT_HEAD(&pwm_devices, pd, pd_list);

}

int
pwm_init_state(uint32_t *cells, struct pwm_state *ps)
{
        struct pwm_device *pd;

        LIST_FOREACH(pd, &pwm_devices, pd_list) {
                if (pd->pd_phandle == cells[0]) {
                        memset(ps, 0, sizeof(struct pwm_state));
                        pd->pd_get_state(pd->pd_cookie, &cells[1], ps);
                        ps->ps_pulse_width = 0;
                        if (pd->pd_cells >= 2)
                                ps->ps_period = cells[2];
                        if (pd->pd_cells >= 3)
                                ps->ps_flags = cells[3];
                        return 0;
                }
        }

        return ENXIO;
}

int
pwm_get_state(uint32_t *cells, struct pwm_state *ps)
{
        struct pwm_device *pd;

        LIST_FOREACH(pd, &pwm_devices, pd_list) {
                if (pd->pd_phandle == cells[0])
                        return pd->pd_get_state(pd->pd_cookie, &cells[1], ps);
        }

        return ENXIO;
}

int
pwm_set_state(uint32_t *cells, struct pwm_state *ps)
{
        struct pwm_device *pd;

        LIST_FOREACH(pd, &pwm_devices, pd_list) {
                if (pd->pd_phandle == cells[0])
                        return pd->pd_set_state(pd->pd_cookie, &cells[1], ps);
        }

        return ENXIO;
}

/*
 * Non-volatile memory support.
 */

LIST_HEAD(, nvmem_device) nvmem_devices =
        LIST_HEAD_INITIALIZER(nvmem_devices);

struct nvmem_cell {
        uint32_t        nc_phandle;
        struct nvmem_device *nc_nd;
        bus_addr_t      nc_addr;
        bus_size_t      nc_size;
        uint32_t        nc_offset;
        uint32_t        nc_bitlen;

        LIST_ENTRY(nvmem_cell) nc_list;
};

LIST_HEAD(, nvmem_cell) nvmem_cells =
        LIST_HEAD_INITIALIZER(nvmem_cells);

void
nvmem_register_child(int node, struct nvmem_device *nd)
{
        struct nvmem_cell *nc;
        uint32_t phandle;
        uint32_t reg[2], bits[2] = {};

        phandle = OF_getpropint(node, "phandle", 0);
        if (phandle == 0)
                return;

        if (OF_getpropintarray(node, "reg", reg, sizeof(reg)) != sizeof(reg))
                return;

        OF_getpropintarray(node, "bits", bits, sizeof(bits));
        
        nc = malloc(sizeof(struct nvmem_cell), M_DEVBUF, M_WAITOK);
        nc->nc_phandle = phandle;
        nc->nc_nd = nd;
        nc->nc_addr = reg[0];
        nc->nc_size = reg[1];
        nc->nc_offset = bits[0];
        nc->nc_bitlen = bits[1];
        LIST_INSERT_HEAD(&nvmem_cells, nc, nc_list);
}

void
nvmem_register(struct nvmem_device *nd)
{
        int node;

        nd->nd_phandle = OF_getpropint(nd->nd_node, "phandle", 0);
        if (nd->nd_phandle)
                LIST_INSERT_HEAD(&nvmem_devices, nd, nd_list);

        for (node = OF_child(nd->nd_node); node; node = OF_peer(node))
                nvmem_register_child(node, nd);
}

int
nvmem_read(uint32_t phandle, bus_addr_t addr, void *data, bus_size_t size)
{
        struct nvmem_device *nd;

        if (phandle == 0)
                return ENXIO;

        LIST_FOREACH(nd, &nvmem_devices, nd_list) {
                if (nd->nd_phandle == phandle)
                        return nd->nd_read(nd->nd_cookie, addr, data, size);
        }

        return ENXIO;
}

int
nvmem_read_cell(int node, const char *name, void *data, bus_size_t size)
{
        struct nvmem_device *nd;
        struct nvmem_cell *nc;
        uint8_t *p = data;
        bus_addr_t addr;
        uint32_t phandle, *phandles;
        uint32_t offset, bitlen;
        int id, len, first;

        id = OF_getindex(node, name, "nvmem-cell-names");
        if (id < 0)
                return ENXIO;

        len = OF_getproplen(node, "nvmem-cells");
        if (len <= 0)
                return ENXIO;

        phandles = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "nvmem-cells", phandles, len);
        phandle = phandles[id];
        free(phandles, M_TEMP, len);

        LIST_FOREACH(nc, &nvmem_cells, nc_list) {
                if (nc->nc_phandle == phandle)
                        break;
        }
        if (nc == NULL)
                return ENXIO;

        nd = nc->nc_nd;
        if (nd->nd_read == NULL)
                return EACCES;

        first = 1;
        addr = nc->nc_addr + (nc->nc_offset / 8);
        offset = nc->nc_offset % 8;
        bitlen = nc->nc_bitlen;
        while (bitlen > 0 && size > 0) {
                uint8_t mask, tmp;
                int error;

                error = nd->nd_read(nd->nd_cookie, addr++, &tmp, 1);
                if (error)
                        return error;

                if (bitlen >= 8)
                        mask = 0xff;
                else
                        mask = (1 << bitlen) - 1;

                if (!first) {
                        *p++ |= (tmp << (8 - offset)) & (mask << (8 - offset));
                        bitlen -= MIN(offset, bitlen);
                        mask >>= offset;
                        size--;
                }

                if (bitlen > 0 && size > 0) {
                        *p = (tmp >> offset) & mask;
                        bitlen -= MIN(8 - offset, bitlen);
                }

                first = 0;
        }
        if (nc->nc_bitlen > 0)
                return 0;

        if (size > nc->nc_size)
                return EINVAL;

        return nd->nd_read(nd->nd_cookie, nc->nc_addr, data, size);
}

int
nvmem_write_cell(int node, const char *name, const void *data, bus_size_t size)
{
        struct nvmem_device *nd;
        struct nvmem_cell *nc;
        const uint8_t *p = data;
        bus_addr_t addr;
        uint32_t phandle, *phandles;
        uint32_t offset, bitlen;
        int id, len, first;

        id = OF_getindex(node, name, "nvmem-cell-names");
        if (id < 0)
                return ENXIO;

        len = OF_getproplen(node, "nvmem-cells");
        if (len <= 0)
                return ENXIO;

        phandles = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "nvmem-cells", phandles, len);
        phandle = phandles[id];
        free(phandles, M_TEMP, len);

        LIST_FOREACH(nc, &nvmem_cells, nc_list) {
                if (nc->nc_phandle == phandle)
                        break;
        }
        if (nc == NULL)
                return ENXIO;

        nd = nc->nc_nd;
        if (nd->nd_write == NULL)
                return EACCES;

        first = 1;
        addr = nc->nc_addr + (nc->nc_offset / 8);
        offset = nc->nc_offset % 8;
        bitlen = nc->nc_bitlen;
        while (bitlen > 0 && size > 0) {
                uint8_t mask, tmp;
                int error;

                error = nd->nd_read(nd->nd_cookie, addr, &tmp, 1);
                if (error)
                        return error;

                if (bitlen >= 8)
                        mask = 0xff;
                else
                        mask = (1 << bitlen) - 1;

                tmp &= ~(mask << offset);
                tmp |= (*p++ << offset) & (mask << offset);
                bitlen -= MIN(8 - offset, bitlen);
                size--;

                if (!first && bitlen > 0 && size > 0) {
                        tmp &= ~(mask >> (8 - offset));
                        tmp |= (*p >> (8 - offset)) & (mask >> (8 - offset));
                        bitlen -= MIN(offset, bitlen);
                }

                error = nd->nd_write(nd->nd_cookie, addr++, &tmp, 1);
                if (error)
                        return error;

                first = 0;
        }
        if (nc->nc_bitlen > 0)
                return 0;

        if (size > nc->nc_size)
                return EINVAL;

        return nd->nd_write(nd->nd_cookie, nc->nc_addr, data, size);
}

/* Port/endpoint interface support */

LIST_HEAD(, endpoint) endpoints =
        LIST_HEAD_INITIALIZER(endpoints);

void
endpoint_register(int node, struct device_port *dp, enum endpoint_type type)
{
        struct endpoint *ep;

        ep = malloc(sizeof(*ep), M_DEVBUF, M_WAITOK);
        ep->ep_node = node;
        ep->ep_phandle = OF_getpropint(node, "phandle", 0);
        ep->ep_reg = OF_getpropint(node, "reg", -1);
        ep->ep_port = dp;
        ep->ep_type = type;

        LIST_INSERT_HEAD(&endpoints, ep, ep_list);
        LIST_INSERT_HEAD(&dp->dp_endpoints, ep, ep_plist);
}

void
device_port_register(int node, struct device_ports *ports,
    enum endpoint_type type)
{
        struct device_port *dp;

        dp = malloc(sizeof(*dp), M_DEVBUF, M_WAITOK);
        dp->dp_node = node;
        dp->dp_phandle = OF_getpropint(node, "phandle", 0);
        dp->dp_reg = OF_getpropint(node, "reg", -1);
        dp->dp_ports = ports;
        LIST_INIT(&dp->dp_endpoints);
        for (node = OF_child(node); node; node = OF_peer(node))
                endpoint_register(node, dp, type);

        LIST_INSERT_HEAD(&ports->dp_ports, dp, dp_list);
}

void
device_ports_register(struct device_ports *ports,
    enum endpoint_type type)
{
        int node;

        LIST_INIT(&ports->dp_ports);

        node = OF_getnodebyname(ports->dp_node, "ports");
        if (node == 0) {
                node = OF_getnodebyname(ports->dp_node, "port");
                if (node == 0)
                        return;
                
                device_port_register(node, ports, type);
                return;
        }

        for (node = OF_child(node); node; node = OF_peer(node))
                device_port_register(node, ports, type);
}

struct device_ports *
device_ports_byphandle(uint32_t phandle)
{
        struct endpoint *ep;

        if (phandle == 0)
                return NULL;

        LIST_FOREACH(ep, &endpoints, ep_list) {
                if (ep->ep_port->dp_phandle == phandle)
                        return ep->ep_port->dp_ports;
        }

        return NULL;
}

struct endpoint *
endpoint_byphandle(uint32_t phandle)
{
        struct endpoint *ep;

        if (phandle == 0)
                return NULL;

        LIST_FOREACH(ep, &endpoints, ep_list) {
                if (ep->ep_phandle == phandle)
                        return ep;
        }

        return NULL;
}

struct endpoint *
endpoint_byreg(struct device_ports *ports, uint32_t dp_reg, uint32_t ep_reg)
{
        struct device_port *dp;
        struct endpoint *ep;

        LIST_FOREACH(dp, &ports->dp_ports, dp_list) {
                if (dp->dp_reg != dp_reg)
                        continue;
                LIST_FOREACH(ep, &dp->dp_endpoints, ep_list) {
                        if (ep->ep_reg != ep_reg)
                                continue;
                        return ep;
                }
        }

        return NULL;
}

struct endpoint *
endpoint_remote(struct endpoint *ep)
{
        struct endpoint *rep;
        int phandle;

        phandle = OF_getpropint(ep->ep_node, "remote-endpoint", 0);
        if (phandle == 0)
                return NULL;

        LIST_FOREACH(rep, &endpoints, ep_list) {
                if (rep->ep_phandle == phandle)
                        return rep;
        }

        return NULL;
}

int
endpoint_activate(struct endpoint *ep, void *arg)
{
        struct device_ports *ports = ep->ep_port->dp_ports;
        return ports->dp_ep_activate(ports->dp_cookie, ep, arg);
}

void *
endpoint_get_cookie(struct endpoint *ep)
{
        struct device_ports *ports = ep->ep_port->dp_ports;
        return ports->dp_ep_get_cookie(ports->dp_cookie, ep);
}

int
device_port_activate(uint32_t phandle, void *arg)
{
        struct device_port *dp = NULL;
        struct endpoint *ep, *rep;
        int count;
        int error;

        if (phandle == 0)
                return ENXIO;

        LIST_FOREACH(ep, &endpoints, ep_list) {
                if (ep->ep_port->dp_phandle == phandle) {
                        dp = ep->ep_port;
                        break;
                }
        }
        if (dp == NULL)
                return ENXIO;

        count = 0;
        LIST_FOREACH(ep, &dp->dp_endpoints, ep_plist) {
                rep = endpoint_remote(ep);
                if (rep == NULL)
                        continue;

                error = endpoint_activate(ep, arg);
                if (error)
                        continue;
                error = endpoint_activate(rep, arg);
                if (error)
                        continue;
                count++;
        }

        return count ? 0 : ENXIO;
}

/* Digital audio interface support */

LIST_HEAD(, dai_device) dai_devices =
        LIST_HEAD_INITIALIZER(dai_devices);

void *
dai_ep_get_cookie(void *cookie, struct endpoint *ep)
{
        return cookie;
}

void
dai_register(struct dai_device *dd)
{
        dd->dd_phandle = OF_getpropint(dd->dd_node, "phandle", 0);
        if (dd->dd_phandle != 0)
                LIST_INSERT_HEAD(&dai_devices, dd, dd_list);

        dd->dd_ports.dp_node = dd->dd_node;
        dd->dd_ports.dp_cookie = dd;
        dd->dd_ports.dp_ep_get_cookie = dai_ep_get_cookie;
        device_ports_register(&dd->dd_ports, EP_DAI_DEVICE);
}

struct dai_device *
dai_byphandle(uint32_t phandle)
{
        struct dai_device *dd;

        if (phandle == 0)
                return NULL;

        LIST_FOREACH(dd, &dai_devices, dd_list) {
                if (dd->dd_phandle == phandle)
                        return dd;
        }

        return NULL;
}

/* MII support */

LIST_HEAD(, mii_bus) mii_busses =
        LIST_HEAD_INITIALIZER(mii_busses);

void
mii_register(struct mii_bus *md)
{
        LIST_INSERT_HEAD(&mii_busses, md, md_list);
}

struct mii_bus *
mii_bynode(int node)
{
        struct mii_bus *md;

        LIST_FOREACH(md, &mii_busses, md_list) {
                if (md->md_node == node)
                        return md;
        }

        return NULL;
}

struct mii_bus *
mii_byphandle(uint32_t phandle)
{
        int node;

        if (phandle == 0)
                return NULL;

        node = OF_getnodebyphandle(phandle);
        if (node == 0)
                return NULL;

        node = OF_parent(node);
        if (node == 0)
                return NULL;

        return mii_bynode(node);
}

/* IOMMU support */

LIST_HEAD(, iommu_device) iommu_devices =
        LIST_HEAD_INITIALIZER(iommu_devices);

void
iommu_device_register(struct iommu_device *id)
{
        id->id_phandle = OF_getpropint(id->id_node, "phandle", 0);
        if (id->id_phandle == 0)
                return;

        LIST_INSERT_HEAD(&iommu_devices, id, id_list);
}

bus_dma_tag_t
iommu_device_do_map(uint32_t phandle, uint32_t *cells, bus_dma_tag_t dmat)
{
        struct iommu_device *id;

        if (phandle == 0)
                return dmat;

        LIST_FOREACH(id, &iommu_devices, id_list) {
                if (id->id_phandle == phandle)
                        return id->id_map(id->id_cookie, cells, dmat);
        }

        return dmat;
}

int
iommu_device_lookup(int node, uint32_t *phandle, uint32_t *cells)
{
        uint32_t *cell;
        uint32_t *map;
        int len, icells, ncells;
        int ret = 1;
        int i;

        len = OF_getproplen(node, "iommus");
        if (len <= 0)
                return ret;

        map = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "iommus", map, len);

        cell = map;
        ncells = len / sizeof(uint32_t);
        while (ncells > 1) {
                node = OF_getnodebyphandle(cell[0]);
                if (node == 0)
                        goto out;

                icells = OF_getpropint(node, "#iommu-cells", 1);
                if (ncells < icells + 1)
                        goto out;

                KASSERT(icells <= 2);

                *phandle = cell[0];
                for (i = 0; i < icells; i++)
                        cells[i] = cell[1 + i];
                ret = 0;
                break;

                cell += (1 + icells);
                ncells -= (1 + icells);
        }

out:
        free(map, M_TEMP, len);

        return ret;
}

int
iommu_device_lookup_pci(int node, uint32_t rid, uint32_t *phandle,
    uint32_t *cells)
{
        uint32_t sid_base;
        uint32_t *cell;
        uint32_t *map;
        uint32_t mask, rid_base;
        int len, length, icells, ncells;
        int ret = 1;

        len = OF_getproplen(node, "iommu-map");
        if (len <= 0)
                return ret;

        map = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "iommu-map", map, len);

        mask = OF_getpropint(node, "iommu-map-mask", 0xffff);
        rid = rid & mask;

        cell = map;
        ncells = len / sizeof(uint32_t);
        while (ncells > 3) {
                node = OF_getnodebyphandle(cell[1]);
                if (node == 0)
                        goto out;

                icells = OF_getpropint(node, "#iommu-cells", 1);
                KASSERT(icells <= 2);

                rid_base = cell[0];
                sid_base = cell[2];
                length = cell[3];
                if (rid >= rid_base && rid < rid_base + length) {
                        cells[0] = sid_base + (rid - rid_base);
                        *phandle = cell[1];
                        ret = 0;
                        break;
                }

                cell += 4;
                ncells -= 4;
        }

out:
        free(map, M_TEMP, len);

        return ret;
}

bus_dma_tag_t
iommu_device_map(int node, bus_dma_tag_t dmat)
{
        uint32_t phandle, cells[2] = {0};

        if (iommu_device_lookup(node, &phandle, &cells[0]))
                return dmat;

        return iommu_device_do_map(phandle, &cells[0], dmat);
}

bus_dma_tag_t
iommu_device_map_pci(int node, uint32_t rid, bus_dma_tag_t dmat)
{
        uint32_t phandle, cells[2] = {0};

        if (iommu_device_lookup_pci(node, rid, &phandle, &cells[0]))
                return dmat;

        return iommu_device_do_map(phandle, &cells[0], dmat);
}

void
iommu_device_do_reserve(uint32_t phandle, uint32_t *cells, bus_addr_t addr,
    bus_size_t size)
{
        struct iommu_device *id;

        if (phandle == 0)
                return;

        LIST_FOREACH(id, &iommu_devices, id_list) {
                if (id->id_phandle == phandle) {
                        id->id_reserve(id->id_cookie, cells, addr, size);
                        break;
                }
        }
}

void
iommu_reserve_region_pci(int node, uint32_t rid, bus_addr_t addr,
    bus_size_t size)
{
        uint32_t phandle, cells[2] = {0};

        if (iommu_device_lookup_pci(node, rid, &phandle, &cells[0]))
                return;

        return iommu_device_do_reserve(phandle, &cells[0], addr, size);
}

/*
 * Mailbox support.
 */

struct mbox_channel {
        struct mbox_device      *mc_md;
        void                    *mc_cookie;
};

LIST_HEAD(, mbox_device) mbox_devices =
        LIST_HEAD_INITIALIZER(mbox_devices);

void
mbox_register(struct mbox_device *md)
{
        md->md_cells = OF_getpropint(md->md_node, "#mbox-cells", 0);
        md->md_phandle = OF_getpropint(md->md_node, "phandle", 0);
        if (md->md_phandle == 0)
                return;

        LIST_INSERT_HEAD(&mbox_devices, md, md_list);
}

struct mbox_channel *
mbox_channel_cells(uint32_t *cells, struct mbox_client *client)
{
        struct mbox_device *md;
        struct mbox_channel *mc;
        uint32_t phandle = cells[0];
        void *cookie;

        LIST_FOREACH(md, &mbox_devices, md_list) {
                if (md->md_phandle == phandle)
                        break;
        }

        if (md && md->md_channel) {
                cookie = md->md_channel(md->md_cookie, &cells[1], client);
                if (cookie) {
                        mc = malloc(sizeof(*mc), M_DEVBUF, M_WAITOK);
                        mc->mc_md = md;
                        mc->mc_cookie = cookie;
                        return mc;
                }
        }

        return NULL;
}

uint32_t *
mbox_next_mbox(uint32_t *cells)
{
        uint32_t phandle = cells[0];
        int node, ncells;

        node = OF_getnodebyphandle(phandle);
        if (node == 0)
                return NULL;

        ncells = OF_getpropint(node, "#mbox-cells", 0);
        return cells + ncells + 1;
}

struct mbox_channel *
mbox_channel_idx(int node, int idx, struct mbox_client *client)
{
        struct mbox_channel *mc = NULL;
        uint32_t *mboxes;
        uint32_t *mbox;
        int len;

        len = OF_getproplen(node, "mboxes");
        if (len <= 0)
                return NULL;

        mboxes = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "mboxes", mboxes, len);

        mbox = mboxes;
        while (mbox && mbox < mboxes + (len / sizeof(uint32_t))) {
                if (idx == 0) {
                        mc = mbox_channel_cells(mbox, client);
                        break;
                }
                mbox = mbox_next_mbox(mbox);
                idx--;
        }

        free(mboxes, M_TEMP, len);
        return mc;
}

struct mbox_channel *
mbox_channel(int node, const char *name, struct mbox_client *client)
{
        int idx;

        idx = OF_getindex(node, name, "mbox-names");
        if (idx == -1)
                return NULL;

        return mbox_channel_idx(node, idx, client);
}

int
mbox_send(struct mbox_channel *mc, const void *data, size_t len)
{
        struct mbox_device *md = mc->mc_md;

        if (md->md_send)
                return md->md_send(mc->mc_cookie, data, len);

        return ENXIO;
}

int
mbox_recv(struct mbox_channel *mc, void *data, size_t len)
{
        struct mbox_device *md = mc->mc_md;

        if (md->md_recv)
                return md->md_recv(mc->mc_cookie, data, len);

        return ENXIO;
}

/* hwlock support */

LIST_HEAD(, hwlock_device) hwlock_devices =
        LIST_HEAD_INITIALIZER(hwlock_devices);

void
hwlock_register(struct hwlock_device *hd)
{
        hd->hd_cells = OF_getpropint(hd->hd_node, "#hwlock-cells", 0);
        hd->hd_phandle = OF_getpropint(hd->hd_node, "phandle", 0);
        if (hd->hd_phandle == 0)
                return;

        LIST_INSERT_HEAD(&hwlock_devices, hd, hd_list);
}

int
hwlock_lock_cells(uint32_t *cells, int lock)
{
        struct hwlock_device *hd;
        uint32_t phandle = cells[0];

        LIST_FOREACH(hd, &hwlock_devices, hd_list) {
                if (hd->hd_phandle == phandle)
                        break;
        }

        if (hd && hd->hd_lock)
                return hd->hd_lock(hd->hd_cookie, &cells[1], lock);

        return ENXIO;
}

uint32_t *
hwlock_next_hwlock(uint32_t *cells)
{
        uint32_t phandle = cells[0];
        int node, ncells;

        node = OF_getnodebyphandle(phandle);
        if (node == 0)
                return NULL;

        ncells = OF_getpropint(node, "#hwlock-cells", 0);
        return cells + ncells + 1;
}

int
hwlock_do_lock_idx(int node, int idx, int lock)
{
        uint32_t *hwlocks;
        uint32_t *hwlock;
        int rv = -1;
        int len;

        len = OF_getproplen(node, "hwlocks");
        if (len <= 0)
                return -1;

        hwlocks = malloc(len, M_TEMP, M_WAITOK);
        OF_getpropintarray(node, "hwlocks", hwlocks, len);

        hwlock = hwlocks;
        while (hwlock && hwlock < hwlocks + (len / sizeof(uint32_t))) {
                if (idx <= 0)
                        rv = hwlock_lock_cells(hwlock, lock);
                if (idx == 0)
                        break;
                hwlock = hwlock_next_hwlock(hwlock);
                idx--;
        }

        free(hwlocks, M_TEMP, len);
        return rv;
}

int
hwlock_lock_idx(int node, int idx)
{
        return hwlock_do_lock_idx(node, idx, 1);
}

int
hwlock_lock_idx_timeout(int node, int idx, int ms)
{
        int i, ret = ENXIO;

        for (i = 0; i <= ms; i++) {
                ret = hwlock_do_lock_idx(node, idx, 1);
                if (ret == EAGAIN) {
                        delay(1000);
                        continue;
                }
                break;
        }

        return ret;
}

int
hwlock_unlock_idx(int node, int idx)
{
        return hwlock_do_lock_idx(node, idx, 0);
}