/*- * Copyright (C) 2008 Nathan Whitehorn * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include "adb.h" #include "adbvar.h" static int adb_bus_probe(device_t dev); static int adb_bus_attach(device_t dev); static int adb_bus_detach(device_t dev); static void adb_bus_enumerate(void *xdev); static void adb_probe_nomatch(device_t dev, device_t child); static int adb_print_child(device_t dev, device_t child); static int adb_send_raw_packet_sync(device_t dev, uint8_t to, uint8_t command, uint8_t reg, int len, u_char *data); static char *adb_device_string[] = { "HOST", "dongle", "keyboard", "mouse", "tablet", "modem", "RESERVED", "misc" }; static device_method_t adb_bus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, adb_bus_probe), DEVMETHOD(device_attach, adb_bus_attach), DEVMETHOD(device_detach, adb_bus_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus Interface */ DEVMETHOD(bus_probe_nomatch, adb_probe_nomatch), DEVMETHOD(bus_print_child, adb_print_child), { 0, 0 }, }; driver_t adb_driver = { "adb", adb_bus_methods, sizeof(struct adb_softc), }; devclass_t adb_devclass; static int adb_bus_probe(device_t dev) { device_set_desc(dev, "Apple Desktop Bus"); return (0); } static int adb_bus_attach(device_t dev) { struct adb_softc *sc = device_get_softc(dev); sc->enum_hook.ich_func = adb_bus_enumerate; sc->enum_hook.ich_arg = dev; /* * We should wait until interrupts are enabled to try to probe * the bus. Enumerating the ADB involves receiving packets, * which works best with interrupts enabled. */ if (config_intrhook_establish(&sc->enum_hook) != 0) return (ENOMEM); return (0); } static void adb_bus_enumerate(void *xdev) { device_t dev = (device_t)xdev; struct adb_softc *sc = device_get_softc(dev); uint8_t i, next_free; uint16_t r3; sc->sc_dev = dev; sc->parent = device_get_parent(dev); sc->packet_reply = 0; sc->autopoll_mask = 0; mtx_init(&sc->sc_sync_mtx,"adbsyn",NULL,MTX_DEF | MTX_RECURSE); /* Initialize devinfo */ for (i = 0; i < 16; i++) { sc->devinfo[i].address = i; sc->devinfo[i].default_address = 0; } /* Reset ADB bus */ adb_send_raw_packet_sync(dev,0,ADB_COMMAND_BUS_RESET,0,0,NULL); DELAY(1500); /* Enumerate bus */ next_free = 8; for (i = 1; i < 7; i++) { int8_t first_relocated = -1; int reply = 0; do { reply = adb_send_raw_packet_sync(dev,i, ADB_COMMAND_TALK,3,0,NULL); if (reply) { /* If we got a response, relocate to next_free */ r3 = sc->devinfo[i].register3; r3 &= 0xf000; r3 |= ((uint16_t)(next_free) & 0x000f) << 8; r3 |= 0x00fe; adb_send_raw_packet_sync(dev,i, ADB_COMMAND_LISTEN,3, sizeof(uint16_t),(u_char *)(&r3)); adb_send_raw_packet_sync(dev,next_free, ADB_COMMAND_TALK,3,0,NULL); sc->devinfo[next_free].default_address = i; if (first_relocated < 0) first_relocated = next_free; next_free++; } else if (first_relocated > 0) { /* Collisions removed, relocate first device back */ r3 = sc->devinfo[i].register3; r3 &= 0xf000; r3 |= ((uint16_t)(i) & 0x000f) << 8; adb_send_raw_packet_sync(dev,first_relocated, ADB_COMMAND_LISTEN,3, sizeof(uint16_t),(u_char *)(&r3)); adb_send_raw_packet_sync(dev,i, ADB_COMMAND_TALK,3,0,NULL); sc->devinfo[i].default_address = i; sc->devinfo[(int)(first_relocated)].default_address = 0; break; } } while (reply); } for (i = 0; i < 16; i++) { if (sc->devinfo[i].default_address) { sc->children[i] = device_add_child(dev, NULL, -1); device_set_ivars(sc->children[i], &sc->devinfo[i]); } } bus_generic_attach(dev); config_intrhook_disestablish(&sc->enum_hook); } static int adb_bus_detach(device_t dev) { struct adb_softc *sc = device_get_softc(dev); mtx_destroy(&sc->sc_sync_mtx); return (bus_generic_detach(dev)); } static void adb_probe_nomatch(device_t dev, device_t child) { struct adb_devinfo *dinfo; if (bootverbose) { dinfo = device_get_ivars(child); device_printf(dev,"ADB %s at device %d (no driver attached)\n", adb_device_string[dinfo->default_address],dinfo->address); } } u_int adb_receive_raw_packet(device_t dev, u_char status, u_char command, int len, u_char *data) { struct adb_softc *sc = device_get_softc(dev); u_char addr = command >> 4; if (len > 0 && (command & 0x0f) == ((ADB_COMMAND_TALK << 2) | 3)) { memcpy(&sc->devinfo[addr].register3,data,2); sc->devinfo[addr].handler_id = data[1]; } if (sc->sync_packet == command) { memcpy(sc->syncreg,data,(len > 8) ? 8 : len); atomic_store_rel_int(&sc->packet_reply,len + 1); } if (sc->children[addr] != NULL) { ADB_RECEIVE_PACKET(sc->children[addr],status, (command & 0x0f) >> 2,command & 0x03,len,data); } return (0); } static int adb_print_child(device_t dev, device_t child) { struct adb_devinfo *dinfo; int retval = 0; dinfo = device_get_ivars(child); retval += bus_print_child_header(dev,child); printf(" at device %d",dinfo->address); retval += bus_print_child_footer(dev, child); return (retval); } u_int adb_send_packet(device_t dev, u_char command, u_char reg, int len, u_char *data) { u_char command_byte = 0; struct adb_devinfo *dinfo; struct adb_softc *sc; sc = device_get_softc(device_get_parent(dev)); dinfo = device_get_ivars(dev); command_byte |= dinfo->address << 4; command_byte |= command << 2; command_byte |= reg; ADB_HB_SEND_RAW_PACKET(sc->parent, command_byte, len, data, 1); return (0); } u_int adb_set_autopoll(device_t dev, u_char enable) { struct adb_devinfo *dinfo; struct adb_softc *sc; uint16_t mod = 0; sc = device_get_softc(device_get_parent(dev)); dinfo = device_get_ivars(dev); mod = enable << dinfo->address; if (enable) { sc->autopoll_mask |= mod; } else { mod = ~mod; sc->autopoll_mask &= mod; } ADB_HB_SET_AUTOPOLL_MASK(sc->parent,sc->autopoll_mask); return (0); } uint8_t adb_get_device_type(device_t dev) { struct adb_devinfo *dinfo; dinfo = device_get_ivars(dev); return (dinfo->default_address); } uint8_t adb_get_device_handler(device_t dev) { struct adb_devinfo *dinfo; dinfo = device_get_ivars(dev); return (dinfo->handler_id); } static int adb_send_raw_packet_sync(device_t dev, uint8_t to, uint8_t command, uint8_t reg, int len, u_char *data) { u_char command_byte = 0; struct adb_softc *sc; int result = -1; int i = 0; sc = device_get_softc(dev); command_byte |= to << 4; command_byte |= command << 2; command_byte |= reg; /* Wait if someone else has a synchronous request pending */ mtx_lock(&sc->sc_sync_mtx); sc->packet_reply = 0; sc->sync_packet = command_byte; ADB_HB_SEND_RAW_PACKET(sc->parent, command_byte, len, data, 1); while (!atomic_fetchadd_int(&sc->packet_reply,0)) { /* * Maybe the command got lost? Try resending and polling the * controller. */ if (i > 40) ADB_HB_SEND_RAW_PACKET(sc->parent, command_byte, len, data, 1); DELAY(100); i++; } result = sc->packet_reply - 1; /* Clear packet sync */ sc->packet_reply = 0; sc->sync_packet = 0xffff; /* We can't match a 16 bit value */ mtx_unlock(&sc->sc_sync_mtx); return (result); } uint8_t adb_set_device_handler(device_t dev, uint8_t newhandler) { struct adb_softc *sc; struct adb_devinfo *dinfo; uint16_t newr3; dinfo = device_get_ivars(dev); sc = device_get_softc(device_get_parent(dev)); newr3 = dinfo->register3 & 0xff00; newr3 |= (uint16_t)(newhandler); adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_LISTEN, 3, sizeof(uint16_t), (u_char *)(&newr3)); adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_TALK, 3, 0, NULL); return (dinfo->handler_id); } uint8_t adb_read_register(device_t dev, u_char reg, size_t *len, void *data) { struct adb_softc *sc; struct adb_devinfo *dinfo; size_t orig_len; dinfo = device_get_ivars(dev); sc = device_get_softc(device_get_parent(dev)); orig_len = *len; mtx_lock(&sc->sc_sync_mtx); *len = adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_TALK, reg, 0, NULL); if (*len > 0) memcpy(data,sc->syncreg,*len); mtx_unlock(&sc->sc_sync_mtx); return ((*len > 0) ? 0 : -1); }