/*-
* Copyright (c) 2006 Michael Lorenz
* 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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``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 THE FOUNDATION OR CONTRIBUTORS
* 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.
*/
#define CUDA_NOTREADY 0x1 /* has not been initialized yet */
#define CUDA_IDLE 0x2 /* the bus is currently idle */
#define CUDA_OUT 0x3 /* sending out a command */
#define CUDA_IN 0x4 /* receiving data */
#define CUDA_POLLING 0x5 /* polling - II only */
sc->sc_state = CUDA_IDLE; /* used by all types of hardware */
cuda_write_reg(sc, vIER, 0x84); /* make sure VIA interrupts are on */
cuda_idle(sc); /* set ADB bus state to idle */
/* sort of a device reset */
(void)cuda_read_reg(sc, vSR); /* clear interrupt */
cuda_write_reg(sc, vIER, 0x04); /* no interrupts while clearing */
cuda_idle(sc); /* reset state to idle */
delay(150);
cuda_tip(sc); /* signal start of frame */
delay(150);
cuda_toggle_ack(sc);
delay(150);
cuda_clear_tip(sc);
delay(150);
cuda_idle(sc); /* back to idle state */
(void)cuda_read_reg(sc, vSR); /* clear interrupt */
cuda_write_reg(sc, vIER, 0x84); /* ints ok now */
}
static int
cuda_intr(void *arg)
{
struct cuda_softc *sc = arg;
int ending, type;
uint8_t reg;
reg = cuda_read_reg(sc, vIFR); /* Read the interrupts */
DPRINTF("[");
if ((reg & 0x80) == 0) {
DPRINTF("irq %02x]", reg);
return 0; /* No interrupts to process */
}
DPRINTF(":");
cuda_write_reg(sc, vIFR, 0x7f); /* Clear 'em */
switch_start:
switch (sc->sc_state) {
case CUDA_IDLE:
/*
* This is an unexpected packet, so grab the first (dummy)
* byte, set up the proper vars, and tell the chip we are
* starting to receive the packet by setting the TIP bit.
*/
sc->sc_in[1] = cuda_read_reg(sc, vSR);
DPRINTF("start: %02x", sc->sc_in[1]);
if (cuda_intr_state(sc) == 0) {
/* must have been a fake start */
DPRINTF(" ... fake start\n");
if (sc->sc_waiting) {
/* start over */
delay(150);
sc->sc_state = CUDA_OUT;
sc->sc_sent = 0;
cuda_out(sc);
cuda_write_reg(sc, vSR, sc->sc_out[1]);
cuda_ack_off(sc);
cuda_tip(sc);
}
break;
}
/*
* If the interrupt is on, we were too slow
* and the chip has already started to send
* something to us, so back out of the write
* and start a read cycle.
*/
if (cuda_intr_state(sc)) {
cuda_in(sc);
cuda_idle(sc);
sc->sc_sent = 0;
sc->sc_state = CUDA_IDLE;
sc->sc_received = 0;
delay(150);
DPRINTF("too slow - incoming message\n");
goto switch_start;
}
/*
* If we got here, it's ok to start sending
* so load the first byte and tell the chip
* we want to send.
*/
DPRINTF("sending ");
case CUDA_OUT:
(void)cuda_read_reg(sc, vSR); /* reset SR-intr in IFR */
sc->sc_sent++;
if (cuda_intr_state(sc)) { /* ADB intr low during write */
DPRINTF("incoming msg during send\n");
cuda_in(sc); /* make sure SR is set to IN */
cuda_idle(sc);
sc->sc_sent = 0; /* must start all over */
sc->sc_state = CUDA_IDLE; /* new state */
sc->sc_received = 0;
sc->sc_waiting = 1; /* must retry when done with
* read */
delay(150);
goto switch_start; /* process next state right
* now */
break;
}
if (sc->sc_out_length == sc->sc_sent) { /* check for done */
sc->sc_waiting = 0; /* done writing */
sc->sc_state = CUDA_IDLE; /* signal bus is idle */
cuda_in(sc);
cuda_idle(sc);
DPRINTF("done sending\n");
} else {
/* send next byte */
cuda_write_reg(sc, vSR, sc->sc_out[sc->sc_sent]);
DPRINTF("%02x", sc->sc_out[sc->sc_sent]);
cuda_toggle_ack(sc); /* signal byte ready to
* shift */
}
break;
case CUDA_NOTREADY:
DPRINTF("adb: not yet initialized\n");
break;
static int
cuda_adb_handler(void *cookie, int len, uint8_t *data)
{
struct cuda_softc *sc = cookie;
if (sc->sc_adb_handler != NULL) {
sc->sc_adb_handler(sc->sc_adb_cookie, len - 1,
&data[1]);
return 0;
}
return -1;
}
static int
cuda_adb_send(void *cookie, int poll, int command, int len, uint8_t *data)
{
struct cuda_softc *sc = cookie;
int i, s = 0;
uint8_t packet[16];
/* construct an ADB command packet and send it */
packet[0] = CUDA_ADB;
packet[1] = command;
for (i = 0; i < len; i++)
packet[i + 2] = data[i];
if (poll || cold) {
s = splhigh();
cuda_poll(sc);
}
cuda_send(sc, poll, len + 2, packet);
if (poll || cold) {
cuda_poll(sc);
splx(s);
}
return 0;
}
/* Copy command and output data bytes, if any, to buffer */
if (send_len > 0)
memcpy(&command[3], send, uimin((int)send_len, 12));
else if (I2C_OP_READ_P(op) && (recv_len == 0)) {
/*
* If no data bytes in either direction, it's a "quick"
* i2c operation. We don't know how to do a quick_read
* since that requires us to set the low bit of the
* address byte after it has been left-shifted.
*/
sc->sc_error = 0;
return -1;
}
/* see if we're supposed to do a read */
if (recv_len > 0) {
sc->sc_iic_done = 0;
command[2] |= 1;
command[3] = 0;
/*
* XXX we need to do something to limit the size of the answer
* - apparently the chip keeps sending until we tell it to stop
*/
sc->sc_i2c_read_len = recv_len;
DPRINTF("rcv_len: %d\n", recv_len);
cuda_send(sc, sc->sc_polling, 3, command);
while ((sc->sc_iic_done == 0) && (sc->sc_error == 0)) {
if (sc->sc_polling || cold) {
cuda_poll(sc);
} else {
mutex_enter(&sc->sc_todevmtx);
cv_timedwait(&sc->sc_todev, &sc->sc_todevmtx, hz);
mutex_exit(&sc->sc_todevmtx);
}
}
if (sc->sc_error) {
aprint_error_dev(sc->sc_dev,
"error trying to read from I2C\n");
sc->sc_error = 0;
return -1;
}
}
DPRINTF("received: %d\n", sc->sc_iic_done);
if ((sc->sc_iic_done > 3) && (recv_len > 0)) {
int rlen;
/* we got an answer */
rlen = uimin(sc->sc_iic_done - 3, recv_len);
memcpy(recv, &sc->sc_in[4], rlen);
#ifdef CUDA_DEBUG
{
int i;
printf("ret:");
for (i = 0; i < rlen; i++)
printf(" %02x", recv[i]);
printf("\n");
}
#endif
return rlen;
}
return 0;
}