Home | History | Annotate | Line # | Download | only in dev
      1 /*	$NetBSD: ofw_i2c_machdep.c,v 1.2 2025/09/21 19:12:45 thorpej Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2021, 2025 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 __KERNEL_RCSID(0, "$NetBSD: ofw_i2c_machdep.c,v 1.2 2025/09/21 19:12:45 thorpej Exp $");
     31 
     32 #include <sys/param.h>
     33 #include <sys/device.h>
     34 #include <sys/endian.h>
     35 #include <sys/kmem.h>
     36 #include <sys/systm.h>
     37 
     38 #include <dev/ofw/openfirm.h>
     39 
     40 #include <dev/i2c/i2cvar.h>
     41 #include <dev/i2c/i2c_calls.h>
     42 #include <dev/i2c/i2c_enum.h>
     43 
     44 /*
     45  * Apple OpenFirmware implementations have the i2c device
     46  * address shifted left 1 bit to account for the r/w bit
     47  * on the wire.  Some implementations also encode the channel
     48  * number (for multi-channel controllers) in bit 8 of the
     49  * address.
     50  */
     51 #define	OFW_I2C_ADDRESS_MASK	__BITS(1,7)
     52 #define	OFW_I2C_ADDRESS_CHMASK	__BIT(8)
     53 
     54 static bool
     55 of_i2c_get_address(device_t dev, int i2c_node, i2c_tag_t tag, int node,
     56     uint32_t *addrp)
     57 {
     58 	uint32_t reg;
     59 	uint32_t addr;
     60 	int channel;
     61 
     62 	/*
     63 	 * dev is the iic bus instance.  We need the controller parent
     64 	 * so we can compare their OFW nodes.
     65 	 */
     66 	int ctlr_node = devhandle_to_of(device_handle(device_parent(dev)));
     67 
     68 	if (OF_getprop(node, "reg", &reg, sizeof(reg)) != sizeof(reg)) {
     69 		/*
     70 		 * Some Apple OpenFirmware implementations use "i2c-address"
     71 		 * instead of "reg".
     72 		 */
     73 		if (OF_getprop(node, "i2c-address", &reg,
     74 			       sizeof(reg)) != sizeof(reg)) {
     75 			/*
     76 			 * No address property; reject the device.
     77 			 */
     78 			return false;
     79 		}
     80 	}
     81 
     82 	addr = __SHIFTOUT(reg, OFW_I2C_ADDRESS_MASK);
     83 	channel = __SHIFTOUT(reg, OFW_I2C_ADDRESS_CHMASK);
     84 
     85 	aprint_debug_dev(dev, "%s: addr=0x%x channel=%u\n", __func__,
     86 	    addr, channel);
     87 
     88 	/*
     89 	 * If the controller supports multiple channels and the controller
     90 	 * node and the i2c bus node are the same, then the devices for
     91 	 * multiple channels are all mixed together in the device tree.
     92 	 * We need to filter them by channel in this case.
     93 	 */
     94 	aprint_debug_dev(dev, "%s: ic_channel=%d i2c_node=%d ctlr_node=%d\n",
     95 	    __func__, tag->ic_channel, i2c_node, ctlr_node);
     96 	if (tag->ic_channel != I2C_CHANNEL_DEFAULT && i2c_node == ctlr_node &&
     97 	    tag->ic_channel != channel) {
     98 		return false;
     99 	}
    100 
    101 	*addrp = addr;
    102 	return true;
    103 }
    104 
    105 static int
    106 of_i2c_enumerate_devices(device_t dev, devhandle_t call_handle, void *v)
    107 {
    108 	struct i2c_enumerate_devices_args *args = v;
    109 	int i2c_node, node;
    110 	char name[32], compat_buf[32];
    111 	uint32_t addr;
    112 	char *clist;
    113 	int clist_size;
    114 	bool cbrv;
    115 
    116 	i2c_node = devhandle_to_of(call_handle);
    117 
    118 	for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
    119 		if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
    120 			continue;
    121 		}
    122 		if (!of_i2c_get_address(dev, i2c_node, args->ia->ia_tag,
    123 					node, &addr)) {
    124 			continue;
    125 		}
    126 
    127 		/*
    128 		 * Some of Apple's older OpenFirmware implementations are
    129 		 * rife with nodes lacking "compatible" properties.
    130 		 */
    131 		clist_size = OF_getproplen(node, "compatible");
    132 		if (clist_size <= 0) {
    133 			clist = name;
    134 			clist_size = 0;
    135 		} else {
    136 			clist = kmem_tmpbuf_alloc(clist_size,
    137 			    compat_buf, sizeof(compat_buf), KM_SLEEP);
    138 			if (OF_getprop(node, "compatible", clist, clist_size)
    139 				       < clist_size) {
    140 				kmem_tmpbuf_free(clist, clist_size, compat_buf);
    141 				continue;
    142 			}
    143 		}
    144 
    145 		cbrv = i2c_enumerate_device(dev, args, name,
    146 		    clist, clist_size, addr,
    147 		    devhandle_from_of(call_handle, node));
    148 
    149 		if (clist != name) {
    150 			kmem_tmpbuf_free(clist, clist_size, compat_buf);
    151 		}
    152 
    153 		if (!cbrv) {
    154 			break;
    155 		}
    156 	}
    157 
    158 	return 0;
    159 }
    160 OF_DEVICE_CALL_REGISTER(I2C_ENUMERATE_DEVICES_STR,
    161 			of_i2c_enumerate_devices);
    162