1 /* $OpenBSD: pca9548.c,v 1.1 2020/06/18 18:05:00 kettenis Exp $ */ 2 3 /* 4 * Copyright (c) 2020 Mark Kettenis 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/param.h> 20 #include <sys/systm.h> 21 #include <sys/device.h> 22 23 #include <machine/bus.h> 24 25 #define _I2C_PRIVATE 26 #include <dev/i2c/i2cvar.h> 27 28 #include <dev/ofw/openfirm.h> 29 #include <dev/ofw/ofw_misc.h> 30 31 #define PCA9548_NUM_CHANNELS 8 32 33 struct pcamux_bus { 34 struct pcamux_softc *pb_sc; 35 int pb_channel; 36 struct i2c_controller pb_ic; 37 struct i2c_bus pb_ib; 38 }; 39 40 struct pcamux_softc { 41 struct device sc_dev; 42 i2c_tag_t sc_tag; 43 i2c_addr_t sc_addr; 44 45 int sc_node; 46 int sc_channel; 47 struct pcamux_bus sc_bus[PCA9548_NUM_CHANNELS]; 48 struct rwlock sc_lock; 49 }; 50 51 int pcamux_match(struct device *, void *, void *); 52 void pcamux_attach(struct device *, struct device *, void *); 53 54 struct cfattach pcamux_ca = { 55 sizeof(struct pcamux_softc), pcamux_match, pcamux_attach 56 }; 57 58 struct cfdriver pcamux_cd = { 59 NULL, "pcamux", DV_DULL 60 }; 61 62 int pcamux_acquire_bus(void *, int); 63 void pcamux_release_bus(void *, int); 64 int pcamux_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, 65 void *, size_t, int); 66 void pcamux_bus_scan(struct device *, struct i2cbus_attach_args *, void *); 67 68 int 69 pcamux_match(struct device *parent, void *match, void *aux) 70 { 71 struct i2c_attach_args *ia = aux; 72 73 if (strcmp(ia->ia_name, "nxp,pca9548") == 0) 74 return (1); 75 return (0); 76 } 77 78 void 79 pcamux_attach(struct device *parent, struct device *self, void *aux) 80 { 81 struct pcamux_softc *sc = (struct pcamux_softc *)self; 82 struct i2c_attach_args *ia = aux; 83 int node = *(int *)ia->ia_cookie; 84 85 sc->sc_tag = ia->ia_tag; 86 sc->sc_addr = ia->ia_addr; 87 88 sc->sc_node = node; 89 sc->sc_channel = -1; /* unknown */ 90 rw_init(&sc->sc_lock, sc->sc_dev.dv_xname); 91 92 printf("\n"); 93 94 for (node = OF_child(node); node; node = OF_peer(node)) { 95 struct i2cbus_attach_args iba; 96 struct pcamux_bus *pb; 97 uint32_t channel; 98 99 channel = OF_getpropint(node, "reg", -1); 100 if (channel >= PCA9548_NUM_CHANNELS) 101 continue; 102 103 pb = &sc->sc_bus[channel]; 104 pb->pb_sc = sc; 105 pb->pb_channel = channel; 106 pb->pb_ic.ic_cookie = pb; 107 pb->pb_ic.ic_acquire_bus = pcamux_acquire_bus; 108 pb->pb_ic.ic_release_bus = pcamux_release_bus; 109 pb->pb_ic.ic_exec = pcamux_exec; 110 111 /* Configure the child busses. */ 112 memset(&iba, 0, sizeof(iba)); 113 iba.iba_name = "iic"; 114 iba.iba_tag = &pb->pb_ic; 115 iba.iba_bus_scan = pcamux_bus_scan; 116 iba.iba_bus_scan_arg = &sc->sc_node; 117 118 config_found(&sc->sc_dev, &iba, iicbus_print); 119 120 pb->pb_ib.ib_node = node; 121 pb->pb_ib.ib_ic = &pb->pb_ic; 122 i2c_register(&pb->pb_ib); 123 } 124 } 125 126 int 127 pcamux_set_channel(struct pcamux_softc *sc, int channel, int flags) 128 { 129 uint8_t data; 130 int error; 131 132 if (channel < -1 || channel >= PCA9548_NUM_CHANNELS) 133 return ENXIO; 134 135 if (sc->sc_channel == channel) 136 return 0; 137 138 data = (channel == -1) ? 0 : (1 << channel); 139 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, 140 sc->sc_addr, NULL, 0, &data, sizeof data, flags); 141 142 return error; 143 } 144 145 int 146 pcamux_acquire_bus(void *cookie, int flags) 147 { 148 struct pcamux_bus *pb = cookie; 149 struct pcamux_softc *sc = pb->pb_sc; 150 int rwflags = RW_WRITE; 151 int error; 152 153 if (flags & I2C_F_POLL) 154 rwflags |= RW_NOSLEEP; 155 156 error = rw_enter(&sc->sc_lock, rwflags); 157 if (error) 158 return error; 159 160 /* Acquire parent bus. */ 161 error = iic_acquire_bus(sc->sc_tag, flags); 162 if (error) { 163 rw_exit_write(&sc->sc_lock); 164 return error; 165 } 166 167 error = pcamux_set_channel(sc, pb->pb_channel, flags); 168 if (error) { 169 iic_release_bus(sc->sc_tag, flags); 170 rw_exit_write(&sc->sc_lock); 171 return error; 172 } 173 174 return 0; 175 } 176 177 void 178 pcamux_release_bus(void *cookie, int flags) 179 { 180 struct pcamux_bus *pb = cookie; 181 struct pcamux_softc *sc = pb->pb_sc; 182 183 /* Release parent bus. */ 184 iic_release_bus(sc->sc_tag, flags); 185 rw_exit_write(&sc->sc_lock); 186 } 187 188 int 189 pcamux_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd, 190 size_t cmdlen, void *buf, size_t buflen, int flags) 191 { 192 struct pcamux_bus *pb = cookie; 193 struct pcamux_softc *sc = pb->pb_sc; 194 195 rw_assert_wrlock(&sc->sc_lock); 196 197 /* Issue the transaction on the parent bus. */ 198 return iic_exec(sc->sc_tag, op, addr, cmd, cmdlen, buf, buflen, flags); 199 } 200 201 void 202 pcamux_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg) 203 { 204 int iba_node = *(int *)arg; 205 struct i2c_attach_args ia; 206 char name[32]; 207 uint32_t reg[1]; 208 int node; 209 210 for (node = OF_child(iba_node); node; node = OF_peer(node)) { 211 memset(name, 0, sizeof(name)); 212 memset(reg, 0, sizeof(reg)); 213 214 if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) 215 continue; 216 if (name[0] == '\0') 217 continue; 218 219 if (OF_getprop(node, "reg", ®, sizeof(reg)) != sizeof(reg)) 220 continue; 221 222 memset(&ia, 0, sizeof(ia)); 223 ia.ia_tag = iba->iba_tag; 224 ia.ia_addr = bemtoh32(®[0]); 225 ia.ia_name = name; 226 ia.ia_cookie = &node; 227 config_found(self, &ia, iic_print); 228 } 229 } 230