#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include <bus/isa/isareg.h>
#include <bus/isa/isavar.h>
#include <bus/isa/isa_device.h>
#include "../iiconf.h"
#include "iicbus_if.h"
#define IO_PCFSIZE 2
#define TIMEOUT 9999
#define nBB 0x01
#define LAB 0x02
#define AAS 0x04
#define LRB 0x08
#define AD0 0x08
#define BER 0x10
#define STS 0x20
#define PIN 0x80
#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;
int pcf_flags;
u_char pcf_addr;
int pcf_slave_mode;
int pcf_started;
device_t 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, const 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[] = {
DEVMETHOD(device_probe, pcf_probe),
DEVMETHOD(device_attach, pcf_attach),
DEVMETHOD(bus_print_child, pcf_print_child),
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),
DEVMETHOD_END
};
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");
bzero(pcf, sizeof(struct pcf_softc));
pcf->rid_irq = pcf->rid_ioport = 0;
pcf->res_irq = pcf->res_ioport = NULL;
pcf->res_ioport = bus_alloc_resource(pcfdev, SYS_RES_IOPORT,
&pcf->rid_ioport, 0ul, ~0ul,
IO_PCFSIZE, RF_ACTIVE);
if (pcf->res_ioport == NULL) {
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 == NULL) {
device_printf(pcfdev, "can't reserve irq, polled mode.\n");
pcf->pcf_flags |= IIC_POLLED;
}
}
pcf_rst_card(pcfdev, IIC_FASTEST, PCF_DEFAULT_ADDR, NULL);
return (0);
error:
if (pcf->res_ioport != NULL) {
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) {
error = BUS_SETUP_INTR(parent, pcfdev, pcf->res_irq,
0, pcfintr, pcfdev,
&pcf->intr_cookie, NULL, NULL);
if (error)
return (error);
}
pcf->iicbus = device_add_child(pcfdev, "iicbus", -1);
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 += kprintf(" on %s addr 0x%x\n", device_get_nameunit(bus),
(int)pcf->pcf_addr);
return (retval);
}
#define pcf_nops() DELAY(10)
#define dummy_read(pcf) PCF_GET_S0(pcf)
#define dummy_write(pcf) PCF_SET_S0(pcf, 0)
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);
}
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);
if (pcf->pcf_started) {
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);
} 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;
PCF_SET_S1(pcf, ES0|STA|STO|ACK);
PCF_SET_S0(pcf, slave);
if ((error = pcf_wait_byte(pcf)))
goto error;
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);
PCF_SET_S0(pcf, slave);
PCF_SET_S1(pcf, PIN|ES0|STA|ACK);
pcf->pcf_started = 1;
if ((error = pcf_wait_byte(pcf)))
goto error;
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) {
dummy_write(pcf);
pcf->pcf_slave_mode = SLAVE_RECEIVER;
iicbus_intr(pcf->iicbus, INTR_NOACK, NULL);
break;
}
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;
iicbus_intr(pcf->iicbus, INTR_TRANSMIT, &data);
PCF_SET_S0(pcf, data);
}
break;
}
if (status & STS) {
dummy_read(pcf);
iicbus_intr(pcf->iicbus, INTR_STOP, NULL);
} else {
data = PCF_GET_S0(pcf);
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:
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;
if (!addr)
pcf->pcf_addr = PCF_DEFAULT_ADDR;
else
pcf->pcf_addr = addr;
PCF_SET_S1(pcf, PIN);
PCF_SET_S0(pcf, pcf->pcf_addr >> 1);
PCF_SET_S1(pcf, PIN|ES1);
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;
}
PCF_SET_S1(pcf, PIN|ES0|ENI|ACK);
pcf->pcf_slave_mode = SLAVE_RECEIVER;
return (0);
}
static int
pcf_write(device_t pcfdev, const char *buf, int len, int *sent,
int timeout )
{
struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
int bytes, error = 0;
#ifdef PCFDEBUG
kprintf("pcf%d: >> writing %d bytes\n", device_get_unit(pcfdev), len);
#endif
bytes = 0;
while (len) {
PCF_SET_S0(pcf, *buf++);
if ((error = pcf_wait_byte(pcf)))
goto error;
if (pcf_noack(pcf, timeout)) {
error = IIC_ENOACK;
goto error;
}
len --;
bytes ++;
}
error:
*sent = bytes;
#ifdef PCFDEBUG
kprintf("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 )
{
struct pcf_softc *pcf = DEVTOSOFTC(pcfdev);
int bytes, error = 0;
#ifdef PCFDEBUG
kprintf("pcf%d: << reading %d bytes\n", device_get_unit(pcfdev), len);
#endif
if (len) {
if (len == 1 && last)
PCF_SET_S1(pcf, ES0);
dummy_read(pcf);
}
bytes = 0;
while (len) {
if ((error = pcf_wait_byte(pcf))) {
pcf_stop(pcfdev);
goto error;
}
if (len == 1 && last)
pcf_stop(pcfdev);
else if (len == 2 && last)
PCF_SET_S1(pcf, ES0);
*buf++ = PCF_GET_S0(pcf);
len --;
bytes ++;
}
error:
*read = bytes;
#ifdef PCFDEBUG
kprintf("pcf%d: << %d bytes read (%d)\n",
device_get_unit(pcfdev), bytes, error);
#endif
return (error);
}
DRIVER_MODULE(pcf, isa, pcf_driver, pcf_devclass, NULL, NULL);