#include <sys/param.h>
#include <sys/bus.h>
#include <sys/lock.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iiconf.h>
#include <dev/pcf/pcfvar.h>
#include "iicbus_if.h"
static int pcf_wait_byte(struct pcf_softc *pcf);
static int pcf_noack(struct pcf_softc *pcf, int timeout);
static void pcf_stop_locked(struct pcf_softc *pcf);
static int
pcf_wait_byte(struct pcf_softc *sc)
{
int counter = TIMEOUT;
PCF_ASSERT_LOCKED(sc);
while (counter--) {
if ((pcf_get_S1(sc) & PIN) == 0)
return (0);
}
#ifdef PCFDEBUG
printf("pcf: timeout!\n");
#endif
return (IIC_ETIMEOUT);
}
static void
pcf_stop_locked(struct pcf_softc *sc)
{
PCF_ASSERT_LOCKED(sc);
#ifdef PCFDEBUG
device_printf(dev, " >> stop\n");
#endif
if (sc->pcf_started) {
pcf_set_S1(sc, PIN|ESO|ENI|STO|ACK);
sc->pcf_started = 0;
}
}
static int
pcf_noack(struct pcf_softc *sc, int timeout)
{
int noack;
int k = timeout/10;
PCF_ASSERT_LOCKED(sc);
do {
noack = pcf_get_S1(sc) & LRB;
if (!noack)
break;
DELAY(10);
} while (k--);
return (noack);
}
int
pcf_repeated_start(device_t dev, u_char slave, int timeout)
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
int error = 0;
PCF_LOCK(sc);
#ifdef PCFDEBUG
device_printf(dev, " >> repeated start for slave %#x\n",
(unsigned)slave);
#endif
pcf_set_S1(sc, ESO|STA|STO|ACK);
pcf_set_S0(sc, slave);
if ((error = pcf_wait_byte(sc)))
goto error;
if (pcf_noack(sc, timeout)) {
error = IIC_ENOACK;
#ifdef PCFDEBUG
printf("pcf: no ack on repeated_start!\n");
#endif
goto error;
}
PCF_UNLOCK(sc);
return (0);
error:
pcf_stop_locked(sc);
PCF_UNLOCK(sc);
return (error);
}
int
pcf_start(device_t dev, u_char slave, int timeout)
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
int error = 0;
PCF_LOCK(sc);
#ifdef PCFDEBUG
device_printf(dev, " >> start for slave %#x\n", (unsigned)slave);
#endif
if ((pcf_get_S1(sc) & nBB) == 0) {
#ifdef PCFDEBUG
printf("pcf: busy!\n");
#endif
PCF_UNLOCK(sc);
return (IIC_EBUSERR);
}
pcf_set_S0(sc, slave);
pcf_set_S1(sc, PIN|ESO|STA|ACK);
sc->pcf_started = 1;
if ((error = pcf_wait_byte(sc)))
goto error;
if (pcf_noack(sc, timeout)) {
error = IIC_ENOACK;
#ifdef PCFDEBUG
printf("pcf: no ack on start!\n");
#endif
goto error;
}
PCF_UNLOCK(sc);
return (0);
error:
pcf_stop_locked(sc);
PCF_UNLOCK(sc);
return (error);
}
int
pcf_stop(device_t dev)
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
#ifdef PCFDEBUG
device_printf(dev, " >> stop\n");
#endif
PCF_LOCK(sc);
pcf_stop_locked(sc);
PCF_UNLOCK(sc);
return (0);
}
void
pcf_intr(void *arg)
{
struct pcf_softc *sc = arg;
char data, status, addr;
char error = 0;
PCF_LOCK(sc);
status = pcf_get_S1(sc);
if (status & PIN) {
printf("pcf: spurious interrupt, status=0x%x\n",
status & 0xff);
goto error;
}
if (status & LAB)
printf("pcf: bus arbitration lost!\n");
if (status & BER) {
error = IIC_EBUSERR;
iicbus_intr(sc->iicbus, INTR_ERROR, &error);
goto error;
}
do {
status = pcf_get_S1(sc);
switch(sc->pcf_slave_mode) {
case SLAVE_TRANSMITTER:
if (status & LRB) {
dummy_write(sc);
sc->pcf_slave_mode = SLAVE_RECEIVER;
iicbus_intr(sc->iicbus, INTR_NOACK, NULL);
break;
}
iicbus_intr(sc->iicbus, INTR_TRANSMIT, &data);
pcf_set_S0(sc, data);
break;
case SLAVE_RECEIVER:
if (status & AAS) {
addr = pcf_get_S0(sc);
if (status & AD0)
iicbus_intr(sc->iicbus, INTR_GENERAL, &addr);
else
iicbus_intr(sc->iicbus, INTR_START, &addr);
if (addr & LSB) {
sc->pcf_slave_mode = SLAVE_TRANSMITTER;
iicbus_intr(sc->iicbus, INTR_TRANSMIT, &data);
pcf_set_S0(sc, data);
}
break;
}
if (status & STS) {
dummy_read(sc);
iicbus_intr(sc->iicbus, INTR_STOP, NULL);
} else {
data = pcf_get_S0(sc);
iicbus_intr(sc->iicbus, INTR_RECEIVE, &data);
}
break;
default:
panic("%s: unknown slave mode (%d)!", __func__,
sc->pcf_slave_mode);
}
} while ((pcf_get_S1(sc) & PIN) == 0);
PCF_UNLOCK(sc);
return;
error:
pcf_set_S1(sc, PIN|ESO|ENI|ACK);
sc->pcf_slave_mode = SLAVE_RECEIVER;
PCF_UNLOCK(sc);
return;
}
int
pcf_rst_card(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
PCF_LOCK(sc);
if (oldaddr)
*oldaddr = sc->pcf_addr;
if (!addr)
sc->pcf_addr = PCF_DEFAULT_ADDR;
else
sc->pcf_addr = addr;
pcf_set_S1(sc, PIN);
pcf_set_S0(sc, sc->pcf_addr >> 1);
pcf_set_S1(sc, PIN|ES1);
switch (speed) {
case IIC_SLOW:
pcf_set_S0(sc, 0x1b);
break;
case IIC_FAST:
pcf_set_S0(sc, 0x19);
break;
case IIC_UNKNOWN:
case IIC_FASTEST:
default:
pcf_set_S0(sc, 0x18);
break;
}
pcf_set_S1(sc, PIN|ESO|ENI|ACK);
sc->pcf_slave_mode = SLAVE_RECEIVER;
PCF_UNLOCK(sc);
return (0);
}
int
pcf_write(device_t dev, const char *buf, int len, int *sent, int timeout )
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
int bytes, error = 0;
#ifdef PCFDEBUG
device_printf(dev, " >> writing %d bytes: %#x%s\n", len,
(unsigned)buf[0], len > 1? "...": "");
#endif
bytes = 0;
PCF_LOCK(sc);
while (len) {
pcf_set_S0(sc, *buf++);
if ((error = pcf_wait_byte(sc)))
goto error;
if (pcf_noack(sc, timeout)) {
error = IIC_ENOACK;
goto error;
}
len --;
bytes ++;
}
error:
*sent = bytes;
PCF_UNLOCK(sc);
#ifdef PCFDEBUG
device_printf(dev, " >> %d bytes written (%d)\n", bytes, error);
#endif
return (error);
}
int
pcf_read(device_t dev, char *buf, int len, int *read, int last,
int delay )
{
struct pcf_softc *sc = DEVTOSOFTC(dev);
int bytes, error = 0;
#ifdef PCFDEBUG
char *obuf = buf;
device_printf(dev, " << reading %d bytes\n", len);
#endif
PCF_LOCK(sc);
if (len) {
if (len == 1 && last)
pcf_set_S1(sc, ESO);
dummy_read(sc);
}
bytes = 0;
while (len) {
if ((error = pcf_wait_byte(sc))) {
pcf_stop_locked(sc);
goto error;
}
if (len == 1 && last)
pcf_stop_locked(sc);
else if (len == 2 && last)
pcf_set_S1(sc, ESO);
*buf++ = pcf_get_S0(sc);
len --;
bytes ++;
}
error:
*read = bytes;
PCF_UNLOCK(sc);
#ifdef PCFDEBUG
device_printf(dev, " << %d bytes read (%d): %#x%s\n", bytes, error,
(unsigned)obuf[0], bytes > 1? "...": "");
#endif
return (error);
}
DRIVER_MODULE(iicbus, pcf, iicbus_driver, 0, 0);
MODULE_DEPEND(pcf, iicbus, PCF_MINVER, PCF_PREFVER, PCF_MAXVER);
MODULE_VERSION(pcf, PCF_MODVER);