summaryrefslogtreecommitdiffstats
path: root/sys/dev/adb/adb_bus.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/adb/adb_bus.c')
-rw-r--r--sys/dev/adb/adb_bus.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/sys/dev/adb/adb_bus.c b/sys/dev/adb/adb_bus.c
new file mode 100644
index 0000000..cc31a9d
--- /dev/null
+++ b/sys/dev/adb/adb_bus.c
@@ -0,0 +1,384 @@
+/*-
+ * 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 <sys/cdefs.h>
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+
+#include <machine/bus.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#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_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);
+ 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]);
+ }
+ }
+
+ return (bus_generic_attach(dev));
+}
+
+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)) {
+ /* Sometimes CUDA controllers hang up during cold boots.
+ Try poking them. */
+ if (i > 10)
+ ADB_HB_CONTROLLER_POLL(sc->parent);
+
+ 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);
+}
+
OpenPOWER on IntegriCloud