1 1.4 macallan /* $NetBSD: drivebay.c,v 1.4 2025/08/18 05:29:04 macallan Exp $ */ 2 1.1 macallan 3 1.1 macallan /*- 4 1.3 macallan * Copyright (c) 2025 Michael Lorenz 5 1.1 macallan * All rights reserved. 6 1.1 macallan * 7 1.1 macallan * Redistribution and use in source and binary forms, with or without 8 1.1 macallan * modification, are permitted provided that the following conditions 9 1.1 macallan * are met: 10 1.1 macallan * 1. Redistributions of source code must retain the above copyright 11 1.1 macallan * notice, this list of conditions and the following disclaimer. 12 1.1 macallan * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 macallan * notice, this list of conditions and the following disclaimer in the 14 1.1 macallan * documentation and/or other materials provided with the distribution. 15 1.1 macallan * 16 1.1 macallan * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 1.1 macallan * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 1.1 macallan * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 1.1 macallan * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 1.1 macallan * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 1.1 macallan * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 1.1 macallan * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 1.1 macallan * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 1.1 macallan * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 1.1 macallan * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 1.1 macallan * POSSIBILITY OF SUCH DAMAGE. 27 1.1 macallan */ 28 1.1 macallan 29 1.1 macallan /* 30 1.3 macallan * a driver for the Xserve G4's drivebays 31 1.1 macallan */ 32 1.1 macallan 33 1.1 macallan #include <sys/cdefs.h> 34 1.4 macallan __KERNEL_RCSID(0, "$NetBSD: drivebay.c,v 1.4 2025/08/18 05:29:04 macallan Exp $"); 35 1.1 macallan 36 1.1 macallan #include <sys/param.h> 37 1.1 macallan #include <sys/systm.h> 38 1.1 macallan #include <sys/device.h> 39 1.1 macallan #include <sys/conf.h> 40 1.1 macallan #include <sys/bus.h> 41 1.1 macallan #include <sys/sysctl.h> 42 1.1 macallan 43 1.1 macallan #include <dev/ofw/openfirm.h> 44 1.1 macallan #include <dev/i2c/i2cvar.h> 45 1.1 macallan 46 1.1 macallan #ifdef DRIVEBAY_DEBUG 47 1.1 macallan #define DPRINTF printf 48 1.1 macallan #else 49 1.1 macallan #define DPRINTF if (0) printf 50 1.1 macallan #endif 51 1.1 macallan 52 1.1 macallan /* commands */ 53 1.1 macallan #define PCAGPIO_INPUT 0x00 /* line status */ 54 1.1 macallan #define PCAGPIO_OUTPUT 0x01 /* output status */ 55 1.1 macallan #define PCAGPIO_REVERT 0x02 /* revert input if set */ 56 1.1 macallan #define PCAGPIO_CONFIG 0x03 /* input if set, output if not */ 57 1.1 macallan 58 1.1 macallan #define O_POWER 0x01 59 1.1 macallan #define O_FAIL 0x02 60 1.1 macallan #define O_INUSE 0x04 61 1.1 macallan #define I_PRESENT 0x08 62 1.1 macallan #define I_SWITCH 0x10 63 1.1 macallan #define I_RESET 0x20 64 1.1 macallan #define I_POWER 0x40 65 1.1 macallan #define I_INUSE 0x80 66 1.1 macallan 67 1.1 macallan static int drivebay_match(device_t, cfdata_t, void *); 68 1.1 macallan static void drivebay_attach(device_t, device_t, void *); 69 1.1 macallan static int drivebay_detach(device_t, int); 70 1.1 macallan 71 1.1 macallan struct drivebay_softc { 72 1.1 macallan device_t sc_dev; 73 1.1 macallan i2c_tag_t sc_i2c; 74 1.1 macallan i2c_addr_t sc_addr; 75 1.1 macallan 76 1.3 macallan uint32_t sc_state, sc_input, sc_last_update; 77 1.1 macallan 78 1.1 macallan #ifdef DRIVEBAY_DEBUG 79 1.1 macallan uint32_t sc_dir, sc_in; 80 1.1 macallan #endif 81 1.1 macallan }; 82 1.1 macallan 83 1.1 macallan 84 1.1 macallan static void drivebay_writereg(struct drivebay_softc *, int, uint32_t); 85 1.1 macallan static uint32_t drivebay_readreg(struct drivebay_softc *, int); 86 1.1 macallan 87 1.1 macallan static int sysctl_power(SYSCTLFN_ARGS); 88 1.1 macallan static int sysctl_fail(SYSCTLFN_ARGS); 89 1.1 macallan static int sysctl_inuse(SYSCTLFN_ARGS); 90 1.1 macallan static int sysctl_present(SYSCTLFN_ARGS); 91 1.2 macallan static int sysctl_switch(SYSCTLFN_ARGS); 92 1.1 macallan 93 1.1 macallan CFATTACH_DECL_NEW(drivebay, sizeof(struct drivebay_softc), 94 1.1 macallan drivebay_match, drivebay_attach, drivebay_detach, NULL); 95 1.1 macallan 96 1.1 macallan static const struct device_compatible_entry compat_data[] = { 97 1.1 macallan { .compat = "drivebay-i2c-gpio", .value = 0 }, 98 1.1 macallan DEVICE_COMPAT_EOL 99 1.1 macallan }; 100 1.1 macallan 101 1.1 macallan static int 102 1.1 macallan drivebay_match(device_t parent, cfdata_t match, void *aux) 103 1.1 macallan { 104 1.1 macallan struct i2c_attach_args *ia = aux; 105 1.1 macallan int match_result; 106 1.1 macallan 107 1.1 macallan if (iic_use_direct_match(ia, match, compat_data, &match_result)) 108 1.1 macallan return 2 * match_result; 109 1.1 macallan 110 1.1 macallan return 0; 111 1.1 macallan } 112 1.1 macallan 113 1.1 macallan #ifdef DRIVEBAY_DEBUG 114 1.1 macallan static void 115 1.1 macallan printdir(struct drivebay_softc *sc, uint32_t val, uint32_t mask, char letter) 116 1.1 macallan { 117 1.1 macallan char flags[17], bits[17]; 118 1.1 macallan uint32_t bit = 0x80; 119 1.1 macallan int i, cnt = 8; 120 1.1 macallan 121 1.1 macallan val &= mask; 122 1.1 macallan for (i = 0; i < cnt; i++) { 123 1.1 macallan flags[i] = (mask & bit) ? letter : '-'; 124 1.1 macallan bits[i] = (val & bit) ? 'X' : ' '; 125 1.1 macallan bit = bit >> 1; 126 1.1 macallan } 127 1.1 macallan flags[cnt] = 0; 128 1.1 macallan bits[cnt] = 0; 129 1.1 macallan printf("%s: dir: %s\n", device_xname(sc->sc_dev), flags); 130 1.1 macallan printf("%s: lvl: %s\n", device_xname(sc->sc_dev), bits); 131 1.1 macallan } 132 1.1 macallan #endif 133 1.1 macallan 134 1.1 macallan static void 135 1.1 macallan drivebay_attach(device_t parent, device_t self, void *aux) 136 1.1 macallan { 137 1.1 macallan struct drivebay_softc *sc = device_private(self); 138 1.1 macallan struct i2c_attach_args *ia = aux; 139 1.1 macallan const struct sysctlnode *me = NULL, *node = NULL; 140 1.1 macallan int ret; 141 1.1 macallan 142 1.1 macallan sc->sc_dev = self; 143 1.1 macallan sc->sc_i2c = ia->ia_tag; 144 1.1 macallan sc->sc_addr = ia->ia_addr; 145 1.3 macallan sc->sc_last_update = 0xffffffff; 146 1.1 macallan 147 1.1 macallan aprint_naive("\n"); 148 1.1 macallan 149 1.1 macallan aprint_normal(": PCA9554\n"); 150 1.1 macallan 151 1.1 macallan drivebay_writereg(sc, PCAGPIO_CONFIG, 0xf8); 152 1.1 macallan sc->sc_state = drivebay_readreg(sc, PCAGPIO_OUTPUT); 153 1.1 macallan 154 1.1 macallan ret = sysctl_createv(NULL, 0, NULL, &me, 155 1.1 macallan CTLFLAG_READWRITE, 156 1.1 macallan CTLTYPE_NODE, device_xname(sc->sc_dev), NULL, 157 1.1 macallan NULL, 0, NULL, 0, 158 1.4 macallan CTL_HW, CTL_CREATE, CTL_EOL); 159 1.1 macallan 160 1.1 macallan ret = sysctl_createv(NULL, 0, NULL, &node, 161 1.1 macallan CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 162 1.1 macallan CTLTYPE_INT, "power", "drive power", 163 1.1 macallan sysctl_power, 1, (void *)sc, 0, 164 1.4 macallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 165 1.1 macallan 166 1.1 macallan ret = sysctl_createv(NULL, 0, NULL, &node, 167 1.1 macallan CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 168 1.1 macallan CTLTYPE_INT, "fail", "drive fail LED", 169 1.1 macallan sysctl_fail, 1, (void *)sc, 0, 170 1.4 macallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 171 1.1 macallan 172 1.1 macallan ret = sysctl_createv(NULL, 0, NULL, &node, 173 1.1 macallan CTLFLAG_READWRITE | CTLFLAG_OWNDESC, 174 1.1 macallan CTLTYPE_INT, "inuse", "drive in use LED", 175 1.1 macallan sysctl_inuse, 1, (void *)sc, 0, 176 1.4 macallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 177 1.1 macallan 178 1.1 macallan ret = sysctl_createv(NULL, 0, NULL, &node, 179 1.2 macallan CTLFLAG_READONLY | CTLFLAG_OWNDESC, 180 1.1 macallan CTLTYPE_INT, "present", "drive present", 181 1.1 macallan sysctl_present, 1, (void *)sc, 0, 182 1.4 macallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 183 1.2 macallan 184 1.2 macallan ret = sysctl_createv(NULL, 0, NULL, &node, 185 1.2 macallan CTLFLAG_READONLY | CTLFLAG_OWNDESC, 186 1.2 macallan CTLTYPE_INT, "switch", "drive switch", 187 1.2 macallan sysctl_switch, 1, (void *)sc, 0, 188 1.4 macallan CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 189 1.1 macallan __USE(ret); 190 1.1 macallan 191 1.1 macallan #ifdef DRIVEBAY_DEBUG 192 1.1 macallan uint32_t in, out; 193 1.1 macallan sc->sc_dir = drivebay_readreg(sc, PCAGPIO_CONFIG); 194 1.1 macallan sc->sc_in = drivebay_readreg(sc, PCAGPIO_INPUT); 195 1.1 macallan in = sc->sc_in; 196 1.1 macallan out = sc->sc_state; 197 1.1 macallan 198 1.1 macallan out &= ~sc->sc_dir; 199 1.1 macallan in &= sc->sc_dir; 200 1.1 macallan 201 1.1 macallan printdir(sc, in, sc->sc_dir, 'I'); 202 1.1 macallan printdir(sc, out, ~sc->sc_dir, 'O'); 203 1.1 macallan 204 1.1 macallan #endif 205 1.1 macallan } 206 1.1 macallan 207 1.1 macallan static int 208 1.1 macallan drivebay_detach(device_t self, int flags) 209 1.1 macallan { 210 1.1 macallan return 0; 211 1.1 macallan } 212 1.1 macallan 213 1.1 macallan static void 214 1.1 macallan drivebay_writereg(struct drivebay_softc *sc, int reg, uint32_t val) 215 1.1 macallan { 216 1.1 macallan uint8_t cmd; 217 1.1 macallan uint8_t creg; 218 1.1 macallan 219 1.1 macallan iic_acquire_bus(sc->sc_i2c, 0); 220 1.1 macallan cmd = reg; 221 1.1 macallan creg = (uint8_t)val; 222 1.1 macallan iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 223 1.1 macallan sc->sc_addr, &cmd, 1, &creg, 1, 0); 224 1.1 macallan if (reg == PCAGPIO_OUTPUT) sc->sc_state = val; 225 1.1 macallan iic_release_bus(sc->sc_i2c, 0); 226 1.1 macallan } 227 1.1 macallan 228 1.1 macallan static uint32_t 229 1.1 macallan drivebay_readreg(struct drivebay_softc *sc, int reg) 230 1.1 macallan { 231 1.1 macallan uint8_t cmd; 232 1.1 macallan uint8_t creg; 233 1.1 macallan uint32_t ret; 234 1.1 macallan 235 1.3 macallan if (reg == PCAGPIO_INPUT) { 236 1.3 macallan if (time_uptime32 == sc->sc_last_update) 237 1.3 macallan return sc->sc_input; 238 1.3 macallan } 239 1.3 macallan 240 1.1 macallan iic_acquire_bus(sc->sc_i2c, 0); 241 1.1 macallan cmd = reg; 242 1.1 macallan iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 243 1.1 macallan sc->sc_addr, &cmd, 1, &creg, 1, 0); 244 1.1 macallan ret = creg; 245 1.1 macallan iic_release_bus(sc->sc_i2c, 0); 246 1.3 macallan if (reg == PCAGPIO_INPUT) { 247 1.3 macallan sc->sc_last_update = time_uptime32; 248 1.3 macallan sc->sc_input = ret; 249 1.3 macallan } 250 1.1 macallan return ret; 251 1.1 macallan } 252 1.1 macallan 253 1.1 macallan static int 254 1.1 macallan sysctl_power(SYSCTLFN_ARGS) 255 1.1 macallan { 256 1.1 macallan struct sysctlnode node = *rnode; 257 1.1 macallan struct drivebay_softc *sc = node.sysctl_data; 258 1.1 macallan int reg = drivebay_readreg(sc, PCAGPIO_INPUT); 259 1.1 macallan int power = (reg & I_POWER) != 0; 260 1.1 macallan 261 1.1 macallan if (newp) { 262 1.1 macallan /* we're asked to write */ 263 1.1 macallan node.sysctl_data = &power; 264 1.1 macallan if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 265 1.1 macallan 266 1.1 macallan power = *(int *)node.sysctl_data; 267 1.1 macallan reg = sc->sc_state; 268 1.1 macallan if (power != 0) reg |= O_POWER; 269 1.1 macallan else reg &= ~O_POWER; 270 1.1 macallan 271 1.1 macallan if (reg != sc->sc_state) { 272 1.1 macallan drivebay_writereg(sc, PCAGPIO_OUTPUT, reg); 273 1.1 macallan sc->sc_state = reg; 274 1.1 macallan } 275 1.1 macallan return 0; 276 1.1 macallan } 277 1.1 macallan return EINVAL; 278 1.1 macallan } else { 279 1.1 macallan 280 1.1 macallan node.sysctl_data = &power; 281 1.1 macallan node.sysctl_size = 4; 282 1.1 macallan return (sysctl_lookup(SYSCTLFN_CALL(&node))); 283 1.1 macallan } 284 1.1 macallan 285 1.1 macallan return 0; 286 1.1 macallan } 287 1.1 macallan 288 1.1 macallan static int 289 1.1 macallan sysctl_inuse(SYSCTLFN_ARGS) 290 1.1 macallan { 291 1.1 macallan struct sysctlnode node = *rnode; 292 1.1 macallan struct drivebay_softc *sc = node.sysctl_data; 293 1.1 macallan int reg = drivebay_readreg(sc, PCAGPIO_INPUT); 294 1.1 macallan int inuse = (reg & I_INUSE) != 0; 295 1.1 macallan 296 1.1 macallan if (newp) { 297 1.1 macallan /* we're asked to write */ 298 1.1 macallan node.sysctl_data = &inuse; 299 1.1 macallan if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 300 1.1 macallan 301 1.1 macallan inuse = *(int *)node.sysctl_data; 302 1.1 macallan reg = sc->sc_state; 303 1.1 macallan if (inuse != 0) reg |= O_INUSE; 304 1.1 macallan else reg &= ~O_INUSE; 305 1.1 macallan 306 1.1 macallan if (reg != sc->sc_state) { 307 1.1 macallan drivebay_writereg(sc, PCAGPIO_OUTPUT, reg); 308 1.1 macallan sc->sc_state = reg; 309 1.1 macallan } 310 1.1 macallan return 0; 311 1.1 macallan } 312 1.1 macallan return EINVAL; 313 1.1 macallan } else { 314 1.1 macallan 315 1.1 macallan node.sysctl_data = &inuse; 316 1.1 macallan node.sysctl_size = 4; 317 1.1 macallan return (sysctl_lookup(SYSCTLFN_CALL(&node))); 318 1.1 macallan } 319 1.1 macallan 320 1.1 macallan return 0; 321 1.1 macallan } 322 1.1 macallan 323 1.1 macallan static int 324 1.1 macallan sysctl_fail(SYSCTLFN_ARGS) 325 1.1 macallan { 326 1.1 macallan struct sysctlnode node = *rnode; 327 1.1 macallan struct drivebay_softc *sc = node.sysctl_data; 328 1.1 macallan int reg = sc->sc_state; 329 1.1 macallan int fail = (reg & O_FAIL) != 0; 330 1.1 macallan 331 1.1 macallan if (newp) { 332 1.1 macallan /* we're asked to write */ 333 1.1 macallan node.sysctl_data = &fail; 334 1.1 macallan if (sysctl_lookup(SYSCTLFN_CALL(&node)) == 0) { 335 1.1 macallan 336 1.1 macallan fail = *(int *)node.sysctl_data; 337 1.1 macallan reg = sc->sc_state; 338 1.1 macallan if (fail != 0) reg |= O_FAIL; 339 1.1 macallan else reg &= ~O_FAIL; 340 1.1 macallan 341 1.1 macallan if (reg != sc->sc_state) { 342 1.1 macallan drivebay_writereg(sc, PCAGPIO_OUTPUT, reg); 343 1.1 macallan sc->sc_state = reg; 344 1.1 macallan } 345 1.1 macallan return 0; 346 1.1 macallan } 347 1.1 macallan return EINVAL; 348 1.1 macallan } else { 349 1.1 macallan 350 1.1 macallan node.sysctl_data = &fail; 351 1.1 macallan node.sysctl_size = 4; 352 1.1 macallan return (sysctl_lookup(SYSCTLFN_CALL(&node))); 353 1.1 macallan } 354 1.1 macallan 355 1.1 macallan return 0; 356 1.1 macallan } 357 1.1 macallan 358 1.1 macallan static int 359 1.1 macallan sysctl_present(SYSCTLFN_ARGS) 360 1.1 macallan { 361 1.1 macallan struct sysctlnode node = *rnode; 362 1.1 macallan struct drivebay_softc *sc = node.sysctl_data; 363 1.1 macallan int reg = drivebay_readreg(sc, PCAGPIO_INPUT); 364 1.1 macallan int present = (reg & I_PRESENT) == 0; 365 1.1 macallan 366 1.1 macallan if (newp) { 367 1.1 macallan /* we're asked to write */ 368 1.1 macallan return EINVAL; 369 1.1 macallan } else { 370 1.1 macallan 371 1.1 macallan node.sysctl_data = &present; 372 1.1 macallan node.sysctl_size = 4; 373 1.1 macallan return (sysctl_lookup(SYSCTLFN_CALL(&node))); 374 1.1 macallan } 375 1.1 macallan 376 1.1 macallan return 0; 377 1.1 macallan } 378 1.2 macallan 379 1.2 macallan static int 380 1.2 macallan sysctl_switch(SYSCTLFN_ARGS) 381 1.2 macallan { 382 1.2 macallan struct sysctlnode node = *rnode; 383 1.2 macallan struct drivebay_softc *sc = node.sysctl_data; 384 1.2 macallan int reg = drivebay_readreg(sc, PCAGPIO_INPUT); 385 1.2 macallan int sw = (reg & I_SWITCH) == 0; 386 1.2 macallan 387 1.2 macallan if (newp) { 388 1.2 macallan /* we're asked to write */ 389 1.2 macallan return EINVAL; 390 1.2 macallan } else { 391 1.2 macallan 392 1.2 macallan node.sysctl_data = &sw; 393 1.2 macallan node.sysctl_size = 4; 394 1.2 macallan return (sysctl_lookup(SYSCTLFN_CALL(&node))); 395 1.2 macallan } 396 1.2 macallan 397 1.2 macallan return 0; 398 1.2 macallan } 399