diff options
author | raj <raj@FreeBSD.org> | 2008-10-13 20:07:13 +0000 |
---|---|---|
committer | raj <raj@FreeBSD.org> | 2008-10-13 20:07:13 +0000 |
commit | 3226c137788c6c0c6b82bd1b88fd42a2a8ea4cb8 (patch) | |
tree | d96b4a1062d4a3a8f75bdb30e300bd1a50f23e4f /sys/arm/mv/twsi.c | |
parent | 000539b88803ce11f549bc8d24154dc9a6b1c386 (diff) | |
download | FreeBSD-src-3226c137788c6c0c6b82bd1b88fd42a2a8ea4cb8.zip FreeBSD-src-3226c137788c6c0c6b82bd1b88fd42a2a8ea4cb8.tar.gz |
Introduce basic support for Marvell families of system-on-chip ARM devices:
* Orion
- 88F5181
- 88F5182
- 88F5281
* Kirkwood
- 88F6281
* Discovery
- MV78100
The above families of SOCs are built around CPU cores compliant with ARMv5TE
instruction set architecture definition. They share a number of integrated
peripherals. This commit brings support for the following basic elements:
* GPIO
* Interrupt controller
* L1, L2 cache
* Timers, watchdog, RTC
* TWSI (I2C)
* UART
Other peripherals drivers will be introduced separately.
Reviewed by: imp, marcel, stass (Thanks guys!)
Obtained from: Marvell, Semihalf
Diffstat (limited to 'sys/arm/mv/twsi.c')
-rw-r--r-- | sys/arm/mv/twsi.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/sys/arm/mv/twsi.c b/sys/arm/mv/twsi.c new file mode 100644 index 0000000..15ec415 --- /dev/null +++ b/sys/arm/mv/twsi.c @@ -0,0 +1,523 @@ +/*- + * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. + * All rights reserved. + * + * Developed by Semihalf. + * + * 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. Neither the name of MARVELL nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY 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 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. + */ + +/* + * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell + * SoCs. Supports master operation only, and works in polling mode. + * + * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software + * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/resource.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <sys/rman.h> + +#include <sys/lock.h> +#include <sys/mutex.h> + +#include <dev/iicbus/iiconf.h> +#include <dev/iicbus/iicbus.h> +#include "iicbus_if.h" + +#define MV_TWSI_NAME "twsi" + +#define TWSI_SLAVE_ADDR 0x00 +#define TWSI_EXT_SLAVE_ADDR 0x10 +#define TWSI_DATA 0x04 + +#define TWSI_CONTROL 0x08 +#define TWSI_CONTROL_ACK (1 << 2) +#define TWSI_CONTROL_IFLG (1 << 3) +#define TWSI_CONTROL_STOP (1 << 4) +#define TWSI_CONTROL_START (1 << 5) +#define TWSI_CONTROL_TWSIEN (1 << 6) +#define TWSI_CONTROL_INTEN (1 << 7) + +#define TWSI_STATUS 0x0c +#define TWSI_STATUS_START 0x08 +#define TWSI_STATUS_RPTD_START 0x10 +#define TWSI_STATUS_ADDR_W_ACK 0x18 +#define TWSI_STATUS_DATA_WR_ACK 0x28 +#define TWSI_STATUS_ADDR_R_ACK 0x40 +#define TWSI_STATUS_DATA_RD_ACK 0x50 +#define TWSI_STATUS_DATA_RD_NOACK 0x58 + +#define TWSI_BAUD_RATE 0x0c +#define TWSI_BAUD_RATE_94DOT3 0x53 /* N=3, M=10 */ +#define TWSI_BAUD_RATE_74DOT1 0x6b /* N=3, M=13 */ +#define TWSI_BAUD_RATE_51DOT8 0x4c /* N=4, M=9 */ +#define TWSI_BAUD_RATE_99DOT7 0x66 /* N=6, M=12 */ + +#define TWSI_SOFT_RESET 0x1c + +#define TWSI_DEBUG +#undef TWSI_DEBUG + +#ifdef TWSI_DEBUG +#define debugf(fmt, args...) do { printf("%s(): ", __func__); printf(fmt,##args); } while (0) +#else +#define debugf(fmt, args...) +#endif + +struct mv_twsi_softc { + device_t dev; + struct resource *res[1]; /* SYS_RES_MEMORY */ + struct mtx mutex; + device_t iicbus; +}; + +static int mv_twsi_probe(device_t); +static int mv_twsi_attach(device_t); +static int mv_twsi_detach(device_t); + +static int mv_twsi_reset(device_t dev, u_char speed, u_char addr, + u_char *oldaddr); +static int mv_twsi_repeated_start(device_t dev, u_char slave, int timeout); +static int mv_twsi_start(device_t dev, u_char slave, int timeout); +static int mv_twsi_stop(device_t dev); +static int mv_twsi_read(device_t dev, char *buf, int len, int *read, int last, + int delay); +static int mv_twsi_write(device_t dev, char *buf, int len, int *sent, + int timeout); + +static struct resource_spec res_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +static device_method_t mv_twsi_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, mv_twsi_probe), + DEVMETHOD(device_attach, mv_twsi_attach), + DEVMETHOD(device_detach, mv_twsi_detach), + + /* iicbus interface */ + DEVMETHOD(iicbus_callback, iicbus_null_callback), + DEVMETHOD(iicbus_repeated_start, mv_twsi_repeated_start), + DEVMETHOD(iicbus_start, mv_twsi_start), + DEVMETHOD(iicbus_stop, mv_twsi_stop), + DEVMETHOD(iicbus_write, mv_twsi_write), + DEVMETHOD(iicbus_read, mv_twsi_read), + DEVMETHOD(iicbus_reset, mv_twsi_reset), + DEVMETHOD(iicbus_transfer, iicbus_transfer_gen), + { 0, 0 } +}; + +static devclass_t mv_twsi_devclass; + +static driver_t mv_twsi_driver = { + MV_TWSI_NAME, + mv_twsi_methods, + sizeof(struct mv_twsi_softc), +}; + +DRIVER_MODULE(twsi, mbus, mv_twsi_driver, mv_twsi_devclass, 0, 0); +DRIVER_MODULE(iicbus, twsi, iicbus_driver, iicbus_devclass, 0, 0); +MODULE_DEPEND(twsi, iicbus, 1, 1, 1); + +static __inline uint32_t +TWSI_READ(struct mv_twsi_softc *sc, bus_size_t off) +{ + + return (bus_read_4(sc->res[0], off)); +} + +static __inline void +TWSI_WRITE(struct mv_twsi_softc *sc, bus_size_t off, uint32_t val) +{ + + bus_write_4(sc->res[0], off, val); +} + +static __inline void +twsi_control_clear(struct mv_twsi_softc *sc, uint32_t mask) +{ + uint32_t val; + + val = TWSI_READ(sc, TWSI_CONTROL); + val &= ~mask; + TWSI_WRITE(sc, TWSI_CONTROL, val); +} + +static __inline void +twsi_control_set(struct mv_twsi_softc *sc, uint32_t mask) +{ + uint32_t val; + + val = TWSI_READ(sc, TWSI_CONTROL); + val |= mask; + TWSI_WRITE(sc, TWSI_CONTROL, val); +} + +static __inline void +twsi_clear_iflg(struct mv_twsi_softc *sc) +{ + + DELAY(1000); + twsi_control_clear(sc, TWSI_CONTROL_IFLG); + DELAY(1000); +} + + +/* + * timeout given in us + * returns + * 0 on sucessfull mask change + * non-zero on timeout + */ +static int +twsi_poll_ctrl(struct mv_twsi_softc *sc, int timeout, uint32_t mask) +{ + + timeout /= 10; + while (!(TWSI_READ(sc, TWSI_CONTROL) & mask)) { + DELAY(10); + if (--timeout < 0) + return (timeout); + } + return (0); +} + + +/* + * 'timeout' is given in us. Note also that timeout handling is not exact -- + * twsi_locked_start() total wait can be more than 2 x timeout + * (twsi_poll_ctrl() is called twice). 'mask' can be either TWSI_STATUS_START + * or TWSI_STATUS_RPTD_START + */ +static int +twsi_locked_start(device_t dev, struct mv_twsi_softc *sc, int32_t mask, + u_char slave, int timeout) +{ + int read_access, iflg_set = 0; + uint32_t status; + + mtx_assert(&sc->mutex, MA_OWNED); + + if (mask == TWSI_STATUS_RPTD_START) + /* read IFLG to know if it should be cleared later; from NBSD */ + iflg_set = TWSI_READ(sc, TWSI_CONTROL) & TWSI_CONTROL_IFLG; + + twsi_control_set(sc, TWSI_CONTROL_START); + + if (mask == TWSI_STATUS_RPTD_START && iflg_set) { + debugf("IFLG set, clearing\n"); + twsi_clear_iflg(sc); + } + + /* + * Without this delay we timeout checking IFLG if the timeout is 0. + * NBSD driver always waits here too. + */ + DELAY(1000); + + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout sending %sSTART condition\n", + mask == TWSI_STATUS_START ? "" : "repeated "); + return (IIC_ETIMEOUT); + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != mask) { + debugf("wrong status (%02x) after sending %sSTART condition\n", + status, mask == TWSI_STATUS_START ? "" : "repeated "); + return (IIC_ESTATUS); + } + + TWSI_WRITE(sc, TWSI_DATA, slave); + DELAY(1000); + twsi_clear_iflg(sc); + + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout sending slave address\n"); + return (IIC_ETIMEOUT); + } + + read_access = (slave & 0x1) ? 1 : 0; + status = TWSI_READ(sc, TWSI_STATUS); + if (status != (read_access ? + TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { + debugf("no ACK (status: %02x) after sending slave address\n", + status); + return (IIC_ENOACK); + } + + return (IIC_NOERR); +} + +static int +mv_twsi_probe(device_t dev) +{ + + device_set_desc(dev, "Marvell Integrated I2C Bus Controller"); + return (BUS_PROBE_DEFAULT); +} + +static int +mv_twsi_attach(device_t dev) +{ + struct mv_twsi_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + + mtx_init(&sc->mutex, device_get_nameunit(dev), MV_TWSI_NAME, MTX_DEF); + + /* Allocate IO resources */ + if (bus_alloc_resources(dev, res_spec, sc->res)) { + device_printf(dev, "could not allocate resources\n"); + mv_twsi_detach(dev); + return (ENXIO); + } + + sc->iicbus = device_add_child(dev, "iicbus", -1); + if (sc->iicbus == NULL) { + device_printf(dev, "could not add iicbus child\n"); + mv_twsi_detach(dev); + return (ENXIO); + } + + bus_generic_attach(dev); + return (0); +} + +static int +mv_twsi_detach(device_t dev) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + if ((rv = bus_generic_detach(dev)) != 0) + return (rv); + + if (sc->iicbus != NULL) + if ((rv = device_delete_child(dev, sc->iicbus)) != 0) + return (rv); + + bus_release_resources(dev, res_spec, sc->res); + + mtx_destroy(&sc->mutex); + return (0); +} + +/* + * Only slave mode supported, disregard [old]addr + */ +static int +mv_twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) +{ + struct mv_twsi_softc *sc; + uint32_t baud_rate; + + sc = device_get_softc(dev); + + switch (speed) { + case IIC_SLOW: + baud_rate = TWSI_BAUD_RATE_51DOT8; + break; + case IIC_FAST: + baud_rate = TWSI_BAUD_RATE_51DOT8; + break; + case IIC_UNKNOWN: + case IIC_FASTEST: + default: + baud_rate = TWSI_BAUD_RATE_99DOT7; + break; + } + + mtx_lock(&sc->mutex); + TWSI_WRITE(sc, TWSI_SOFT_RESET, 0x0); + DELAY(2000); + TWSI_WRITE(sc, TWSI_BAUD_RATE, baud_rate); + TWSI_WRITE(sc, TWSI_CONTROL, TWSI_CONTROL_TWSIEN | TWSI_CONTROL_ACK); + DELAY(1000); + mtx_unlock(&sc->mutex); + + return (0); +} + +/* + * timeout is given in us + */ +static int +mv_twsi_repeated_start(device_t dev, u_char slave, int timeout) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, + timeout); + mtx_unlock(&sc->mutex); + + if (rv) { + mv_twsi_stop(dev); + return (rv); + } else + return (IIC_NOERR); +} + +/* + * timeout is given in us + */ +static int +mv_twsi_start(device_t dev, u_char slave, int timeout) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); + mtx_unlock(&sc->mutex); + + if (rv) { + mv_twsi_stop(dev); + return (rv); + } else + return (IIC_NOERR); +} + +static int +mv_twsi_stop(device_t dev) +{ + struct mv_twsi_softc *sc; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + twsi_control_set(sc, TWSI_CONTROL_STOP); + DELAY(1000); + twsi_clear_iflg(sc); + mtx_unlock(&sc->mutex); + + return (IIC_NOERR); +} + +static int +mv_twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) +{ + struct mv_twsi_softc *sc; + uint32_t status; + int last_byte, rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + *read = 0; + while (*read < len) { + /* + * Check if we are reading last byte of the last buffer, + * do not send ACK then, per I2C specs + */ + last_byte = ((*read == len - 1) && last) ? 1 : 0; + if (last_byte) + twsi_control_clear(sc, TWSI_CONTROL_ACK); + else + twsi_control_set(sc, TWSI_CONTROL_ACK); + + DELAY (1000); + twsi_clear_iflg(sc); + + if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { + debugf("timeout reading data\n"); + rv = IIC_ETIMEOUT; + goto out; + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != (last_byte ? + TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { + debugf("wrong status (%02x) while reading\n", status); + rv = IIC_ESTATUS; + goto out; + } + + *buf++ = TWSI_READ(sc, TWSI_DATA); + (*read)++; + } + rv = IIC_NOERR; +out: + mtx_unlock(&sc->mutex); + return (rv); +} + +static int +mv_twsi_write(device_t dev, char *buf, int len, int *sent, int timeout) +{ + struct mv_twsi_softc *sc; + uint32_t status; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + *sent = 0; + while (*sent < len) { + TWSI_WRITE(sc, TWSI_DATA, *buf++); + + twsi_clear_iflg(sc); + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout writing data\n"); + rv = IIC_ETIMEOUT; + goto out; + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != TWSI_STATUS_DATA_WR_ACK) { + debugf("wrong status (%02x) while writing\n", status); + rv = IIC_ESTATUS; + goto out; + } + (*sent)++; + } + rv = IIC_NOERR; +out: + mtx_unlock(&sc->mutex); + return (rv); +} |