/* $NetBSD: pca9564.c,v 1.2 2016/02/14 19:54:21 chs Exp $ */
/*
* Copyright (c) 2010 NONAKA Kimihiro <nonaka@netbsd.org>
* 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 ``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 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>
__KERNEL_RCSID(0, "$NetBSD: pca9564.c,v 1.2 2016/02/14 19:54:21 chs Exp $");
#include <sys/param.h>
#include <sys/device.h>
#include <sys/mutex.h>
#include <sys/bus.h>
#include <dev/i2c/i2cvar.h>
#include <dev/ic/pca9564reg.h>
#include <dev/ic/pca9564var.h>
#if defined(PCA9564_DEBUG)
int pca9564debug = 0;
#define DPRINTF(s) if (pca9564debug) printf s
#else
#define DPRINTF(s)
#endif
static int pca9564_acquire_bus(void *, int);
static void pca9564_release_bus(void *, int);
static int pca9564_send_start(void *, int);
static int pca9564_send_stop(void *, int);
static int pca9564_initiate_xfer(void *, uint16_t, int);
static int pca9564_read_byte(void *, uint8_t *, int);
static int pca9564_write_byte(void *, uint8_t, int);
static int pca9564_ack(void *, bool, int);
#define CSR_READ(sc, r) (*sc->sc_ios.read_byte)(sc->sc_dev, r)
#define CSR_WRITE(sc, r, v) (*sc->sc_ios.write_byte)(sc->sc_dev, r, v)
void
pca9564_attach(struct pca9564_softc *sc)
{
struct i2cbus_attach_args iba;
aprint_naive("\n");
aprint_normal(": PCA9564 I2C Controller\n");
mutex_init(&sc->sc_buslock, MUTEX_DEFAULT, IPL_NONE);
sc->sc_i2c.ic_cookie = sc;
sc->sc_i2c.ic_acquire_bus = pca9564_acquire_bus;
sc->sc_i2c.ic_release_bus = pca9564_release_bus;
sc->sc_i2c.ic_send_start = pca9564_send_start;
sc->sc_i2c.ic_send_stop = pca9564_send_stop;
sc->sc_i2c.ic_initiate_xfer = pca9564_initiate_xfer;
sc->sc_i2c.ic_read_byte = pca9564_read_byte;
sc->sc_i2c.ic_write_byte = pca9564_write_byte;
sc->sc_i2c.ic_exec = NULL;
/* set serial clock rate */
switch (sc->sc_i2c_clock) {
case 330000: /* 330kHz */
sc->sc_i2c_clock = I2CCON_CR_330KHZ;
break;
case 288000: /* 288kHz */
sc->sc_i2c_clock = I2CCON_CR_288KHZ;
break;
case 217000: /* 217kHz */
sc->sc_i2c_clock = I2CCON_CR_217KHZ;
break;
case 146000: /* 146kHz */
sc->sc_i2c_clock = I2CCON_CR_146KHZ;
break;
case 88000: /* 88kHz */
sc->sc_i2c_clock = I2CCON_CR_88KHZ;
break;
case 0: /* default */
case 59000: /* 59kHz */
sc->sc_i2c_clock = I2CCON_CR_59KHZ;
break;
case 44000: /* 44kHz */
sc->sc_i2c_clock = I2CCON_CR_44KHZ;
break;
case 36000: /* 36kHz */
sc->sc_i2c_clock = I2CCON_CR_36KHZ;
break;
default:
aprint_error_dev(sc->sc_dev, "unknown i2c clock %dHz\n",
sc->sc_i2c_clock);
sc->sc_i2c_clock = I2CCON_CR_59KHZ;
break;
}
memset(&iba, 0, sizeof(iba));
iba.iba_tag = &sc->sc_i2c;
(void) config_found_ia(sc->sc_dev, "i2cbus", &iba, iicbus_print);
}
static int
pca9564_acquire_bus(void *cookie, int flags)
{
struct pca9564_softc *sc = cookie;
uint8_t control;
mutex_enter(&sc->sc_buslock);
/* Enable SIO and set clock */
control = CSR_READ(sc, PCA9564_I2CCON);
control |= I2CCON_ENSIO;
control &= ~(I2CCON_STA|I2CCON_STO|I2CCON_SI|I2CCON_AA);
control &= ~I2CCON_CR_MASK;
control |= sc->sc_i2c_clock;
CSR_WRITE(sc, PCA9564_I2CCON, control);
delay(500);
return 0;
}
static void
pca9564_release_bus(void *cookie, int flags)
{
struct pca9564_softc *sc = cookie;
uint8_t control;
/* Disable SIO */
control = CSR_READ(sc, PCA9564_I2CCON);
control &= ~I2CCON_ENSIO;
CSR_WRITE(sc, PCA9564_I2CCON, control);
mutex_exit(&sc->sc_buslock);
}
#define PCA9564_TIMEOUT 100 /* protocol timeout, in uSecs */
static int
pca9564_wait(struct pca9564_softc *sc, int flags)
{
int timeout;
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
for (timeout = PCA9564_TIMEOUT; timeout > 0; timeout--) {
if (CSR_READ(sc, PCA9564_I2CCON) & I2CCON_SI)
break;
delay(1);
}
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
if (timeout == 0) {
aprint_error_dev(sc->sc_dev, "timeout\n");
return ETIMEDOUT;
}
return 0;
}
static int
pca9564_send_start(void *cookie, int flags)
{
struct pca9564_softc *sc = cookie;
uint8_t control;
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
control = CSR_READ(sc, PCA9564_I2CCON);
control |= I2CCON_STA;
control &= ~(I2CCON_STO|I2CCON_SI);
CSR_WRITE(sc, PCA9564_I2CCON, control);
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
return pca9564_wait(sc, flags);
}
static int
pca9564_send_stop(void *cookie, int flags)
{
struct pca9564_softc *sc = cookie;
uint8_t control;
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
control = CSR_READ(sc, PCA9564_I2CCON);
control |= I2CCON_STO;
control &= ~(I2CCON_STA|I2CCON_SI);
CSR_WRITE(sc, PCA9564_I2CCON, control);
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
return 0;
}
static int
pca9564_initiate_xfer(void *cookie, uint16_t addr, int flags)
{
struct pca9564_softc *sc = cookie;
int error, rd_req = (flags & I2C_F_READ) != 0;
uint8_t data, control;
error = pca9564_send_start(sc, flags);
if (error) {
aprint_error_dev(sc->sc_dev, "failed to send start %s xfer\n",
rd_req ? "read" : "write");
return error;
}
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
control = CSR_READ(sc, PCA9564_I2CCON);
data = (addr << 1) | (rd_req ? 1 : 0);
CSR_WRITE(sc, PCA9564_I2CDAT, data);
control &= ~(I2CCON_STO|I2CCON_STA|I2CCON_SI);
CSR_WRITE(sc, PCA9564_I2CCON, control);
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
error = pca9564_wait(sc, flags);
if (error)
aprint_error_dev(sc->sc_dev, "failed to initiate %s xfer\n",
rd_req ? "read" : "write");
return error;
}
static int
pca9564_read_byte(void *cookie, uint8_t *bytep, int flags)
{
struct pca9564_softc *sc = cookie;
int send_stop = (flags & I2C_F_STOP) != 0;
int error;
error = pca9564_ack(sc, !send_stop, flags);
if (error) {
aprint_error_dev(sc->sc_dev, "failed to ack\n");
return error;
}
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
*bytep = CSR_READ(sc, PCA9564_I2CDAT);
DPRINTF(("%s: status=%#x, byte=%#x\n", __func__,
CSR_READ(sc, PCA9564_I2CSTA), *bytep));
if (send_stop)
pca9564_send_stop(sc, flags);
return 0;
}
static int
pca9564_write_byte(void *cookie, uint8_t byte, int flags)
{
struct pca9564_softc *sc = cookie;
int send_stop = (flags & I2C_F_STOP) != 0;
int error;
uint8_t control;
DPRINTF(("%s: status=%#x, byte=%#x\n", __func__,
CSR_READ(sc, PCA9564_I2CSTA), byte));
control = CSR_READ(sc, PCA9564_I2CCON);
CSR_WRITE(sc, PCA9564_I2CDAT, byte);
control &= ~(I2CCON_STO|I2CCON_STA|I2CCON_SI);
CSR_WRITE(sc, PCA9564_I2CCON, control);
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
error = pca9564_wait(sc, flags);
if (error)
aprint_error_dev(sc->sc_dev, "write byte failed\n");
if (send_stop)
pca9564_send_stop(sc, flags);
return error;
}
static int
pca9564_ack(void *cookie, bool ack, int flags)
{
struct pca9564_softc *sc = cookie;
uint8_t control;
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
control = CSR_READ(sc, PCA9564_I2CCON);
control &= ~(I2CCON_STO|I2CCON_STA|I2CCON_SI|I2CCON_AA);
if (ack)
control |= I2CCON_AA;
CSR_WRITE(sc, PCA9564_I2CCON, control);
DPRINTF(("%s: status=%#x\n", __func__, CSR_READ(sc, PCA9564_I2CSTA)));
return pca9564_wait(sc, flags);
}