Home | History | Annotate | Line # | Download | only in ofw
ofw_i2c_subr.c revision 1.1.6.9
      1 /*	$NetBSD: ofw_i2c_subr.c,v 1.1.6.9 2021/08/08 01:06:57 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.6.9 2021/08/08 01:06:57 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 	prop_dictionary_t props;
    136 	uint32_t addr;
    137 	char *clist;
    138 	int clist_size;
    139 	bool cbrv;
    140 
    141 	i2c_node = devhandle_to_of(call_handle);
    142 
    143 	for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
    144 		if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
    145 			continue;
    146 		}
    147 		if (!of_i2c_get_address(args->ia->ia_tag, node, &addr)) {
    148 			continue;
    149 		}
    150 
    151 		clist_size = OF_getproplen(node, "compatible");
    152 		if (clist_size <= 0) {
    153 #ifndef OFW_I2C_ALLOW_MISSING_COMPATIBLE_PROPERTY
    154 			continue;
    155 #else
    156 			clist_size = 0;
    157 #endif
    158 		}
    159 		clist = kmem_tmpbuf_alloc(clist_size,
    160 		    compat_buf, sizeof(compat_buf), KM_SLEEP);
    161 		if (OF_getprop(node, "compatible", clist, clist_size) <
    162 		    clist_size) {
    163 			kmem_tmpbuf_free(clist, clist_size, compat_buf);
    164 			continue;
    165 		}
    166 		props = prop_dictionary_create();
    167 
    168 		args->ia->ia_addr = (i2c_addr_t)addr;
    169 		args->ia->ia_name = name;
    170 		args->ia->ia_clist = clist;
    171 		args->ia->ia_clist_size = clist_size;
    172 		args->ia->ia_prop = props;
    173 		args->ia->ia_devhandle = devhandle_from_of(node);
    174 
    175 		cbrv = args->callback(dev, args);
    176 
    177 		prop_object_release(props);
    178 		kmem_tmpbuf_free(clist, clist_size, compat_buf);
    179 
    180 		if (!cbrv) {
    181 			break;
    182 		}
    183 	}
    184 
    185 	return 0;
    186 }
    187 OF_DEVICE_CALL_REGISTER("i2c-enumerate-devices", of_i2c_enumerate_devices);
    188