forked from Imagelibrary/rtems
448 lines
11 KiB
C
448 lines
11 KiB
C
/* $NetBSD: gti2c.c,v 1.2 2005/02/27 00:27:21 perry 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.
|
|
*/
|
|
|
|
/* Fixed many things + ported to RTEMS by Till Straumann, 2005 */
|
|
|
|
#include <stdio.h>
|
|
#include <rtems.h>
|
|
#include <libcpu/io.h>
|
|
#include <sys/errno.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <rtems/score/sysstate.h>
|
|
#include <bsp/irq.h>
|
|
#include <rtems/libi2c.h>
|
|
|
|
#include <sys/cdefs.h>
|
|
|
|
#include <bsp/gtintrreg.h>
|
|
#include <bsp/gti2creg.h>
|
|
#include <bsp/gti2c_busdrv.h>
|
|
|
|
#define ENABLE_IRQ_AT_PIC_HACK /* workaround for a bad HW bug */
|
|
#undef DEBUG
|
|
|
|
#ifndef BSP_IRQ_MIN_PRIO
|
|
#define BSP_IRQ_MIN_PRIO 1
|
|
#endif
|
|
|
|
struct gti2c_softc {
|
|
uint32_t sc_gt;
|
|
uint32_t sc_cntl;
|
|
int sc_inited;
|
|
rtems_id sc_sync;
|
|
int sc_irqs; /* statistics */
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
#define STATIC
|
|
#else
|
|
#define STATIC static
|
|
#endif
|
|
|
|
typedef struct {
|
|
rtems_libi2c_bus_t bus_desc;
|
|
struct gti2c_softc pvt;
|
|
} gti2c_desc_rec, *gti2c_desc;
|
|
|
|
STATIC rtems_status_code
|
|
gt_i2c_init(rtems_libi2c_bus_t *bh);
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_start(rtems_libi2c_bus_t *bh);
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_stop(rtems_libi2c_bus_t *bh);
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw);
|
|
STATIC int
|
|
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);
|
|
STATIC int
|
|
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len);
|
|
|
|
static rtems_libi2c_bus_ops_t myops = {
|
|
.init = gt_i2c_init,
|
|
.send_start = gt_i2c_send_start,
|
|
.send_stop = gt_i2c_send_stop,
|
|
.send_addr = gt_i2c_send_addr,
|
|
.read_bytes = gt_i2c_read_bytes,
|
|
.write_bytes = gt_i2c_write_bytes,
|
|
};
|
|
|
|
static gti2c_desc_rec my_bus_tbl = {
|
|
{
|
|
.ops = &myops,
|
|
.size = sizeof(my_bus_tbl),
|
|
},/* public fields */
|
|
{
|
|
.sc_gt = BSP_MV64x60_BASE,
|
|
.sc_cntl = I2C_Control_TWSIEn,
|
|
.sc_inited = 0,
|
|
.sc_sync = 0
|
|
} /* our private fields */
|
|
};
|
|
|
|
|
|
static inline uint32_t
|
|
gt_read(uint32_t base, uint32_t off)
|
|
{
|
|
return in_le32((volatile uint32_t*)(base+off));
|
|
}
|
|
|
|
static inline void
|
|
gt_write(uint32_t base, uint32_t off, uint32_t val)
|
|
{
|
|
out_le32((volatile uint32_t*)(base+off), val);
|
|
}
|
|
|
|
|
|
static inline void
|
|
disable_irq(struct gti2c_softc *sc)
|
|
{
|
|
uint32_t v = gt_read(sc->sc_gt, I2C_REG_Control);
|
|
gt_write(sc->sc_gt, I2C_REG_Control, v & ~I2C_Control_IntEn);
|
|
}
|
|
|
|
|
|
static rtems_status_code
|
|
gt_i2c_wait(struct gti2c_softc *sc, uint32_t control, uint32_t desired_status)
|
|
{
|
|
uint32_t status;
|
|
rtems_status_code rval;
|
|
|
|
control |= I2C_Control_IntEn;
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_Control, control | sc->sc_cntl);
|
|
|
|
if ( sc->sc_inited ) {
|
|
|
|
#ifdef ENABLE_IRQ_AT_PIC_HACK
|
|
BSP_enable_irq_at_pic(BSP_IRQ_I2C);
|
|
#endif
|
|
|
|
rval = rtems_semaphore_obtain(sc->sc_sync, RTEMS_WAIT, 100);
|
|
|
|
if ( RTEMS_SUCCESSFUL != rval )
|
|
return rval;
|
|
} else {
|
|
uint32_t then, now;
|
|
|
|
/* run in polling mode - useful during init */
|
|
if ( _System_state_Is_up(_System_state_Get()) ) {
|
|
printk("WARNING: gti2c running in polled mode -- should initialize properly!\n");
|
|
}
|
|
|
|
asm volatile("mftb %0":"=r"(then));
|
|
|
|
do {
|
|
asm volatile("mftb %0":"=r"(now));
|
|
/* poll timebase for .2 seconds assuming a bus clock of 100MHz */
|
|
if ( now - then > (uint32_t)100000000/4/5 )
|
|
return RTEMS_TIMEOUT;
|
|
} while ( ! (I2C_Control_IFlg & gt_read(sc->sc_gt, I2C_REG_Control)) );
|
|
}
|
|
|
|
status = gt_read(sc->sc_gt, I2C_REG_Status);
|
|
|
|
if ( status != desired_status && (status!=I2C_Status_ReStarted || desired_status!=I2C_Status_Started) )
|
|
return RTEMS_IO_ERROR;
|
|
|
|
return RTEMS_SUCCESSFUL;
|
|
}
|
|
|
|
static void
|
|
gt_i2c_intr(void *arg)
|
|
{
|
|
struct gti2c_softc * const sc = &my_bus_tbl.pvt;
|
|
uint32_t v;
|
|
|
|
v = gt_read(sc->sc_gt, I2C_REG_Control);
|
|
if ((v & I2C_Control_IFlg) == 0) {
|
|
printk("gt_i2c_intr: IRQ but IFlg not set??\n");
|
|
return;
|
|
}
|
|
gt_write(sc->sc_gt, I2C_REG_Control, v & ~(I2C_Control_IntEn));
|
|
#if 0
|
|
gt_read(sc->sc_gt, I2C_REG_Control);
|
|
asm volatile("sync");
|
|
/* This is how bad it is: after turning off the IntEn bit, the line
|
|
* still remains asserted! (shame on you.)
|
|
*
|
|
* The test below (on MVME6100; the MVME5500 has the same problem
|
|
* but the main cause register address is different; substitute
|
|
* 0xf100000c for 0xf1000c68 on a 5500).
|
|
*
|
|
* The skew was 101 TB ticks or ~3us (bus freq 133MHz) which
|
|
* really sucks.
|
|
*
|
|
* Therefore, we must disable the interrupt at the PIC
|
|
*/
|
|
{unsigned from,to;
|
|
asm volatile("mftb %0":"=r"(from));
|
|
while ( in_le32((volatile uint32_t*)0xf100000c) & 0x20 )
|
|
;
|
|
asm volatile("mftb %0":"=r"(to));
|
|
printk("I2C IRQ remained asserted for %i TB ticks!\n",to-from);
|
|
}
|
|
#endif
|
|
#ifdef ENABLE_IRQ_AT_PIC_HACK
|
|
BSP_disable_irq_at_pic(BSP_IRQ_I2C);
|
|
#endif
|
|
|
|
sc->sc_irqs++;
|
|
|
|
rtems_semaphore_release(sc->sc_sync);
|
|
}
|
|
|
|
STATIC rtems_status_code
|
|
gt_i2c_init(rtems_libi2c_bus_t *bh)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
unsigned m,n,N;
|
|
|
|
disable_irq(sc);
|
|
|
|
/* reset */
|
|
gt_write(sc->sc_gt, I2C_REG_SoftReset, 0);
|
|
gt_write(sc->sc_gt, I2C_REG_SlaveAddr, 0);
|
|
gt_write(sc->sc_gt, I2C_REG_ExtSlaveAddr, 0);
|
|
|
|
/* Set baud rate; I don't know the details
|
|
* but have to assume that it has to fit into 7 bits
|
|
* (as indicated by some experiment)
|
|
*/
|
|
n = 0, N=1<<n;
|
|
do {
|
|
n++, N<<=1;
|
|
/* increase 2^n until m becomes small enough */
|
|
m = BSP_bus_frequency / 10 / 62500 / N;
|
|
} while ( m > 16 );
|
|
|
|
/* n is at least 1 */
|
|
if ( n > 8 ) {
|
|
n = 8; m = 16; /* nothing else we can do */
|
|
}
|
|
if ( 0 == m )
|
|
m = 1; /* nothing we can do */
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_BaudRate, I2C_BaudRate(m-1, n-1));
|
|
|
|
if ( !sc->sc_inited ) {
|
|
|
|
if ( _System_state_Is_up(_System_state_Get()) ) {
|
|
rtems_irq_connect_data ii = {
|
|
.name = BSP_IRQ_I2C,
|
|
.hdl = gt_i2c_intr,
|
|
.on = 0,
|
|
.off = 0,
|
|
.isOn = 0
|
|
};
|
|
rtems_status_code err;
|
|
/* synchronization semaphore */
|
|
err = rtems_semaphore_create(
|
|
rtems_build_name('g','i','2','c'),
|
|
0,
|
|
RTEMS_SIMPLE_BINARY_SEMAPHORE | RTEMS_LOCAL,
|
|
0,
|
|
&sc->sc_sync);
|
|
if ( err ) {
|
|
sc->sc_sync = 0;
|
|
return err;
|
|
}
|
|
if ( !BSP_install_rtems_irq_handler(&ii) ) {
|
|
fprintf(stderr,"Unable to install interrupt handler\n");
|
|
rtems_semaphore_delete(sc->sc_sync);
|
|
return RTEMS_INTERNAL_ERROR;
|
|
}
|
|
BSP_irq_set_priority(BSP_IRQ_I2C, BSP_IRQ_MIN_PRIO);
|
|
sc->sc_inited = 1;
|
|
} else {
|
|
}
|
|
} else {
|
|
rtems_semaphore_flush(sc->sc_sync);
|
|
}
|
|
return RTEMS_SUCCESSFUL;
|
|
}
|
|
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_start(rtems_libi2c_bus_t *bh)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
|
|
return gt_i2c_wait(sc, I2C_Control_Start, I2C_Status_Started);
|
|
}
|
|
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_stop(rtems_libi2c_bus_t *bh)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
uint32_t data;
|
|
|
|
data = gt_read(sc->sc_gt, I2C_REG_Status);
|
|
if ( I2C_Status_Started == data || I2C_Status_ReStarted == data ) {
|
|
/* According to the spec, a void message (start - stop sequence)
|
|
* is illegal and indeed, the chip plays bad tricks with us, i.e.,
|
|
* sometimes it hangs the bus so that it remains idle forever.
|
|
* so we have to address someone...
|
|
*/
|
|
gt_i2c_send_addr(bh, /*just something... */ 8, 1);
|
|
data = gt_read(sc->sc_gt, I2C_REG_Status);
|
|
}
|
|
|
|
if ( I2C_Status_AddrReadAck == data ) {
|
|
/* Another thing: spec says that the master generates stop only after
|
|
* not acknowledging the last byte. Again, the chip doesn't like
|
|
* to be stopped in this condition - hence we just do it the favor
|
|
* and read a single byte...
|
|
*/
|
|
gt_i2c_read_bytes(bh, (unsigned char *)&data, 1);
|
|
}
|
|
|
|
gt_write(sc->sc_gt, I2C_REG_Control, I2C_Control_Stop | sc->sc_cntl);
|
|
|
|
/* should we poll for idle? There seems to be in IRQ when this completes */
|
|
return RTEMS_SUCCESSFUL;
|
|
}
|
|
|
|
STATIC rtems_status_code
|
|
gt_i2c_send_addr(rtems_libi2c_bus_t *bh, uint32_t addr, int rw)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
uint32_t data, wanted_status;
|
|
uint8_t read_mask = rw ? 1 : 0;
|
|
rtems_status_code 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 & 0x300) >> 7);
|
|
gt_write(sc->sc_gt, I2C_REG_Data, data);
|
|
error = gt_i2c_wait(sc, 0, wanted_status);
|
|
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);
|
|
}
|
|
|
|
STATIC int
|
|
gt_i2c_read_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
rtems_status_code error;
|
|
register unsigned char *p=buf;
|
|
|
|
while ( len-- > 0 ) {
|
|
error = gt_i2c_wait(
|
|
sc,
|
|
len ? I2C_Control_ACK : 0,
|
|
len ? I2C_Status_MasterReadAck : I2C_Status_MasterReadNoAck);
|
|
if ( error ) {
|
|
return -error;
|
|
}
|
|
*p++ = gt_read(sc->sc_gt, I2C_REG_Data);
|
|
}
|
|
|
|
return p-buf;
|
|
}
|
|
|
|
STATIC int
|
|
gt_i2c_write_bytes(rtems_libi2c_bus_t *bh, unsigned char *buf, int len)
|
|
{
|
|
struct gti2c_softc * const sc = &((gti2c_desc)bh)->pvt;
|
|
int rval = 0;
|
|
rtems_status_code error;
|
|
|
|
while ( len-- > 0 ) {
|
|
gt_write(sc->sc_gt, I2C_REG_Data, buf[rval]);
|
|
error = gt_i2c_wait(sc, 0, I2C_Status_MasterWriteAck);
|
|
if ( error ) {
|
|
return -error;
|
|
}
|
|
rval++;
|
|
}
|
|
|
|
return rval;
|
|
}
|
|
|
|
rtems_libi2c_bus_t *gt64260_i2c_bus_descriptor = &my_bus_tbl.bus_desc;
|
|
|
|
#ifdef DEBUG_MODULAR
|
|
|
|
void
|
|
_cexpModuleInitialize(void *arg)
|
|
{
|
|
gt_i2c_init(>64260_i2c_bus_descriptor->bus_desc);
|
|
}
|
|
|
|
int
|
|
_cexpModuleFinalize(void * arg)
|
|
{
|
|
struct gti2c_softc * const sc = >64260_i2c_bus_descriptor->pvt;
|
|
|
|
rtems_irq_connect_data ii = {
|
|
name: BSP_IRQ_I2C,
|
|
hdl: gt_i2c_intr,
|
|
on: noop,
|
|
off: noop,
|
|
isOn: inoop
|
|
};
|
|
|
|
rtems_semaphore_delete(sc->sc_sync);
|
|
|
|
return !BSP_remove_rtems_irq_handler(&ii);
|
|
}
|
|
|
|
#endif
|