Home | History | Annotate | Line # | Download | only in ofw
ofw_i2c_subr.c revision 1.1.16.3
      1 /*	$NetBSD: ofw_i2c_subr.c,v 1.1.16.3 2021/09/11 12:44:49 thorpej Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2021 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_subr.c,v 1.1.16.3 2021/09/11 12:44:49 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 #include <dev/ofw/openfirm.h>
     38 #include <dev/i2c/i2cvar.h>
     39 
     40 #ifdef __HAVE_OPENFIRMWARE_VARIANT_AAPL
     41 /*
     42  * Apple OpenFirmware implementations have the i2c device
     43  * address shifted left 1 bit to account for the r/w bit
     44  * on the wire.  We also want to look at only the least-
     45  * significant 8 bits of the address cell.
     46  */
     47 #define	OFW_I2C_ADDRESS_MASK	__BITS(0,7)
     48 #define	OFW_I2C_ADDRESS_SHIFT	1
     49 
     50 /*
     51  * Some of Apple's older OpenFirmware implementations are rife with
     52  * nodes lacking "compatible" properties.
     53  */
     54 #define	OFW_I2C_ALLOW_MISSING_COMPATIBLE_PROPERTY
     55 #endif /* __HAVE_OPENFIRMWARE_VARIANT_AAPL */
     56 
     57 #ifdef __HAVE_OPENFIRMWARE_VARIANT_SUNW
     58 /*
     59  * Sun OpenFirmware implementations use 2 cells for the
     60  * i2c device "reg" property, the first containing the
     61  * channel number, the second containing the i2c device
     62  * address shifted left 1 bit to account for the r/w bit
     63  * on the wire.
     64  */
     65 #define	OFW_I2C_REG_NCELLS	2
     66 #define	OFW_I2C_REG_CHANNEL	0
     67 #define	OFW_I2C_REG_ADDRESS	1
     68 #define	OFW_I2C_ADDRESS_SHIFT	1
     69 #endif /* __HAVE_OPENFIRMWARE_VARIANT_SUNW */
     70 
     71 #ifndef OFW_I2C_REG_NCELLS
     72 #define	OFW_I2C_REG_NCELLS	1
     73 #endif
     74 
     75 #ifndef OFW_I2C_REG_ADDRESS
     76 #define	OFW_I2C_REG_ADDRESS	0
     77 #endif
     78 
     79 /* No default for OFW_I2C_REG_CHANNEL. */
     80 
     81 #ifndef OFW_I2C_ADDRESS_MASK
     82 #define	OFW_I2C_ADDRESS_MASK	__BITS(0,31)
     83 #endif
     84 
     85 #ifndef OFW_I2C_ADDRESS_SHIFT
     86 #define	OFW_I2C_ADDRESS_SHIFT	0
     87 #endif
     88 
     89 static bool
     90 of_i2c_get_address(i2c_tag_t tag, int node, uint32_t *addrp)
     91 {
     92 	uint32_t reg[OFW_I2C_REG_NCELLS];
     93 	uint32_t addr;
     94 #ifdef OFW_I2C_REG_CHANNEL
     95 	uint32_t channel;
     96 #endif
     97 
     98 	if (OF_getprop(node, "reg", reg, sizeof(reg)) != sizeof(reg)) {
     99 		/*
    100 		 * "reg" property is malformed; reject the device.
    101 		 */
    102 		return false;
    103 	}
    104 
    105 	addr = be32toh(reg[OFW_I2C_REG_ADDRESS]);
    106 	addr = (addr & OFW_I2C_ADDRESS_MASK) >> OFW_I2C_ADDRESS_SHIFT;
    107 
    108 #ifdef OFW_I2C_REG_CHANNEL
    109 	/*
    110 	 * If the channel in the "reg" property does not match,
    111 	 * reject the device.
    112 	 */
    113 	channel = be32toh(reg[OFW_I2C_REG_CHANNEL]);
    114 	if (channel != tag->ic_channel) {
    115 		return false;
    116 	}
    117 #endif
    118 
    119 	*addrp = addr;
    120 	return true;
    121 }
    122 
    123 /*
    124  * This follows the Device Tree bindings for i2c, which for the most part
    125  * work for the classical OpenFirmware implementations, as well.  There are
    126  * some quirks do deal with for different OpenFirmware implementations, which
    127  * are mainly in how the "reg" property is interpreted.
    128  */
    129 static int
    130 of_i2c_enumerate_devices(device_t dev, devhandle_t call_handle, void *v)
    131 {
    132 	struct i2c_enumerate_devices_args *args = v;
    133 	int i2c_node, node;
    134 	char name[32], compat_buf[32];
    135 	uint32_t addr;
    136 	char *clist;
    137 	int clist_size;
    138 	bool cbrv;
    139 
    140 	i2c_node = devhandle_to_of(call_handle);
    141 
    142 	/*
    143 	 * The Device Tree bindings state that if a controller has a
    144 	 * child node named "i2c-bus", then that is the node beneath
    145 	 * which the child devices are populated.
    146 	 */
    147 	for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
    148 		if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
    149 			continue;
    150 		}
    151 		if (strcmp(name, "i2c-bus") == 0) {
    152 			i2c_node = node;
    153 			break;
    154 		}
    155 	}
    156 
    157 	for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
    158 		if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
    159 			continue;
    160 		}
    161 		if (!of_i2c_get_address(args->ia->ia_tag, node, &addr)) {
    162 			continue;
    163 		}
    164 
    165 		clist_size = OF_getproplen(node, "compatible");
    166 		if (clist_size <= 0) {
    167 #ifndef OFW_I2C_ALLOW_MISSING_COMPATIBLE_PROPERTY
    168 			continue;
    169 #else
    170 			clist_size = 0;
    171 #endif
    172 		}
    173 		clist = kmem_tmpbuf_alloc(clist_size,
    174 		    compat_buf, sizeof(compat_buf), KM_SLEEP);
    175 		if (OF_getprop(node, "compatible", clist, clist_size) <
    176 		    clist_size) {
    177 			kmem_tmpbuf_free(clist, clist_size, compat_buf);
    178 			continue;
    179 		}
    180 
    181 		args->ia->ia_addr = (i2c_addr_t)addr;
    182 		args->ia->ia_name = name;
    183 		args->ia->ia_clist = clist;
    184 		args->ia->ia_clist_size = clist_size;
    185 		args->ia->ia_devhandle = devhandle_from_of(node);
    186 
    187 		cbrv = args->callback(dev, args);
    188 
    189 		kmem_tmpbuf_free(clist, clist_size, compat_buf);
    190 
    191 		if (!cbrv) {
    192 			break;
    193 		}
    194 	}
    195 
    196 	return 0;
    197 }
    198 OF_DEVICE_CALL_REGISTER("i2c-enumerate-devices", of_i2c_enumerate_devices);
    199