root/sys/arch/macppc/dev/ofw_i2c_machdep.c
/*      $NetBSD: ofw_i2c_machdep.c,v 1.2 2025/09/21 19:12:45 thorpej Exp $      */

/*
 * Copyright (c) 2021, 2025 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: ofw_i2c_machdep.c,v 1.2 2025/09/21 19:12:45 thorpej Exp $");

#include <sys/param.h>
#include <sys/device.h>
#include <sys/endian.h>
#include <sys/kmem.h>
#include <sys/systm.h>

#include <dev/ofw/openfirm.h>

#include <dev/i2c/i2cvar.h>
#include <dev/i2c/i2c_calls.h>
#include <dev/i2c/i2c_enum.h>

/*
 * Apple OpenFirmware implementations have the i2c device
 * address shifted left 1 bit to account for the r/w bit
 * on the wire.  Some implementations also encode the channel
 * number (for multi-channel controllers) in bit 8 of the
 * address.
 */
#define OFW_I2C_ADDRESS_MASK    __BITS(1,7)
#define OFW_I2C_ADDRESS_CHMASK  __BIT(8)

static bool
of_i2c_get_address(device_t dev, int i2c_node, i2c_tag_t tag, int node,
    uint32_t *addrp)
{
        uint32_t reg;
        uint32_t addr;
        int channel;

        /*
         * dev is the iic bus instance.  We need the controller parent
         * so we can compare their OFW nodes.
         */
        int ctlr_node = devhandle_to_of(device_handle(device_parent(dev)));

        if (OF_getprop(node, "reg", &reg, sizeof(reg)) != sizeof(reg)) {
                /*
                 * Some Apple OpenFirmware implementations use "i2c-address"
                 * instead of "reg".
                 */
                if (OF_getprop(node, "i2c-address", &reg,
                               sizeof(reg)) != sizeof(reg)) {
                        /*
                         * No address property; reject the device.
                         */
                        return false;
                }
        }

        addr = __SHIFTOUT(reg, OFW_I2C_ADDRESS_MASK);
        channel = __SHIFTOUT(reg, OFW_I2C_ADDRESS_CHMASK);

        aprint_debug_dev(dev, "%s: addr=0x%x channel=%u\n", __func__,
            addr, channel);

        /*
         * If the controller supports multiple channels and the controller
         * node and the i2c bus node are the same, then the devices for
         * multiple channels are all mixed together in the device tree.
         * We need to filter them by channel in this case.
         */
        aprint_debug_dev(dev, "%s: ic_channel=%d i2c_node=%d ctlr_node=%d\n",
            __func__, tag->ic_channel, i2c_node, ctlr_node);
        if (tag->ic_channel != I2C_CHANNEL_DEFAULT && i2c_node == ctlr_node &&
            tag->ic_channel != channel) {
                return false;
        }

        *addrp = addr;
        return true;
}

static int
of_i2c_enumerate_devices(device_t dev, devhandle_t call_handle, void *v)
{
        struct i2c_enumerate_devices_args *args = v;
        int i2c_node, node;
        char name[32], compat_buf[32];
        uint32_t addr;
        char *clist;
        int clist_size;
        bool cbrv;

        i2c_node = devhandle_to_of(call_handle);

        for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
                if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
                        continue;
                }
                if (!of_i2c_get_address(dev, i2c_node, args->ia->ia_tag,
                                        node, &addr)) {
                        continue;
                }

                /*
                 * Some of Apple's older OpenFirmware implementations are
                 * rife with nodes lacking "compatible" properties.
                 */     
                clist_size = OF_getproplen(node, "compatible");
                if (clist_size <= 0) {
                        clist = name;
                        clist_size = 0;
                } else {
                        clist = kmem_tmpbuf_alloc(clist_size,
                            compat_buf, sizeof(compat_buf), KM_SLEEP);
                        if (OF_getprop(node, "compatible", clist, clist_size)
                                       < clist_size) {
                                kmem_tmpbuf_free(clist, clist_size, compat_buf);
                                continue;
                        }
                }

                cbrv = i2c_enumerate_device(dev, args, name,
                    clist, clist_size, addr,
                    devhandle_from_of(call_handle, node));

                if (clist != name) {
                        kmem_tmpbuf_free(clist, clist_size, compat_buf);
                }

                if (!cbrv) {
                        break;
                }
        }

        return 0;
}
OF_DEVICE_CALL_REGISTER(I2C_ENUMERATE_DEVICES_STR,
                        of_i2c_enumerate_devices);