/*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 1998 Nicolas Souchu, Marc Bouget * Copyright (c) 2004 Joerg Wunsch * 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 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 AUTHOR 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" /* Not so official debugging option. */ /* #define PCFDEBUG */ 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); /* * Polling mode for master operations wait for a new * byte incoming or outgoing */ 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 /* * Send STOP condition iff the START condition was previously sent. * STOP is sent only once even if an iicbus_stop() is called after * an iicbus_read()... see pcf_read(): the PCF needs to send the stop * before the last char is read. */ if (sc->pcf_started) { /* set stop condition and enable IT */ 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); /* XXX wait 10 us */ } 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 /* repeated start */ pcf_set_S1(sc, ESO|STA|STO|ACK); /* set slave address to PCF. Last bit (LSB) must be set correctly * according to transfer direction */ pcf_set_S0(sc, slave); /* wait for address sent, polling */ if ((error = pcf_wait_byte(sc))) goto error; /* check for ack */ 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); } /* set slave address to PCF. Last bit (LSB) must be set correctly * according to transfer direction */ pcf_set_S0(sc, slave); /* START only */ pcf_set_S1(sc, PIN|ESO|STA|ACK); sc->pcf_started = 1; /* wait for address sent, polling */ if ((error = pcf_wait_byte(sc))) goto error; /* check for ACK */ 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) { /* ack interrupt line */ dummy_write(sc); /* no ack, don't send anymore */ sc->pcf_slave_mode = SLAVE_RECEIVER; iicbus_intr(sc->iicbus, INTR_NOACK, NULL); break; } /* get data from upper code */ 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; /* get the first char from upper code */ iicbus_intr(sc->iicbus, INTR_TRANSMIT, &data); /* send first data byte */ pcf_set_S0(sc, data); } break; } /* stop condition received? */ if (status & STS) { /* ack interrupt line */ dummy_read(sc); /* emulate intr stop condition */ iicbus_intr(sc->iicbus, INTR_STOP, NULL); } else { /* get data, ack interrupt line */ data = pcf_get_S0(sc); /* deliver the character */ 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: /* unknown event on bus...reset PCF */ 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; /* retrieve own address from bus level */ if (!addr) sc->pcf_addr = PCF_DEFAULT_ADDR; else sc->pcf_addr = addr; pcf_set_S1(sc, PIN); /* initialize S1 */ /* own address S'O<>0 */ pcf_set_S0(sc, sc->pcf_addr >> 1); /* select clock register */ pcf_set_S1(sc, PIN|ES1); /* select bus speed : 18=90kb, 19=45kb, 1A=11kb, 1B=1.5kb */ switch (speed) { case IIC_SLOW: pcf_set_S0(sc, 0x1b); /* XXX Sun uses 0x1f */ break; case IIC_FAST: pcf_set_S0(sc, 0x19); /* XXX Sun: 0x1d */ break; case IIC_UNKNOWN: case IIC_FASTEST: default: pcf_set_S0(sc, 0x18); /* XXX Sun: 0x1c */ break; } /* set bus on, ack=yes, INT=yes */ 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 /* us */) { 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++); /* wait for the byte to be send */ if ((error = pcf_wait_byte(sc))) goto error; /* check if ack received */ 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 /* us */) { 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); /* trig the bus to get the first data byte in S0 */ if (len) { if (len == 1 && last) /* just one byte to read */ pcf_set_S1(sc, ESO); /* no ack */ dummy_read(sc); } bytes = 0; while (len) { /* XXX delay needed here */ /* wait for trigged byte */ if ((error = pcf_wait_byte(sc))) { pcf_stop_locked(sc); goto error; } if (len == 1 && last) /* ok, last data byte already in S0, no I2C activity * on next pcf_get_S0() */ pcf_stop_locked(sc); else if (len == 2 && last) /* next trigged byte with no ack */ pcf_set_S1(sc, ESO); /* receive byte, trig next byte */ *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);