summaryrefslogtreecommitdiffstats
path: root/sys/dev/pcf/pcf.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pcf/pcf.c')
-rw-r--r--sys/dev/pcf/pcf.c635
1 files changed, 635 insertions, 0 deletions
diff --git a/sys/dev/pcf/pcf.c b/sys/dev/pcf/pcf.c
new file mode 100644
index 0000000..5799f4c
--- /dev/null
+++ b/sys/dev/pcf/pcf.c
@@ -0,0 +1,635 @@
+/*-
+ * Copyright (c) 1998 Nicolas Souchu, Marc Bouget
+ * 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/systm.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/bus.h>
+
+#include <machine/bus.h>
+#include <machine/resource.h>
+#include <sys/rman.h>
+
+#include <isa/isareg.h>
+#include <isa/isavar.h>
+
+#include <i386/isa/isa_device.h>
+
+#include <dev/iicbus/iiconf.h>
+#include "iicbus_if.h"
+
+#define IO_PCFSIZE 2
+
+#define TIMEOUT 9999 /* XXX */
+
+/* Status bits of S1 register (read only) */
+#define nBB 0x01 /* busy when low set/reset by STOP/START*/
+#define LAB 0x02 /* lost arbitration bit in multi-master mode */
+#define AAS 0x04 /* addressed as slave */
+#define LRB 0x08 /* last received byte when not AAS */
+#define AD0 0x08 /* general call received when AAS */
+#define BER 0x10 /* bus error, misplaced START or STOP */
+#define STS 0x20 /* STOP detected in slave receiver mode */
+#define PIN 0x80 /* pending interrupt not (r/w) */
+
+/* Control bits of S1 register (write only) */
+#define ACK 0x01
+#define STO 0x02
+#define STA 0x04
+#define ENI 0x08
+#define ES2 0x10
+#define ES1 0x20
+#define ES0 0x40
+
+#define BUFSIZE 2048
+
+#define SLAVE_TRANSMITTER 0x1
+#define SLAVE_RECEIVER 0x2
+
+#define PCF_DEFAULT_ADDR 0xaa
+
+struct pcf_softc {
+
+ int pcf_base; /* isa port */
+ int pcf_flags;
+ u_char pcf_addr; /* interface I2C address */
+
+ int pcf_slave_mode; /* receiver or transmitter */
+ int pcf_started; /* 1 if start condition sent */
+
+ device_t iicbus; /* the corresponding iicbus */
+
+ int rid_irq, rid_ioport;
+ struct resource *res_irq, *res_ioport;
+ void *intr_cookie;
+};
+
+static int pcf_probe(device_t);
+static int pcf_attach(device_t);
+static void pcfintr(void *arg);
+
+static int pcf_print_child(device_t, device_t);
+
+static int pcf_repeated_start(device_t, u_char, int);
+static int pcf_start(device_t, u_char, int);
+static int pcf_stop(device_t);
+static int pcf_write(device_t, char *, int, int *, int);
+static int pcf_read(device_t, char *, int, int *, int, int);
+static int pcf_rst_card(device_t, u_char, u_char, u_char *);
+
+static device_method_t pcf_methods[] = {
+ /* device interface */
+ DEVMETHOD(device_probe, pcf_probe),
+ DEVMETHOD(device_attach, pcf_attach),
+
+ /* bus interface */
+ DEVMETHOD(bus_print_child, pcf_print_child),
+
+ /* iicbus interface */
+ DEVMETHOD(iicbus_callback, iicbus_null_callback),
+ DEVMETHOD(iicbus_repeated_start, pcf_repeated_start),
+ DEVMETHOD(iicbus_start, pcf_start),
+ DEVMETHOD(iicbus_stop, pcf_stop),
+ DEVMETHOD(iicbus_write, pcf_write),
+ DEVMETHOD(iicbus_read, pcf_read),
+ DEVMETHOD(iicbus_reset, pcf_rst_card),
+
+ { 0, 0 }
+};
+
+static driver_t pcf_driver = {
+ "pcf",
+ pcf_methods,
+ sizeof(struct pcf_softc),
+};
+
+static devclass_t pcf_devclass;
+
+#define DEVTOSOFTC(dev) ((struct pcf_softc *)device_get_softc(dev))
+
+static int
+pcf_probe(device_t pcfdev)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ device_t parent = device_get_parent(pcfdev);
+ uintptr_t base;
+
+ device_set_desc(pcfdev, "PCF8584 I2C bus controller");
+
+ pcf = DEVTOSOFTC(pcfdev);
+ bzero(pcf, sizeof(struct pcf_softc));
+
+ pcf->rid_irq = pcf->rid_ioport = 0;
+ pcf->res_irq = pcf->res_ioport = 0;
+
+ /* IO port is mandatory */
+ pcf->res_ioport = bus_alloc_resource(pcfdev, SYS_RES_IOPORT,
+ &pcf->rid_ioport, 0ul, ~0ul,
+ IO_PCFSIZE, RF_ACTIVE);
+ if (pcf->res_ioport == 0) {
+ device_printf(pcfdev, "cannot reserve I/O port range\n");
+ goto error;
+ }
+ BUS_READ_IVAR(parent, pcfdev, ISA_IVAR_PORT, &base);
+ pcf->pcf_base = base;
+
+ pcf->pcf_flags = device_get_flags(pcfdev);
+
+ if (!(pcf->pcf_flags & IIC_POLLED)) {
+ pcf->res_irq = bus_alloc_resource(pcfdev, SYS_RES_IRQ, &pcf->rid_irq,
+ 0ul, ~0ul, 1, RF_ACTIVE);
+ if (pcf->res_irq == 0) {
+ device_printf(pcfdev, "can't reserve irq, polled mode.\n");
+ pcf->pcf_flags |= IIC_POLLED;
+ }
+ }
+
+ /* reset the chip */
+ pcf_rst_card(pcfdev, IIC_FASTEST, PCF_DEFAULT_ADDR, NULL);
+
+ return (0);
+error:
+ if (pcf->res_ioport != 0) {
+ bus_deactivate_resource(pcfdev, SYS_RES_IOPORT, pcf->rid_ioport,
+ pcf->res_ioport);
+ bus_release_resource(pcfdev, SYS_RES_IOPORT, pcf->rid_ioport,
+ pcf->res_ioport);
+ }
+ return (ENXIO);
+}
+
+static int
+pcf_attach(device_t pcfdev)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ device_t parent = device_get_parent(pcfdev);
+ int error = 0;
+
+ if (pcf->res_irq) {
+ /* default to the tty mask for registration */ /* XXX */
+ error = BUS_SETUP_INTR(parent, pcfdev, pcf->res_irq, INTR_TYPE_NET,
+ pcfintr, pcfdev, &pcf->intr_cookie);
+ if (error)
+ return (error);
+ }
+
+ pcf->iicbus = device_add_child(pcfdev, "iicbus", -1);
+
+ /* probe and attach the iicbus */
+ bus_generic_attach(pcfdev);
+
+ return (0);
+}
+
+static int
+pcf_print_child(device_t bus, device_t dev)
+{
+ struct pcf_softc *pcf = (struct pcf_softc *)device_get_softc(bus);
+ int retval = 0;
+
+ retval += bus_print_child_header(bus, dev);
+ retval += printf(" on %s addr 0x%x\n", device_get_nameunit(bus),
+ (int)pcf->pcf_addr);
+
+ return (retval);
+}
+
+/*
+ * PCF8584 datasheet : when operate at 8 MHz or more, a minimun time of
+ * 6 clocks cycles must be left between two consecutives access
+ */
+#define pcf_nops() DELAY(10)
+
+#define dummy_read(pcf) PCF_GET_S0(pcf)
+#define dummy_write(pcf) PCF_SET_S0(pcf, 0)
+
+/*
+ * Specific register access to PCF8584
+ */
+static void PCF_SET_S0(struct pcf_softc *pcf, int data)
+{
+ outb(pcf->pcf_base, data);
+ pcf_nops();
+}
+
+static void PCF_SET_S1(struct pcf_softc *pcf, int data)
+{
+ outb(pcf->pcf_base+1, data);
+ pcf_nops();
+}
+
+static char PCF_GET_S0(struct pcf_softc *pcf)
+{
+ char data;
+
+ data = inb(pcf->pcf_base);
+ pcf_nops();
+
+ return (data);
+}
+
+static char PCF_GET_S1(struct pcf_softc *pcf)
+{
+ char data;
+
+ data = inb(pcf->pcf_base+1);
+ pcf_nops();
+
+ return (data);
+}
+
+/*
+ * Polling mode for master operations wait for a new
+ * byte incomming or outgoing
+ */
+static int pcf_wait_byte(struct pcf_softc *pcf)
+{
+ int counter = TIMEOUT;
+
+ while (counter--) {
+
+ if ((PCF_GET_S1(pcf) & PIN) == 0)
+ return (0);
+ }
+
+ return (IIC_ETIMEOUT);
+}
+
+static int pcf_stop(device_t pcfdev)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+
+ /*
+ * 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 (pcf->pcf_started) {
+ /* set stop condition and enable IT */
+ PCF_SET_S1(pcf, PIN|ES0|ENI|STO|ACK);
+
+ pcf->pcf_started = 0;
+ }
+
+ return (0);
+}
+
+
+static int pcf_noack(struct pcf_softc *pcf, int timeout)
+{
+ int noack;
+ int k = timeout/10;
+
+ do {
+ noack = PCF_GET_S1(pcf) & LRB;
+ if (!noack)
+ break;
+ DELAY(10); /* XXX wait 10 us */
+ } while (k--);
+
+ return (noack);
+}
+
+static int pcf_repeated_start(device_t pcfdev, u_char slave, int timeout)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ int error = 0;
+
+ /* repeated start */
+ PCF_SET_S1(pcf, ES0|STA|STO|ACK);
+
+ /* set slave address to PCF. Last bit (LSB) must be set correctly
+ * according to transfer direction */
+ PCF_SET_S0(pcf, slave);
+
+ /* wait for address sent, polling */
+ if ((error = pcf_wait_byte(pcf)))
+ goto error;
+
+ /* check for ack */
+ if (pcf_noack(pcf, timeout)) {
+ error = IIC_ENOACK;
+ goto error;
+ }
+
+ return (0);
+
+error:
+ pcf_stop(pcfdev);
+ return (error);
+}
+
+static int pcf_start(device_t pcfdev, u_char slave, int timeout)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ int error = 0;
+
+ if ((PCF_GET_S1(pcf) & nBB) == 0)
+ return (IIC_EBUSBSY);
+
+ /* set slave address to PCF. Last bit (LSB) must be set correctly
+ * according to transfer direction */
+ PCF_SET_S0(pcf, slave);
+
+ /* START only */
+ PCF_SET_S1(pcf, PIN|ES0|STA|ACK);
+
+ pcf->pcf_started = 1;
+
+ /* wait for address sent, polling */
+ if ((error = pcf_wait_byte(pcf)))
+ goto error;
+
+ /* check for ACK */
+ if (pcf_noack(pcf, timeout)) {
+ error = IIC_ENOACK;
+ goto error;
+ }
+
+ return (0);
+
+error:
+ pcf_stop(pcfdev);
+ return (error);
+}
+
+static void
+pcfintr(void *arg)
+{
+ device_t pcfdev = (device_t)arg;
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+
+ char data, status, addr;
+ char error = 0;
+
+ status = PCF_GET_S1(pcf);
+
+ if (status & PIN) {
+ device_printf(pcfdev, "spurious interrupt, status=0x%x\n", status & 0xff);
+
+ goto error;
+ }
+
+ if (status & LAB)
+ device_printf(pcfdev, "bus arbitration lost!\n");
+
+ if (status & BER) {
+ error = IIC_EBUSERR;
+ iicbus_intr(pcf->iicbus, INTR_ERROR, &error);
+
+ goto error;
+ }
+
+ do {
+ status = PCF_GET_S1(pcf);
+
+ switch(pcf->pcf_slave_mode) {
+
+ case SLAVE_TRANSMITTER:
+ if (status & LRB) {
+ /* ack interrupt line */
+ dummy_write(pcf);
+
+ /* no ack, don't send anymore */
+ pcf->pcf_slave_mode = SLAVE_RECEIVER;
+
+ iicbus_intr(pcf->iicbus, INTR_NOACK, NULL);
+ break;
+ }
+
+ /* get data from upper code */
+ iicbus_intr(pcf->iicbus, INTR_TRANSMIT, &data);
+
+ PCF_SET_S0(pcf, data);
+ break;
+
+ case SLAVE_RECEIVER:
+ if (status & AAS) {
+ addr = PCF_GET_S0(pcf);
+
+ if (status & AD0)
+ iicbus_intr(pcf->iicbus, INTR_GENERAL, &addr);
+ else
+ iicbus_intr(pcf->iicbus, INTR_START, &addr);
+
+ if (addr & LSB) {
+ pcf->pcf_slave_mode = SLAVE_TRANSMITTER;
+
+ /* get the first char from upper code */
+ iicbus_intr(pcf->iicbus, INTR_TRANSMIT, &data);
+
+ /* send first data byte */
+ PCF_SET_S0(pcf, data);
+ }
+
+ break;
+ }
+
+ /* stop condition received? */
+ if (status & STS) {
+ /* ack interrupt line */
+ dummy_read(pcf);
+
+ /* emulate intr stop condition */
+ iicbus_intr(pcf->iicbus, INTR_STOP, NULL);
+
+ } else {
+ /* get data, ack interrupt line */
+ data = PCF_GET_S0(pcf);
+
+ /* deliver the character */
+ iicbus_intr(pcf->iicbus, INTR_RECEIVE, &data);
+ }
+ break;
+
+ default:
+ panic("%s: unknown slave mode (%d)!", __func__,
+ pcf->pcf_slave_mode);
+ }
+
+ } while ((PCF_GET_S1(pcf) & PIN) == 0);
+
+ return;
+
+error:
+ /* unknown event on bus...reset PCF */
+ PCF_SET_S1(pcf, PIN|ES0|ENI|ACK);
+
+ pcf->pcf_slave_mode = SLAVE_RECEIVER;
+
+ return;
+}
+
+static int pcf_rst_card(device_t pcfdev, u_char speed, u_char addr, u_char *oldaddr)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+
+ if (oldaddr)
+ *oldaddr = pcf->pcf_addr;
+
+ /* retrieve own address from bus level */
+ if (!addr)
+ pcf->pcf_addr = PCF_DEFAULT_ADDR;
+ else
+ pcf->pcf_addr = addr;
+
+ PCF_SET_S1(pcf, PIN); /* initialize S1 */
+
+ /* own address S'O<>0 */
+ PCF_SET_S0(pcf, pcf->pcf_addr >> 1);
+
+ /* select clock register */
+ PCF_SET_S1(pcf, PIN|ES1);
+
+ /* select bus speed : 18=90kb, 19=45kb, 1A=11kb, 1B=1.5kb */
+ switch (speed) {
+ case IIC_SLOW:
+ PCF_SET_S0(pcf, 0x1b);
+ break;
+
+ case IIC_FAST:
+ PCF_SET_S0(pcf, 0x19);
+ break;
+
+ case IIC_UNKNOWN:
+ case IIC_FASTEST:
+ default:
+ PCF_SET_S0(pcf, 0x18);
+ break;
+ }
+
+ /* set bus on, ack=yes, INT=yes */
+ PCF_SET_S1(pcf, PIN|ES0|ENI|ACK);
+
+ pcf->pcf_slave_mode = SLAVE_RECEIVER;
+
+ return (0);
+}
+
+static int
+pcf_write(device_t pcfdev, char *buf, int len, int *sent, int timeout /* us */)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ int bytes, error = 0;
+
+#ifdef PCFDEBUG
+ printf("pcf%d: >> writing %d bytes\n", device_get_unit(pcfdev), len);
+#endif
+
+ bytes = 0;
+ while (len) {
+
+ PCF_SET_S0(pcf, *buf++);
+
+ /* wait for the byte to be send */
+ if ((error = pcf_wait_byte(pcf)))
+ goto error;
+
+ /* check if ack received */
+ if (pcf_noack(pcf, timeout)) {
+ error = IIC_ENOACK;
+ goto error;
+ }
+
+ len --;
+ bytes ++;
+ }
+
+error:
+ *sent = bytes;
+
+#ifdef PCFDEBUG
+ printf("pcf%d: >> %d bytes written (%d)\n",
+ device_get_unit(pcfdev), bytes, error);
+#endif
+
+ return (error);
+}
+
+static int
+pcf_read(device_t pcfdev, char *buf, int len, int *read, int last,
+ int delay /* us */)
+{
+ struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
+ int bytes, error = 0;
+
+#ifdef PCFDEBUG
+ printf("pcf%d: << reading %d bytes\n", device_get_unit(pcfdev), len);
+#endif
+
+ /* 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(pcf, ES0); /* no ack */
+
+ dummy_read(pcf);
+ }
+
+ bytes = 0;
+ while (len) {
+
+ /* XXX delay needed here */
+
+ /* wait for trigged byte */
+ if ((error = pcf_wait_byte(pcf))) {
+ pcf_stop(pcfdev);
+ goto error;
+ }
+
+ if (len == 1 && last)
+ /* ok, last data byte already in S0, no I2C activity
+ * on next PCF_GET_S0() */
+ pcf_stop(pcfdev);
+
+ else if (len == 2 && last)
+ /* next trigged byte with no ack */
+ PCF_SET_S1(pcf, ES0);
+
+ /* receive byte, trig next byte */
+ *buf++ = PCF_GET_S0(pcf);
+
+ len --;
+ bytes ++;
+ };
+
+error:
+ *read = bytes;
+
+#ifdef PCFDEBUG
+ printf("pcf%d: << %d bytes read (%d)\n",
+ device_get_unit(pcfdev), bytes, error);
+#endif
+
+ return (error);
+}
+
+DRIVER_MODULE(pcf, isa, pcf_driver, pcf_devclass, 0, 0);
OpenPOWER on IntegriCloud