pcfiic_ebus.c revision 1.7.16.1 1 1.7.16.1 thorpej /* $NetBSD: pcfiic_ebus.c,v 1.7.16.1 2021/08/09 00:30:08 thorpej Exp $ */
2 1.1 martin /* $OpenBSD: pcfiic_ebus.c,v 1.13 2008/06/08 03:07:40 deraadt Exp $ */
3 1.1 martin
4 1.1 martin /*
5 1.7.16.1 thorpej * Copyright (c) 2021 The NetBSD Foundation, Inc.
6 1.7.16.1 thorpej * All rights reserved.
7 1.7.16.1 thorpej *
8 1.7.16.1 thorpej * This code is derived from software contributed to The NetBSD Foundation
9 1.7.16.1 thorpej * by Jason R. Thorpe.
10 1.7.16.1 thorpej *
11 1.7.16.1 thorpej * Redistribution and use in source and binary forms, with or without
12 1.7.16.1 thorpej * modification, are permitted provided that the following conditions
13 1.7.16.1 thorpej * are met:
14 1.7.16.1 thorpej * 1. Redistributions of source code must retain the above copyright
15 1.7.16.1 thorpej * notice, this list of conditions and the following disclaimer.
16 1.7.16.1 thorpej * 2. Redistributions in binary form must reproduce the above copyright
17 1.7.16.1 thorpej * notice, this list of conditions and the following disclaimer in the
18 1.7.16.1 thorpej * documentation and/or other materials provided with the distribution.
19 1.7.16.1 thorpej *
20 1.7.16.1 thorpej * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 1.7.16.1 thorpej * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 1.7.16.1 thorpej * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 1.7.16.1 thorpej * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 1.7.16.1 thorpej * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 1.7.16.1 thorpej * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 1.7.16.1 thorpej * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 1.7.16.1 thorpej * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 1.7.16.1 thorpej * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 1.7.16.1 thorpej * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 1.7.16.1 thorpej * POSSIBILITY OF SUCH DAMAGE.
31 1.7.16.1 thorpej */
32 1.7.16.1 thorpej
33 1.7.16.1 thorpej /*
34 1.1 martin * Copyright (c) 2006 David Gwynne <dlg (at) openbsd.org>
35 1.1 martin *
36 1.1 martin * Permission to use, copy, modify, and distribute this software for any
37 1.1 martin * purpose with or without fee is hereby granted, provided that the above
38 1.1 martin * copyright notice and this permission notice appear in all copies.
39 1.1 martin *
40 1.1 martin * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
41 1.1 martin * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
42 1.1 martin * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
43 1.1 martin * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
44 1.1 martin * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
45 1.1 martin * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
46 1.1 martin * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
47 1.1 martin */
48 1.1 martin
49 1.3 mrg #include <sys/cdefs.h>
50 1.7.16.1 thorpej __KERNEL_RCSID(0, "$NetBSD: pcfiic_ebus.c,v 1.7.16.1 2021/08/09 00:30:08 thorpej Exp $");
51 1.3 mrg
52 1.1 martin /*
53 1.1 martin * Device specific driver for the EBus i2c devices found on some sun4u
54 1.1 martin * systems. On systems not having a boot-bus controller the i2c devices
55 1.1 martin * are PCF8584.
56 1.1 martin */
57 1.1 martin
58 1.1 martin #include <sys/param.h>
59 1.1 martin #include <sys/systm.h>
60 1.1 martin #include <sys/device.h>
61 1.1 martin #include <sys/kernel.h>
62 1.7.16.1 thorpej #include <sys/kmem.h>
63 1.7.16.1 thorpej #include <sys/mutex.h>
64 1.1 martin
65 1.2 dyoung #include <sys/bus.h>
66 1.1 martin #include <machine/openfirm.h>
67 1.1 martin #include <machine/autoconf.h>
68 1.1 martin
69 1.1 martin #include <dev/ebus/ebusreg.h>
70 1.1 martin #include <dev/ebus/ebusvar.h>
71 1.1 martin
72 1.1 martin #include <dev/i2c/i2cvar.h>
73 1.1 martin
74 1.1 martin #include <dev/ic/pcf8584var.h>
75 1.5 jdc #include <dev/ic/pcf8584reg.h>
76 1.1 martin
77 1.1 martin struct pcfiic_ebus_softc {
78 1.1 martin struct pcfiic_softc esc_sc;
79 1.1 martin
80 1.7.16.1 thorpej kmutex_t esc_ctrl_lock;
81 1.7.16.1 thorpej bus_space_handle_t esc_ioh; /* for channel selection */
82 1.7.16.1 thorpej
83 1.1 martin void *esc_ih;
84 1.1 martin };
85 1.1 martin
86 1.7.16.1 thorpej static void
87 1.7.16.1 thorpej bbc_select_channel(struct pcfiic_ebus_softc *esc, uint8_t channel)
88 1.7.16.1 thorpej {
89 1.7.16.1 thorpej bus_space_write_1(esc->esc_sc.sc_iot, esc->esc_ioh, 0, channel);
90 1.7.16.1 thorpej bus_space_barrier(esc->esc_sc.sc_iot, esc->esc_ioh, 0, 1,
91 1.7.16.1 thorpej BUS_SPACE_BARRIER_WRITE);
92 1.7.16.1 thorpej }
93 1.7.16.1 thorpej
94 1.7.16.1 thorpej static int
95 1.7.16.1 thorpej bbc_acquire_bus(void *v, int flags)
96 1.7.16.1 thorpej {
97 1.7.16.1 thorpej struct pcfiic_channel *ch = v;
98 1.7.16.1 thorpej struct pcfiic_ebus_softc *esc = container_of(ch->ch_sc,
99 1.7.16.1 thorpej struct pcfiic_ebus_softc, esc_sc);
100 1.7.16.1 thorpej
101 1.7.16.1 thorpej if (flags & I2C_F_POLL) {
102 1.7.16.1 thorpej if (! mutex_tryenter(&esc->esc_ctrl_lock)) {
103 1.7.16.1 thorpej return EBUSY;
104 1.7.16.1 thorpej }
105 1.7.16.1 thorpej } else {
106 1.7.16.1 thorpej mutex_enter(&esc->esc_ctrl_lock);
107 1.7.16.1 thorpej }
108 1.7.16.1 thorpej
109 1.7.16.1 thorpej bbc_select_channel(esc, (uint8_t)ch->ch_channel);
110 1.7.16.1 thorpej return 0;
111 1.7.16.1 thorpej }
112 1.7.16.1 thorpej
113 1.7.16.1 thorpej static void
114 1.7.16.1 thorpej bbc_release_bus(void *v, int flags)
115 1.7.16.1 thorpej {
116 1.7.16.1 thorpej struct pcfiic_channel *ch = v;
117 1.7.16.1 thorpej struct pcfiic_ebus_softc *esc = container_of(ch->ch_sc,
118 1.7.16.1 thorpej struct pcfiic_ebus_softc, esc_sc);
119 1.1 martin
120 1.7.16.1 thorpej mutex_exit(&esc->esc_ctrl_lock);
121 1.7.16.1 thorpej }
122 1.7.16.1 thorpej
123 1.7.16.1 thorpej static void
124 1.7.16.1 thorpej bbc_initialize_channels(struct pcfiic_ebus_softc *esc)
125 1.7.16.1 thorpej {
126 1.7.16.1 thorpej struct pcfiic_softc *sc = &esc->esc_sc;
127 1.7.16.1 thorpej struct pcfiic_channel *ch;
128 1.7.16.1 thorpej devhandle_t devhandle = device_handle(sc->sc_dev);
129 1.7.16.1 thorpej unsigned int busmap = 0;
130 1.7.16.1 thorpej int node = devhandle_to_of(devhandle);
131 1.7.16.1 thorpej uint32_t reg[2];
132 1.7.16.1 thorpej uint32_t channel;
133 1.7.16.1 thorpej int i, nchannels;
134 1.7.16.1 thorpej
135 1.7.16.1 thorpej /*
136 1.7.16.1 thorpej * Two physical I2C busses share a single controller. The
137 1.7.16.1 thorpej * devices are not distinct, so it's not easy to treat it
138 1.7.16.1 thorpej * it as a mux.
139 1.7.16.1 thorpej *
140 1.7.16.1 thorpej * The locking order is:
141 1.7.16.1 thorpej *
142 1.7.16.1 thorpej * iic bus mutex -> ctrl_lock
143 1.7.16.1 thorpej *
144 1.7.16.1 thorpej * ctrl_lock is taken in bbc_acquire_bus.
145 1.7.16.1 thorpej */
146 1.7.16.1 thorpej mutex_init(&esc->esc_ctrl_lock, MUTEX_DEFAULT, IPL_NONE);
147 1.7.16.1 thorpej sc->sc_acquire_bus = bbc_acquire_bus;
148 1.7.16.1 thorpej sc->sc_release_bus = bbc_release_bus;
149 1.7.16.1 thorpej
150 1.7.16.1 thorpej /*
151 1.7.16.1 thorpej * The Sun device tree has all devices, no matter the
152 1.7.16.1 thorpej * channel, as direct children of this node. Figure
153 1.7.16.1 thorpej * out which channel numbers are listed, count them,
154 1.7.16.1 thorpej * and then populate the channel structures.
155 1.7.16.1 thorpej */
156 1.7.16.1 thorpej for (node = OF_child(node); node != 0; node = OF_peer(node)) {
157 1.7.16.1 thorpej if (OF_getprop(node, "reg", reg, sizeof(reg)) != sizeof(reg)) {
158 1.7.16.1 thorpej continue;
159 1.7.16.1 thorpej }
160 1.7.16.1 thorpej
161 1.7.16.1 thorpej /* Channel is in the first cell. */
162 1.7.16.1 thorpej channel = be32toh(reg[0]);
163 1.7.16.1 thorpej KASSERT(channel < 32);
164 1.7.16.1 thorpej
165 1.7.16.1 thorpej busmap |= __BIT(channel);
166 1.7.16.1 thorpej }
167 1.7.16.1 thorpej
168 1.7.16.1 thorpej nchannels = popcount(busmap);
169 1.7.16.1 thorpej if (nchannels == 0) {
170 1.7.16.1 thorpej /* No child devices. */
171 1.7.16.1 thorpej return;
172 1.7.16.1 thorpej }
173 1.7.16.1 thorpej
174 1.7.16.1 thorpej ch = kmem_alloc(nchannels * sizeof(*ch), KM_SLEEP);
175 1.7.16.1 thorpej for (i = 0; i < nchannels; i++) {
176 1.7.16.1 thorpej channel = ffs(busmap);
177 1.7.16.1 thorpej KASSERT(channel != 0);
178 1.7.16.1 thorpej channel--; /* ffs() returns 0 if no bits set. */
179 1.7.16.1 thorpej busmap &= ~__BIT(channel);
180 1.7.16.1 thorpej
181 1.7.16.1 thorpej ch[i].ch_channel = channel;
182 1.7.16.1 thorpej ch[i].ch_devhandle = devhandle;
183 1.7.16.1 thorpej }
184 1.7.16.1 thorpej
185 1.7.16.1 thorpej sc->sc_channels = ch;
186 1.7.16.1 thorpej sc->sc_nchannels = nchannels;
187 1.7.16.1 thorpej }
188 1.7.16.1 thorpej
189 1.7.16.1 thorpej static int
190 1.1 martin pcfiic_ebus_match(device_t parent, struct cfdata *match, void *aux)
191 1.1 martin {
192 1.1 martin struct ebus_attach_args *ea = aux;
193 1.1 martin char compat[32];
194 1.1 martin
195 1.1 martin if (strcmp(ea->ea_name, "SUNW,envctrl") == 0 ||
196 1.1 martin strcmp(ea->ea_name, "SUNW,envctrltwo") == 0)
197 1.1 martin return (1);
198 1.1 martin
199 1.1 martin if (strcmp(ea->ea_name, "i2c") != 0)
200 1.1 martin return (0);
201 1.1 martin
202 1.1 martin if (OF_getprop(ea->ea_node, "compatible", compat, sizeof(compat)) == -1)
203 1.1 martin return (0);
204 1.1 martin
205 1.1 martin if (strcmp(compat, "pcf8584") == 0 ||
206 1.1 martin strcmp(compat, "i2cpcf,8584") == 0 ||
207 1.1 martin strcmp(compat, "SUNW,i2c-pic16f747") == 0 ||
208 1.1 martin strcmp(compat, "SUNW,bbc-i2c") == 0)
209 1.1 martin return (1);
210 1.1 martin
211 1.1 martin return (0);
212 1.1 martin }
213 1.1 martin
214 1.7.16.1 thorpej static void
215 1.1 martin pcfiic_ebus_attach(device_t parent, device_t self, void *aux)
216 1.1 martin {
217 1.1 martin struct pcfiic_ebus_softc *esc = device_private(self);
218 1.1 martin struct pcfiic_softc *sc = &esc->esc_sc;
219 1.1 martin struct ebus_attach_args *ea = aux;
220 1.1 martin char compat[32];
221 1.7.16.1 thorpej uint32_t addr[2];
222 1.7.16.1 thorpej uint8_t clock = PCF8584_CLK_12 | PCF8584_SCL_90;
223 1.1 martin int swapregs = 0;
224 1.1 martin
225 1.1 martin if (ea->ea_nreg < 1 || ea->ea_nreg > 2) {
226 1.1 martin printf(": expected 1 or 2 registers, got %d\n", ea->ea_nreg);
227 1.1 martin return;
228 1.1 martin }
229 1.1 martin
230 1.7 jdc /* E450 and E250 have a different clock */
231 1.7 jdc if ((strcmp(ea->ea_name, "SUNW,envctrl") == 0) ||
232 1.7 jdc (strcmp(ea->ea_name, "SUNW,envctrltwo") == 0))
233 1.7 jdc clock = PCF8584_CLK_12 | PCF8584_SCL_45;
234 1.7 jdc
235 1.1 martin sc->sc_dev = self;
236 1.1 martin if (OF_getprop(ea->ea_node, "compatible", compat, sizeof(compat)) > 0 &&
237 1.1 martin strcmp(compat, "SUNW,bbc-i2c") == 0) {
238 1.1 martin /*
239 1.1 martin * On BBC-based machines, Sun swapped the order of
240 1.1 martin * the registers on their clone pcf, plus they feed
241 1.1 martin * it a non-standard clock.
242 1.1 martin */
243 1.1 martin int clk = prom_getpropint(findroot(), "clock-frequency", 0);
244 1.1 martin
245 1.1 martin if (clk < 105000000)
246 1.5 jdc clock = PCF8584_CLK_3 | PCF8584_SCL_90;
247 1.1 martin else if (clk < 160000000)
248 1.5 jdc clock = PCF8584_CLK_4_43 | PCF8584_SCL_90;
249 1.1 martin swapregs = 1;
250 1.1 martin }
251 1.1 martin
252 1.1 martin if (OF_getprop(ea->ea_node, "own-address", &addr, sizeof(addr)) == -1) {
253 1.7.16.1 thorpej addr[0] = 0;
254 1.7.16.1 thorpej addr[1] = 0x55 << 1;
255 1.7.16.1 thorpej } else if (addr[1] == 0x00 || addr[1] > 0xff) {
256 1.1 martin printf(": invalid address on I2C bus");
257 1.1 martin return;
258 1.1 martin }
259 1.1 martin
260 1.7.16.1 thorpej if (bus_space_map(ea->ea_bustag, EBUS_ADDR_FROM_REG(&ea->ea_reg[0]),
261 1.7.16.1 thorpej ea->ea_reg[0].size, 0, &sc->sc_ioh) == 0) {
262 1.1 martin sc->sc_iot = ea->ea_bustag;
263 1.1 martin } else {
264 1.1 martin printf(": can't map register space\n");
265 1.1 martin return;
266 1.1 martin }
267 1.1 martin
268 1.1 martin if (ea->ea_nreg == 2) {
269 1.1 martin /*
270 1.1 martin * Second register only occurs on BBC-based machines,
271 1.1 martin * and is likely not prom mapped
272 1.7.16.1 thorpej */
273 1.7.16.1 thorpej if (bus_space_map(sc->sc_iot,
274 1.7.16.1 thorpej EBUS_ADDR_FROM_REG(&ea->ea_reg[1]),
275 1.7.16.1 thorpej ea->ea_reg[1].size, 0, &esc->esc_ioh) != 0) {
276 1.1 martin printf(": can't map 2nd register space\n");
277 1.1 martin return;
278 1.1 martin }
279 1.7.16.1 thorpej bbc_initialize_channels(esc);
280 1.1 martin }
281 1.1 martin
282 1.1 martin if (ea->ea_nintr >= 1)
283 1.1 martin esc->esc_ih = bus_intr_establish(sc->sc_iot, ea->ea_intr[0],
284 1.1 martin IPL_BIO, pcfiic_intr, sc);
285 1.1 martin else
286 1.1 martin esc->esc_ih = NULL;
287 1.1 martin
288 1.1 martin if (esc->esc_ih == NULL)
289 1.1 martin sc->sc_poll = 1;
290 1.1 martin
291 1.7.16.1 thorpej pcfiic_attach(sc, (i2c_addr_t)(addr[1] >> 1), clock, swapregs);
292 1.1 martin }
293 1.7.16.1 thorpej
294 1.7.16.1 thorpej CFATTACH_DECL_NEW(pcfiic, sizeof(struct pcfiic_ebus_softc),
295 1.7.16.1 thorpej pcfiic_ebus_match, pcfiic_ebus_attach, NULL, NULL);
296