/* $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 __KERNEL_RCSID(0, "$NetBSD: ofw_i2c_machdep.c,v 1.2 2025/09/21 19:12:45 thorpej Exp $"); #include #include #include #include #include #include #include #include #include /* * 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", ®, sizeof(reg)) != sizeof(reg)) { /* * Some Apple OpenFirmware implementations use "i2c-address" * instead of "reg". */ if (OF_getprop(node, "i2c-address", ®, 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);