freebsd-dev/sys/dev/pcf/pcf.c
Warner Losh 4d846d260e spdx: The BSD-2-Clause-FreeBSD identifier is obsolete, drop -FreeBSD
The SPDX folks have obsoleted the BSD-2-Clause-FreeBSD identifier. Catch
up to that fact and revert to their recommended match of BSD-2-Clause.

Discussed with:		pfg
MFC After:		3 days
Sponsored by:		Netflix
2023-05-12 10:44:03 -06:00

484 lines
9.8 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#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"
/* 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);