/*- * 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, u_char *reply); 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; sc->sync_packet = 0xffff; /* 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,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,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),NULL); adb_send_raw_packet_sync(dev,next_free, ADB_COMMAND_TALK,3,0,NULL,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),NULL); adb_send_raw_packet_sync(dev,i, ADB_COMMAND_TALK,3,0,NULL,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) { 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); wakeup(sc); } 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 *reply) { u_char command_byte = 0; struct adb_softc *sc; int result = -1; int i = 1; 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 */ while (!atomic_cmpset_int(&sc->sync_packet, 0xffff, command_byte)) tsleep(sc, 0, "ADB sync", hz/10); 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 == 0) ADB_HB_SEND_RAW_PACKET(sc->parent, command_byte, len, data, 1); tsleep(sc, 0, "ADB sync", hz/10); i++; } result = sc->packet_reply - 1; if (reply != NULL && result > 0) memcpy(reply,sc->syncreg,result); /* Clear packet sync */ sc->packet_reply = 0; /* * We can't match a value beyond 8 bits, so set sync_packet to * 0xffff to avoid collisions. */ atomic_set_int(&sc->sync_packet, 0xffff); 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), NULL); adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_TALK, 3, 0, NULL, NULL); return (dinfo->handler_id); } size_t adb_read_register(device_t dev, u_char reg, void *data) { struct adb_softc *sc; struct adb_devinfo *dinfo; size_t result; dinfo = device_get_ivars(dev); sc = device_get_softc(device_get_parent(dev)); result = adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_TALK, reg, 0, NULL, data); return (result); } size_t adb_write_register(device_t dev, u_char reg, size_t len, void *data) { struct adb_softc *sc; struct adb_devinfo *dinfo; size_t result; dinfo = device_get_ivars(dev); sc = device_get_softc(device_get_parent(dev)); result = adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_LISTEN, reg, len, (u_char *)data, NULL); result = adb_send_raw_packet_sync(sc->sc_dev,dinfo->address, ADB_COMMAND_TALK, reg, 0, NULL, NULL); return (result); }