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