ofw_i2c_machdep.c revision 1.1 1 /* $NetBSD: ofw_i2c_machdep.c,v 1.1 2025/09/21 17:58:56 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.1 2025/09/21 17:58:56 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", ®, 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", ®,
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 /*
86 * If the controller supports multiple channels and the controller
87 * node and the i2c bus node are the same, then the devices for
88 * multiple channels are all mixed together in the device tree.
89 * We need to filter them by channel in this case.
90 */
91 if (tag->ic_channel != I2C_CHANNEL_DEFAULT && i2c_node == ctlr_node &&
92 tag->ic_channel != channel) {
93 return false;
94 }
95
96 *addrp = addr;
97 return true;
98 }
99
100 static int
101 of_i2c_enumerate_devices(device_t dev, devhandle_t call_handle, void *v)
102 {
103 struct i2c_enumerate_devices_args *args = v;
104 int i2c_node, node;
105 char name[32], compat_buf[32];
106 uint32_t addr;
107 char *clist;
108 int clist_size;
109 bool cbrv;
110
111 i2c_node = devhandle_to_of(call_handle);
112
113 for (node = OF_child(i2c_node); node != 0; node = OF_peer(node)) {
114 if (OF_getprop(node, "name", name, sizeof(name)) <= 0) {
115 continue;
116 }
117 if (!of_i2c_get_address(dev, i2c_node, args->ia->ia_tag,
118 node, &addr)) {
119 continue;
120 }
121
122 /*
123 * Some of Apple's older OpenFirmware implementations are
124 * rife with nodes lacking "compatible" properties.
125 */
126 clist_size = OF_getproplen(node, "compatible");
127 if (clist_size <= 0) {
128 clist = name;
129 clist_size = 0;
130 } else {
131 clist = kmem_tmpbuf_alloc(clist_size,
132 compat_buf, sizeof(compat_buf), KM_SLEEP);
133 if (OF_getprop(node, "compatible", clist, clist_size)
134 < clist_size) {
135 kmem_tmpbuf_free(clist, clist_size, compat_buf);
136 continue;
137 }
138 }
139
140 cbrv = i2c_enumerate_device(dev, args, name,
141 clist, clist_size, addr,
142 devhandle_from_of(call_handle, node));
143
144 if (clist != name) {
145 kmem_tmpbuf_free(clist, clist_size, compat_buf);
146 }
147
148 if (!cbrv) {
149 break;
150 }
151 }
152
153 return 0;
154 }
155 OF_DEVICE_CALL_REGISTER(I2C_ENUMERATE_DEVICES_STR,
156 of_i2c_enumerate_devices);
157