1 1.14 thorpej /* $NetBSD: oj6sh.c,v 1.14 2025/09/13 14:10:44 thorpej Exp $ */ 2 1.1 hkenken 3 1.1 hkenken /* 4 1.1 hkenken * Copyright (c) 2014 Genetec Corporation. All rights reserved. 5 1.1 hkenken * Written by Hashimoto Kenichi for Genetec Corporation. 6 1.1 hkenken * 7 1.1 hkenken * Redistribution and use in source and binary forms, with or without 8 1.1 hkenken * modification, are permitted provided that the following conditions 9 1.1 hkenken * are met: 10 1.1 hkenken * 1. Redistributions of source code must retain the above copyright 11 1.1 hkenken * notice, this list of conditions and the following disclaimer. 12 1.1 hkenken * 2. Redistributions in binary form must reproduce the above copyright 13 1.1 hkenken * notice, this list of conditions and the following disclaimer in the 14 1.1 hkenken * documentation and/or other materials provided with the distribution. 15 1.1 hkenken * 16 1.1 hkenken * THIS SOFTWARE IS PROVIDED BY GENETEC CORPORATION ``AS IS'' AND 17 1.1 hkenken * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 1.1 hkenken * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 1.1 hkenken * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GENETEC CORPORATION 20 1.1 hkenken * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 1.1 hkenken * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 1.1 hkenken * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 1.1 hkenken * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 1.1 hkenken * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 1.1 hkenken * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 1.1 hkenken * POSSIBILITY OF SUCH DAMAGE. 27 1.1 hkenken */ 28 1.1 hkenken 29 1.1 hkenken /* 30 1.1 hkenken * Sharp NetWalker's Optical Joystick 31 1.1 hkenken */ 32 1.1 hkenken 33 1.1 hkenken #include <sys/cdefs.h> 34 1.14 thorpej __KERNEL_RCSID(0, "$NetBSD: oj6sh.c,v 1.14 2025/09/13 14:10:44 thorpej Exp $"); 35 1.1 hkenken 36 1.1 hkenken #include "opt_oj6sh.h" 37 1.1 hkenken 38 1.1 hkenken #include <sys/param.h> 39 1.1 hkenken #include <sys/systm.h> 40 1.1 hkenken #include <sys/kernel.h> 41 1.1 hkenken #include <sys/device.h> 42 1.1 hkenken #include <sys/lock.h> 43 1.1 hkenken #include <sys/callout.h> 44 1.1 hkenken #include <sys/bus.h> 45 1.1 hkenken #include <sys/mutex.h> 46 1.2 hkenken #include <sys/workqueue.h> 47 1.1 hkenken 48 1.1 hkenken #include <dev/wscons/wsconsio.h> 49 1.1 hkenken #include <dev/wscons/wsmousevar.h> 50 1.1 hkenken #include <dev/wscons/wsdisplayvar.h> 51 1.1 hkenken 52 1.1 hkenken #include <dev/hpc/hpcfbio.h> 53 1.1 hkenken #include <dev/hpc/hpctpanelvar.h> 54 1.1 hkenken 55 1.1 hkenken #include <dev/spi/spivar.h> 56 1.1 hkenken 57 1.1 hkenken #ifdef OJ6SH_DEBUG 58 1.1 hkenken int oj6sh_debug = OJ6SH_DEBUG; 59 1.1 hkenken #define DPRINTF(n,x) if (oj6sh_debug>(n)) printf x; 60 1.1 hkenken #else 61 1.1 hkenken #define DPRINTF(n,x) 62 1.1 hkenken #endif 63 1.1 hkenken 64 1.1 hkenken #define POLLRATE (hz/30) 65 1.1 hkenken 66 1.1 hkenken /* register address */ 67 1.1 hkenken #define OJ6SH_PRODUCT 0x00 68 1.1 hkenken #define OJ6SH_REVISION 0x01 69 1.1 hkenken #define OJ6SH_MOTION 0x02 70 1.1 hkenken #define OJ6SH_DELTA_X 0x03 71 1.1 hkenken #define OJ6SH_DELTA_Y 0x04 72 1.1 hkenken #define OJ6SH_SQUAL 0x05 73 1.1 hkenken #define OJ6SH_SHUTTER 0x06 74 1.1 hkenken #define OJ6SH_CONFIG 0x11 75 1.1 hkenken #define OJ6SH_RESET 0x3a 76 1.1 hkenken #define POWERON_RESET 0x5a 77 1.1 hkenken #define OJ6SH_N_REVISION 0x3e 78 1.1 hkenken #define OJ6SH_N_PRODUCT 0x3f 79 1.1 hkenken 80 1.1 hkenken struct oj6sh_softc { 81 1.1 hkenken device_t sc_dev; 82 1.1 hkenken 83 1.14 thorpej spi_handle_t sc_sh; 84 1.1 hkenken struct callout sc_c; 85 1.1 hkenken 86 1.1 hkenken kmutex_t sc_lock; 87 1.2 hkenken struct workqueue *sc_wq; 88 1.2 hkenken struct work sc_wk; 89 1.2 hkenken 90 1.1 hkenken int sc_enabled; 91 1.1 hkenken 92 1.1 hkenken device_t sc_wsmousedev; 93 1.1 hkenken }; 94 1.1 hkenken 95 1.1 hkenken struct oj6sh_delta { 96 1.1 hkenken int x; 97 1.1 hkenken int y; 98 1.1 hkenken }; 99 1.1 hkenken 100 1.14 thorpej static uint8_t oj6sh_read(spi_handle_t, uint8_t); 101 1.14 thorpej static void oj6sh_write(spi_handle_t, uint8_t, uint8_t); 102 1.1 hkenken 103 1.14 thorpej static int oj6sh_match(device_t, cfdata_t, void *); 104 1.14 thorpej static void oj6sh_attach(device_t, device_t, void *); 105 1.1 hkenken 106 1.1 hkenken CFATTACH_DECL_NEW(oj6sh, sizeof(struct oj6sh_softc), 107 1.1 hkenken oj6sh_match, oj6sh_attach, NULL, NULL); 108 1.1 hkenken 109 1.14 thorpej static bool oj6sh_motion(spi_handle_t); 110 1.14 thorpej static bool oj6sh_squal(spi_handle_t); 111 1.14 thorpej static bool oj6sh_shuttrer(spi_handle_t); 112 1.14 thorpej static int oj6sh_readdelta(spi_handle_t, struct oj6sh_delta *); 113 1.1 hkenken 114 1.1 hkenken static void oj6sh_poll(void *); 115 1.2 hkenken static void oj6sh_cb(struct work *, void *); 116 1.2 hkenken static int oj6sh_enable(void *); 117 1.2 hkenken static void oj6sh_disable(void *); 118 1.1 hkenken static int oj6sh_ioctl(void *, u_long, void *, int, struct lwp *); 119 1.1 hkenken 120 1.1 hkenken static bool oj6sh_resume(device_t, const pmf_qual_t *); 121 1.1 hkenken static bool oj6sh_suspend(device_t, const pmf_qual_t *); 122 1.1 hkenken 123 1.1 hkenken static const struct wsmouse_accessops oj6sh_accessops = { 124 1.1 hkenken .enable = oj6sh_enable, 125 1.1 hkenken .ioctl = oj6sh_ioctl, 126 1.1 hkenken .disable = oj6sh_disable 127 1.1 hkenken }; 128 1.1 hkenken 129 1.5 martin static const struct device_compatible_entry compat_data[] = { 130 1.6 thorpej { .compat = "oj6sh" }, 131 1.7 thorpej DEVICE_COMPAT_EOL 132 1.5 martin }; 133 1.5 martin 134 1.1 hkenken static int 135 1.4 hkenken oj6sh_match(device_t parent, cfdata_t cf, void *aux) 136 1.1 hkenken { 137 1.1 hkenken struct spi_attach_args *sa = aux; 138 1.13 thorpej int match_result; 139 1.1 hkenken 140 1.13 thorpej if (spi_use_direct_match(sa, compat_data, &match_result)) { 141 1.13 thorpej return match_result; 142 1.13 thorpej } 143 1.1 hkenken 144 1.13 thorpej return SPI_MATCH_DEFAULT; 145 1.1 hkenken } 146 1.1 hkenken 147 1.1 hkenken static void 148 1.1 hkenken oj6sh_doattach(device_t self) 149 1.1 hkenken { 150 1.1 hkenken struct oj6sh_softc *sc = device_private(self); 151 1.1 hkenken uint8_t product; 152 1.1 hkenken uint8_t rev; 153 1.1 hkenken uint8_t product_inv; 154 1.1 hkenken uint8_t rev_inv; 155 1.1 hkenken 156 1.1 hkenken /* reset */ 157 1.1 hkenken oj6sh_write(sc->sc_sh, OJ6SH_RESET, POWERON_RESET); 158 1.1 hkenken delay(10000); 159 1.1 hkenken 160 1.1 hkenken /* resolution */ 161 1.1 hkenken oj6sh_write(sc->sc_sh, OJ6SH_CONFIG, 0x80); 162 1.1 hkenken 163 1.1 hkenken product = oj6sh_read(sc->sc_sh, OJ6SH_PRODUCT); 164 1.1 hkenken rev = oj6sh_read(sc->sc_sh, OJ6SH_REVISION); 165 1.1 hkenken product_inv = oj6sh_read(sc->sc_sh, OJ6SH_N_PRODUCT); 166 1.1 hkenken rev_inv = oj6sh_read(sc->sc_sh, OJ6SH_N_REVISION); 167 1.1 hkenken 168 1.1 hkenken if (((product | product_inv) != 0xff) || ((rev | rev_inv) != 0xff)) { 169 1.1 hkenken aprint_error_dev(self, 170 1.1 hkenken "mismatch product (%02x:%02x), rev (%02x:%02x)\n", 171 1.1 hkenken product, product_inv, rev, rev_inv); 172 1.1 hkenken return; 173 1.1 hkenken } 174 1.1 hkenken 175 1.1 hkenken aprint_normal("%s: id 0x%02x, revision 0x%02x\n", 176 1.1 hkenken device_xname(sc->sc_dev), product, rev); 177 1.1 hkenken 178 1.1 hkenken return; 179 1.1 hkenken } 180 1.1 hkenken 181 1.1 hkenken static void 182 1.1 hkenken oj6sh_attach(device_t parent, device_t self, void *aux) 183 1.1 hkenken { 184 1.1 hkenken struct oj6sh_softc *sc = device_private(self); 185 1.1 hkenken struct spi_attach_args *sa = aux; 186 1.1 hkenken struct wsmousedev_attach_args a; 187 1.10 thorpej int error; 188 1.1 hkenken 189 1.4 hkenken aprint_naive("\n"); 190 1.1 hkenken aprint_normal(": OJ6SH-T25 Optical Joystick\n"); 191 1.1 hkenken 192 1.12 thorpej error = spi_configure(self, sa->sa_handle, SPI_MODE_0, 193 1.13 thorpej SPI_FREQ_kHz(2500)); 194 1.10 thorpej if (error) { 195 1.10 thorpej return; 196 1.10 thorpej } 197 1.10 thorpej 198 1.1 hkenken mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); 199 1.1 hkenken 200 1.1 hkenken sc->sc_dev = self; 201 1.1 hkenken sc->sc_enabled = 0; 202 1.1 hkenken 203 1.1 hkenken callout_init(&sc->sc_c, 0); 204 1.2 hkenken workqueue_create(&sc->sc_wq, "oj6sh", 205 1.2 hkenken oj6sh_cb, sc, PRI_NONE, IPL_BIO, 0); 206 1.1 hkenken 207 1.1 hkenken sc->sc_sh = sa->sa_handle; 208 1.1 hkenken 209 1.1 hkenken a.accessops = &oj6sh_accessops; 210 1.1 hkenken a.accesscookie = sc; 211 1.1 hkenken 212 1.9 thorpej sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint, CFARGS_NONE); 213 1.1 hkenken 214 1.1 hkenken config_interrupts(self, oj6sh_doattach); 215 1.1 hkenken } 216 1.1 hkenken 217 1.1 hkenken static void 218 1.1 hkenken oj6sh_poll(void *arg) 219 1.1 hkenken { 220 1.1 hkenken struct oj6sh_softc *sc = (struct oj6sh_softc *)arg; 221 1.2 hkenken workqueue_enqueue(sc->sc_wq, &sc->sc_wk, NULL); 222 1.2 hkenken } 223 1.2 hkenken 224 1.2 hkenken static void 225 1.2 hkenken oj6sh_cb(struct work *wk, void *arg) 226 1.2 hkenken { 227 1.2 hkenken struct oj6sh_softc *sc = (struct oj6sh_softc *)arg; 228 1.1 hkenken struct oj6sh_delta delta = {0, 0}; 229 1.1 hkenken uint32_t buttons = 0; 230 1.1 hkenken int s; 231 1.1 hkenken int x, y; 232 1.1 hkenken 233 1.1 hkenken mutex_enter(&sc->sc_lock); 234 1.1 hkenken 235 1.1 hkenken if (oj6sh_motion(sc->sc_sh) == false) 236 1.1 hkenken goto out; 237 1.1 hkenken else if ((oj6sh_squal(sc->sc_sh) == true) && 238 1.1 hkenken (oj6sh_shuttrer(sc->sc_sh) == true)) 239 1.1 hkenken goto out; 240 1.1 hkenken 241 1.1 hkenken oj6sh_readdelta(sc->sc_sh, &delta); 242 1.1 hkenken DPRINTF(3,("%s: x = %d, y = %d\n", device_xname(sc->sc_dev), 243 1.1 hkenken delta.x, delta.y)); 244 1.1 hkenken 245 1.4 hkenken #if defined(OJ6SH_DOWN_Y_LEFT_X) 246 1.1 hkenken y = -delta.y; 247 1.1 hkenken x = -delta.x; 248 1.1 hkenken #elif defined(OJ6SH_UP_X_LEFT_Y) 249 1.1 hkenken y = delta.x; 250 1.1 hkenken x = -delta.y; 251 1.1 hkenken #elif defined(OJ6SH_DOWN_X_RIGHT_Y) 252 1.1 hkenken y = -delta.x; 253 1.1 hkenken x = delta.y; 254 1.1 hkenken #else /* OJ6SH_UP_Y_RIGHT_X */ 255 1.1 hkenken y = delta.y; 256 1.1 hkenken x = delta.x; 257 1.1 hkenken #endif 258 1.1 hkenken s = spltty(); 259 1.1 hkenken wsmouse_input(sc->sc_wsmousedev, buttons, x, y, 0, 0, 260 1.1 hkenken WSMOUSE_INPUT_DELTA); 261 1.1 hkenken splx(s); 262 1.1 hkenken out: 263 1.1 hkenken mutex_exit(&sc->sc_lock); 264 1.3 hkenken 265 1.3 hkenken if (sc->sc_enabled) 266 1.3 hkenken callout_reset(&sc->sc_c, POLLRATE, oj6sh_poll, sc); 267 1.1 hkenken } 268 1.1 hkenken 269 1.1 hkenken static uint8_t 270 1.14 thorpej oj6sh_read(spi_handle_t spi, uint8_t reg) 271 1.1 hkenken { 272 1.1 hkenken uint8_t ret = 0; 273 1.1 hkenken 274 1.1 hkenken spi_send_recv(spi, 1, ®, 1, &ret); 275 1.1 hkenken DPRINTF(4,("%s: 0x%02x = 0x%02x\n", __func__, reg, ret)); 276 1.1 hkenken return ret; 277 1.1 hkenken } 278 1.1 hkenken 279 1.1 hkenken static void 280 1.14 thorpej oj6sh_write(spi_handle_t spi, uint8_t reg, uint8_t val) 281 1.1 hkenken { 282 1.1 hkenken uint8_t tmp[2] = {reg | 0x80, val}; 283 1.1 hkenken 284 1.1 hkenken spi_send(spi, 2, tmp); 285 1.1 hkenken DPRINTF(4,("%s: 0x%02x = 0x%02x\n", __func__, reg, val)); 286 1.1 hkenken return; 287 1.1 hkenken } 288 1.1 hkenken 289 1.1 hkenken static bool 290 1.14 thorpej oj6sh_motion(spi_handle_t spi) 291 1.1 hkenken { 292 1.1 hkenken uint16_t motion; 293 1.1 hkenken motion = oj6sh_read(spi, OJ6SH_MOTION); 294 1.1 hkenken return (motion & __BIT(7) ? true : false); 295 1.1 hkenken } 296 1.1 hkenken 297 1.1 hkenken static bool 298 1.14 thorpej oj6sh_squal(spi_handle_t spi) 299 1.1 hkenken { 300 1.1 hkenken uint16_t squal; 301 1.1 hkenken squal = oj6sh_read(spi, OJ6SH_SQUAL); 302 1.1 hkenken return (squal < 25 ? true : false); 303 1.1 hkenken } 304 1.1 hkenken 305 1.1 hkenken static bool 306 1.14 thorpej oj6sh_shuttrer(spi_handle_t spi) 307 1.1 hkenken { 308 1.1 hkenken uint16_t shutter; 309 1.1 hkenken shutter = oj6sh_read(spi, OJ6SH_SHUTTER) << 8; 310 1.1 hkenken shutter |= oj6sh_read(spi, OJ6SH_SHUTTER + 1); 311 1.1 hkenken return (shutter > 600 ? true : false); 312 1.1 hkenken } 313 1.1 hkenken 314 1.1 hkenken static int 315 1.14 thorpej oj6sh_readdelta(spi_handle_t spi, struct oj6sh_delta *delta) 316 1.1 hkenken { 317 1.1 hkenken delta->x = (int8_t)oj6sh_read(spi, OJ6SH_DELTA_X); 318 1.1 hkenken delta->y = (int8_t)oj6sh_read(spi, OJ6SH_DELTA_Y); 319 1.1 hkenken return 0; 320 1.1 hkenken } 321 1.1 hkenken 322 1.1 hkenken int 323 1.1 hkenken oj6sh_ioctl(void *v, u_long cmd, void *data, int flag, struct lwp *l) 324 1.1 hkenken { 325 1.1 hkenken struct wsmouse_id *id; 326 1.1 hkenken 327 1.1 hkenken switch (cmd) { 328 1.1 hkenken case WSMOUSEIO_GTYPE: 329 1.1 hkenken *(u_int *)data = WSMOUSE_TYPE_PS2; 330 1.1 hkenken return 0; 331 1.1 hkenken case WSMOUSEIO_GETID: 332 1.1 hkenken id = (struct wsmouse_id *)data; 333 1.1 hkenken if (id->type != WSMOUSE_ID_TYPE_UIDSTR) 334 1.1 hkenken return EINVAL; 335 1.1 hkenken strlcpy(id->data, "OJ6SH-T25", WSMOUSE_ID_MAXLEN); 336 1.1 hkenken id->length = strlen(id->data); 337 1.1 hkenken return 0; 338 1.1 hkenken } 339 1.1 hkenken 340 1.1 hkenken return EPASSTHROUGH; 341 1.1 hkenken } 342 1.1 hkenken 343 1.1 hkenken int 344 1.1 hkenken oj6sh_enable(void *v) 345 1.1 hkenken { 346 1.1 hkenken struct oj6sh_softc *sc = (struct oj6sh_softc *)v; 347 1.1 hkenken 348 1.1 hkenken DPRINTF(3,("%s: oj6sh_enable()\n", device_xname(sc->sc_dev))); 349 1.1 hkenken if (sc->sc_enabled) { 350 1.1 hkenken DPRINTF(3,("%s: already enabled\n", device_xname(sc->sc_dev))); 351 1.1 hkenken return EBUSY; 352 1.1 hkenken } 353 1.1 hkenken 354 1.1 hkenken if (!pmf_device_register(sc->sc_dev, oj6sh_suspend, oj6sh_resume)) 355 1.1 hkenken aprint_error_dev(sc->sc_dev, "couldn't establish power handler\n"); 356 1.1 hkenken 357 1.1 hkenken sc->sc_enabled = 1; 358 1.1 hkenken callout_reset(&sc->sc_c, POLLRATE, oj6sh_poll, sc); 359 1.1 hkenken 360 1.1 hkenken return 0; 361 1.1 hkenken } 362 1.1 hkenken 363 1.1 hkenken void 364 1.1 hkenken oj6sh_disable(void *v) 365 1.1 hkenken { 366 1.1 hkenken struct oj6sh_softc *sc = (struct oj6sh_softc *)v; 367 1.1 hkenken 368 1.1 hkenken DPRINTF(3,("%s: oj6sh_disable()\n", device_xname(sc->sc_dev))); 369 1.1 hkenken if (!sc->sc_enabled) { 370 1.1 hkenken DPRINTF(3,("%s: already disabled()\n", device_xname(sc->sc_dev))); 371 1.1 hkenken return; 372 1.1 hkenken } 373 1.1 hkenken 374 1.1 hkenken pmf_device_deregister(sc->sc_dev); 375 1.1 hkenken 376 1.1 hkenken sc->sc_enabled = 0; 377 1.1 hkenken 378 1.1 hkenken return; 379 1.1 hkenken } 380 1.1 hkenken 381 1.1 hkenken static bool 382 1.1 hkenken oj6sh_suspend(device_t dv, const pmf_qual_t *qual) 383 1.1 hkenken { 384 1.1 hkenken struct oj6sh_softc *sc = device_private(dv); 385 1.1 hkenken 386 1.1 hkenken DPRINTF(3,("%s: oj6sh_suspend()\n", device_xname(sc->sc_dev))); 387 1.1 hkenken callout_stop(&sc->sc_c); 388 1.1 hkenken sc->sc_enabled = 0; 389 1.1 hkenken 390 1.1 hkenken return true; 391 1.1 hkenken } 392 1.1 hkenken 393 1.1 hkenken static bool 394 1.1 hkenken oj6sh_resume(device_t dv, const pmf_qual_t *qual) 395 1.1 hkenken { 396 1.1 hkenken struct oj6sh_softc *sc = device_private(dv); 397 1.1 hkenken 398 1.1 hkenken DPRINTF(3,("%s: oj6sh_resume()\n", device_xname(sc->sc_dev))); 399 1.1 hkenken sc->sc_enabled = 1; 400 1.1 hkenken callout_reset(&sc->sc_c, POLLRATE, oj6sh_poll, sc); 401 1.1 hkenken 402 1.1 hkenken return true; 403 1.1 hkenken } 404