f697a802bb
Submitted by: Andreas Tobler <andreast-list at fgznet dot ch> MFC after: 2 weeks
423 lines
10 KiB
C
423 lines
10 KiB
C
/*-
|
|
* 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_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);
|
|
}
|