1 /* $OpenBSD: pca9548.c,v 1.3 2020/09/29 13:59:22 patrick 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 PCA9546_NUM_CHANNELS 4 32 #define PCA9548_NUM_CHANNELS 8 33 #define PCAMUX_MAX_CHANNELS 8 34 35 struct pcamux_bus { 36 struct pcamux_softc *pb_sc; 37 int pb_node; 38 int pb_channel; 39 struct i2c_controller pb_ic; 40 struct i2c_bus pb_ib; 41 }; 42 43 struct pcamux_softc { 44 struct device sc_dev; 45 i2c_tag_t sc_tag; 46 i2c_addr_t sc_addr; 47 48 int sc_node; 49 int sc_channel; 50 int sc_nchannel; 51 struct pcamux_bus sc_bus[PCAMUX_MAX_CHANNELS]; 52 struct rwlock sc_lock; 53 }; 54 55 int pcamux_match(struct device *, void *, void *); 56 void pcamux_attach(struct device *, struct device *, void *); 57 58 struct cfattach pcamux_ca = { 59 sizeof(struct pcamux_softc), pcamux_match, pcamux_attach 60 }; 61 62 struct cfdriver pcamux_cd = { 63 NULL, "pcamux", DV_DULL 64 }; 65 66 int pcamux_acquire_bus(void *, int); 67 void pcamux_release_bus(void *, int); 68 int pcamux_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, 69 void *, size_t, int); 70 void pcamux_bus_scan(struct device *, struct i2cbus_attach_args *, void *); 71 72 int 73 pcamux_match(struct device *parent, void *match, void *aux) 74 { 75 struct i2c_attach_args *ia = aux; 76 77 if (strcmp(ia->ia_name, "nxp,pca9546") == 0 || 78 strcmp(ia->ia_name, "nxp,pca9548") == 0) 79 return (1); 80 return (0); 81 } 82 83 void 84 pcamux_attach(struct device *parent, struct device *self, void *aux) 85 { 86 struct pcamux_softc *sc = (struct pcamux_softc *)self; 87 struct i2c_attach_args *ia = aux; 88 int node = *(int *)ia->ia_cookie; 89 90 sc->sc_tag = ia->ia_tag; 91 sc->sc_addr = ia->ia_addr; 92 93 sc->sc_node = node; 94 sc->sc_channel = -1; /* unknown */ 95 rw_init(&sc->sc_lock, sc->sc_dev.dv_xname); 96 97 if (strcmp(ia->ia_name, "nxp,pca9546") == 0) 98 sc->sc_nchannel = 4; 99 else if (strcmp(ia->ia_name, "nxp,pca9548") == 0) 100 sc->sc_nchannel = 8; 101 102 printf("\n"); 103 104 for (node = OF_child(node); node; node = OF_peer(node)) { 105 struct i2cbus_attach_args iba; 106 struct pcamux_bus *pb; 107 uint32_t channel; 108 109 channel = OF_getpropint(node, "reg", -1); 110 if (channel >= sc->sc_nchannel) 111 continue; 112 113 pb = &sc->sc_bus[channel]; 114 pb->pb_sc = sc; 115 pb->pb_node = node; 116 pb->pb_channel = channel; 117 pb->pb_ic.ic_cookie = pb; 118 pb->pb_ic.ic_acquire_bus = pcamux_acquire_bus; 119 pb->pb_ic.ic_release_bus = pcamux_release_bus; 120 pb->pb_ic.ic_exec = pcamux_exec; 121 122 /* Configure the child busses. */ 123 memset(&iba, 0, sizeof(iba)); 124 iba.iba_name = "iic"; 125 iba.iba_tag = &pb->pb_ic; 126 iba.iba_bus_scan = pcamux_bus_scan; 127 iba.iba_bus_scan_arg = &pb->pb_node; 128 129 config_found(&sc->sc_dev, &iba, iicbus_print); 130 131 pb->pb_ib.ib_node = node; 132 pb->pb_ib.ib_ic = &pb->pb_ic; 133 i2c_register(&pb->pb_ib); 134 } 135 } 136 137 int 138 pcamux_set_channel(struct pcamux_softc *sc, int channel, int flags) 139 { 140 uint8_t data; 141 int error; 142 143 if (channel < -1 || channel >= sc->sc_nchannel) 144 return ENXIO; 145 146 if (sc->sc_channel == channel) 147 return 0; 148 149 data = (channel == -1) ? 0 : (1 << channel); 150 error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, 151 sc->sc_addr, NULL, 0, &data, sizeof data, flags); 152 153 return error; 154 } 155 156 int 157 pcamux_acquire_bus(void *cookie, int flags) 158 { 159 struct pcamux_bus *pb = cookie; 160 struct pcamux_softc *sc = pb->pb_sc; 161 int rwflags = RW_WRITE; 162 int error; 163 164 if (flags & I2C_F_POLL) 165 rwflags |= RW_NOSLEEP; 166 167 error = rw_enter(&sc->sc_lock, rwflags); 168 if (error) 169 return error; 170 171 /* Acquire parent bus. */ 172 error = iic_acquire_bus(sc->sc_tag, flags); 173 if (error) { 174 rw_exit_write(&sc->sc_lock); 175 return error; 176 } 177 178 error = pcamux_set_channel(sc, pb->pb_channel, flags); 179 if (error) { 180 iic_release_bus(sc->sc_tag, flags); 181 rw_exit_write(&sc->sc_lock); 182 return error; 183 } 184 185 return 0; 186 } 187 188 void 189 pcamux_release_bus(void *cookie, int flags) 190 { 191 struct pcamux_bus *pb = cookie; 192 struct pcamux_softc *sc = pb->pb_sc; 193 194 /* Release parent bus. */ 195 iic_release_bus(sc->sc_tag, flags); 196 rw_exit_write(&sc->sc_lock); 197 } 198 199 int 200 pcamux_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd, 201 size_t cmdlen, void *buf, size_t buflen, int flags) 202 { 203 struct pcamux_bus *pb = cookie; 204 struct pcamux_softc *sc = pb->pb_sc; 205 206 rw_assert_wrlock(&sc->sc_lock); 207 208 /* Issue the transaction on the parent bus. */ 209 return iic_exec(sc->sc_tag, op, addr, cmd, cmdlen, buf, buflen, flags); 210 } 211 212 void 213 pcamux_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg) 214 { 215 int iba_node = *(int *)arg; 216 struct i2c_attach_args ia; 217 char name[32]; 218 uint32_t reg[1]; 219 int node; 220 221 for (node = OF_child(iba_node); node; node = OF_peer(node)) { 222 memset(name, 0, sizeof(name)); 223 memset(reg, 0, sizeof(reg)); 224 225 if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) 226 continue; 227 if (name[0] == '\0') 228 continue; 229 230 if (OF_getprop(node, "reg", ®, sizeof(reg)) != sizeof(reg)) 231 continue; 232 233 memset(&ia, 0, sizeof(ia)); 234 ia.ia_tag = iba->iba_tag; 235 ia.ia_addr = bemtoh32(®[0]); 236 ia.ia_name = name; 237 ia.ia_cookie = &node; 238 config_found(self, &ia, iic_print); 239 } 240 } 241