278 lines
7.2 KiB
C
278 lines
7.2 KiB
C
/* $NetBSD: gti2c.c,v 1.5 2006/06/26 18:21:39 drochner Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2005 Brocade Communcations, inc.
|
|
* All rights reserved.
|
|
*
|
|
* Written by Matt Thomas for Brocade Communcations, Inc.
|
|
*
|
|
* 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.
|
|
* 3. The name of Brocade Communications, Inc. may not be used to endorse
|
|
* or promote products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY BROCADE COMMUNICATIONS, INC. ``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 EITHER BROCADE COMMUNICATIONS, INC. 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: gti2c.c,v 1.5 2006/06/26 18:21:39 drochner Exp $");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/device.h>
|
|
#include <sys/conf.h>
|
|
#include <sys/proc.h>
|
|
|
|
#include <machine/intr.h>
|
|
#include <machine/bus.h>
|
|
|
|
#include <dev/marvell/gtintrreg.h>
|
|
#include <dev/marvell/gti2creg.h>
|
|
|
|
#include <dev/marvell/gtvar.h>
|
|
|
|
#include <dev/i2c/i2cvar.h>
|
|
|
|
struct gti2c_softc {
|
|
struct device sc_dev;
|
|
struct evcnt sc_ev_intr;
|
|
struct i2c_controller sc_i2c;
|
|
struct gt_softc *sc_gt;
|
|
struct lock sc_lock;
|
|
};
|
|
|
|
static int gt_i2c_match(struct device *, struct cfdata *, void *);
|
|
static void gt_i2c_attach(struct device *, struct device *, void *);
|
|
|
|
CFATTACH_DECL(gtiic, sizeof(struct gti2c_softc),
|
|
gt_i2c_match, gt_i2c_attach, NULL, NULL);
|
|
|
|
extern struct cfdriver gtiic_cd;
|
|
|
|
static int
|
|
gt_i2c_wait(struct gti2c_softc *sc, uint32_t control,
|
|
uint32_t desired_status, int flags)
|
|
{
|
|
uint32_t status;
|
|
int error = 0;
|
|
|
|
again:
|
|
if (flags & I2C_F_POLL)
|
|
control |= I2C_Control_IntEn;
|
|
if (desired_status != I2C_Status_MasterReadAck)
|
|
gt_write(sc->sc_gt, I2C_REG_Control, control);
|
|
|
|
for (;;) {
|
|
control = gt_read(sc->sc_gt, I2C_REG_Control);
|
|
if (control & I2C_Control_IFlg)
|
|
break;
|
|
error = tsleep(sc, PZERO, "gti2cwait",
|
|
(flags & I2C_F_POLL) ? 1 : 0);
|
|
if (error && (error != ETIMEDOUT || !(flags & I2C_F_POLL)))
|
|
return error;
|
|
}
|
|
|
|
status = gt_read(sc->sc_gt, I2C_REG_Status);
|
|
if (status != desired_status)
|
|
return EIO;
|
|
|
|
if ((flags & I2C_F_LAST) &&
|
|
desired_status != I2C_Status_MasterReadAck) {
|
|
control = I2C_Control_Stop;
|
|
goto again;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
gt_i2c_acquire_bus(void *cookie, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
uint32_t status;
|
|
int error;
|
|
|
|
if (flags & I2C_F_POLL)
|
|
return 0;
|
|
|
|
error = lockmgr(&sc->sc_lock, LK_EXCLUSIVE, NULL);
|
|
if (error)
|
|
return error;
|
|
|
|
status = gt_read(sc->sc_gt, I2C_REG_Status);
|
|
if (status != I2C_Status_Idle) {
|
|
gt_write(sc->sc_gt, I2C_REG_SoftReset, 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gt_i2c_release_bus(void *cookie, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
|
|
lockmgr(&sc->sc_lock, LK_RELEASE, NULL);
|
|
}
|
|
|
|
static int
|
|
gt_i2c_send_start(void *cookie, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
|
|
return gt_i2c_wait(sc, I2C_Control_Start, I2C_Status_Started, flags);
|
|
}
|
|
|
|
static int
|
|
gt_i2c_send_stop(void *cookie, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
|
|
return gt_i2c_wait(sc, I2C_Control_Stop, I2C_Status_Idle, flags);
|
|
}
|
|
|
|
static int
|
|
gt_i2c_initiate_xfer(void *cookie, i2c_addr_t addr, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
uint32_t data, wanted_status;
|
|
uint8_t read_mask = (flags & I2C_F_READ) != 0;
|
|
int error;
|
|
|
|
if (read_mask) {
|
|
wanted_status = I2C_Status_AddrReadAck;
|
|
} else {
|
|
wanted_status = I2C_Status_AddrWriteAck;
|
|
}
|
|
/*
|
|
* First byte contains whether this xfer is a read or write.
|
|
*/
|
|
data = read_mask;
|
|
if (addr > 0x7f) {
|
|
/*
|
|
* If this is a 10bit request, the first address byte is
|
|
* 0b11110<b9><b8><r/w>.
|
|
*/
|
|
data |= 0xf0 | ((addr & 0x30) >> 7);
|
|
gt_write(sc->sc_gt, I2C_REG_Data, data);
|
|
error = gt_i2c_wait(sc, 0, wanted_status, flags);
|
|
if (error)
|
|
return error;
|
|
/*
|
|
* The first address byte has been sent, now to send
|
|
* the second one.
|
|
*/
|
|
if (read_mask) {
|
|
wanted_status = I2C_Status_2ndAddrReadAck;
|
|
} else {
|
|
wanted_status = I2C_Status_2ndAddrWriteAck;
|
|
}
|
|
data = (uint8_t) addr;
|
|
} else {
|
|
data |= (addr << 1);
|
|
}
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_Data, data);
|
|
return gt_i2c_wait(sc, 0, wanted_status, flags);
|
|
}
|
|
|
|
static int
|
|
gt_i2c_read_byte(void *cookie, uint8_t *dp, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
int error;
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_Data, *dp);
|
|
error = gt_i2c_wait(sc, 0, I2C_Status_MasterReadAck, flags);
|
|
if (error == 0) {
|
|
*dp = gt_read(sc->sc_gt, I2C_REG_Data);
|
|
}
|
|
if (flags & I2C_F_LAST)
|
|
gt_write(sc->sc_gt, I2C_REG_Control, 0);
|
|
return error;
|
|
}
|
|
|
|
static int
|
|
gt_i2c_write_byte(void *cookie, uint8_t v, int flags)
|
|
{
|
|
struct gti2c_softc * const sc = cookie;
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_Data, v);
|
|
return gt_i2c_wait(sc, 0, I2C_Status_MasterWriteAck, flags);
|
|
}
|
|
|
|
static int
|
|
gt_i2c_intr(void *aux)
|
|
{
|
|
struct gti2c_softc * const sc = aux;
|
|
uint32_t v;
|
|
|
|
v = gt_read(sc->sc_gt, I2C_REG_Control);
|
|
if ((v & I2C_Control_IFlg) == 0)
|
|
return 0;
|
|
gt_write(sc->sc_gt, I2C_REG_Control, v & ~I2C_Control_IntEn);
|
|
|
|
sc->sc_ev_intr.ev_count++;
|
|
|
|
wakeup(sc);
|
|
return 1;
|
|
}
|
|
|
|
int
|
|
gt_i2c_match(struct device *parent, struct cfdata *cfdata, void *aux)
|
|
{
|
|
struct gt_softc * const gt = device_private(parent);
|
|
struct gt_attach_args * const ga = aux;
|
|
|
|
return GT_I2COK(gt, ga, >iic_cd);
|
|
}
|
|
|
|
void
|
|
gt_i2c_attach(struct device *parent, struct device *self, void *aux)
|
|
{
|
|
struct gt_softc * const gt = device_private(parent);
|
|
struct gti2c_softc * const sc = device_private(self);
|
|
struct gt_attach_args * const ga = aux;
|
|
struct i2cbus_attach_args iba;
|
|
|
|
sc->sc_gt = gt;
|
|
|
|
GT_I2CFOUND(gt, ga);
|
|
|
|
lockinit(&sc->sc_lock, PZERO, sc->sc_dev.dv_xname, 0, 0);
|
|
|
|
sc->sc_i2c.ic_cookie = sc;
|
|
sc->sc_i2c.ic_acquire_bus = gt_i2c_acquire_bus;
|
|
sc->sc_i2c.ic_release_bus = gt_i2c_release_bus;
|
|
sc->sc_i2c.ic_release_bus = gt_i2c_release_bus;
|
|
sc->sc_i2c.ic_send_start = gt_i2c_send_start;
|
|
sc->sc_i2c.ic_send_stop = gt_i2c_send_stop;
|
|
sc->sc_i2c.ic_initiate_xfer = gt_i2c_initiate_xfer;
|
|
sc->sc_i2c.ic_read_byte = gt_i2c_read_byte;
|
|
sc->sc_i2c.ic_write_byte = gt_i2c_write_byte;
|
|
|
|
intr_establish(IRQ_I2C, IST_LEVEL, IPL_I2C, gt_i2c_intr, sc);
|
|
|
|
evcnt_attach_dynamic(&sc->sc_ev_intr, EVCNT_TYPE_INTR, NULL,
|
|
sc->sc_dev.dv_xname, "intr");
|
|
|
|
iba.iba_tag = &sc->sc_i2c;
|
|
config_found_ia(self, "i2cbus", &iba, iicbus_print);
|
|
}
|