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