/* $OpenBSD: sociic.c,v 1.2 2009/09/06 20:09:34 kettenis Exp $ */ /* * Copyright (c) 2008 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /* * Driver for the I2C interface on the MPC8349E processors. */ #include <sys/param.h> #include <sys/systm.h> #include <sys/device.h> #include <sys/rwlock.h> #include <machine/autoconf.h> #include <machine/bus.h> #include <dev/ofw/openfirm.h> #include <dev/i2c/i2cvar.h> #define I2C_ADR 0x00 /* Address Register */ #define I2C_FDR 0x04 /* Fequency Divider Register */ #define I2C_CR 0x08 /* Control Register */ #define I2C_CR_MEN 0x80 #define I2C_CR_MIEN 0x40 #define I2C_CR_MSTA 0x20 #define I2C_CR_MTX 0x10 #define I2C_CR_TXAK 0x08 #define I2C_CR_RSTA 0x04 #define I2C_CR_BCST 0x01 #define I2C_SR 0x0c /* Status Register */ #define I2C_SR_MCF 0x80 #define I2C_SR_MAAS 0x40 #define I2C_SR_MBB 0x20 #define I2C_SR_MAL 0x10 #define I2C_SR_BCSTM 0x08 #define I2C_SR_SRW 0x04 #define I2C_SR_MIF 0x02 #define I2C_SR_RXAK 0x01 #define I2C_DR 0x10 /* Data Register */ #define I2C_DFSRR 0x14 /* Digital Filter Sampling Rate Register */ struct sociic_softc { struct device sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; struct i2c_controller sc_i2c; struct rwlock sc_lock; }; int sociic_match(struct device *, void *, void *); void sociic_attach(struct device *, struct device *, void *); struct cfattach sociic_ca = { sizeof(struct sociic_softc), sociic_match, sociic_attach }; struct cfdriver sociic_cd = { NULL, "sociic", DV_DULL }; void sociic_write(struct sociic_softc *, bus_addr_t, uint8_t); uint8_t sociic_read(struct sociic_softc *, bus_addr_t); int sociic_wait(struct sociic_softc *, int); int sociic_wait_bus(struct sociic_softc *); int sociic_i2c_acquire_bus(void *, int); void sociic_i2c_release_bus(void *, int); int sociic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, void *, size_t, int); int sociic_match(struct device *parent, void *cfdata, void *aux) { struct obio_attach_args *oa = aux; char buf[32]; if (OF_getprop(oa->oa_node, "compatible", buf, sizeof(buf)) <= 0 || strcmp(buf, "fsl-i2c") != 0) return (0); return (1); } void sociic_attach(struct device *parent, struct device *self, void *aux) { struct sociic_softc *sc = (void *)self; struct obio_attach_args *oa = aux; struct i2cbus_attach_args iba; sc->sc_iot = oa->oa_iot; if (bus_space_map(sc->sc_iot, oa->oa_offset, 24, 0, &sc->sc_ioh)) { printf(": can't map registers\n"); return; } printf("\n"); rw_init(&sc->sc_lock, "iiclk"); sc->sc_i2c.ic_cookie = sc; sc->sc_i2c.ic_acquire_bus = sociic_i2c_acquire_bus; sc->sc_i2c.ic_release_bus = sociic_i2c_release_bus; sc->sc_i2c.ic_exec = sociic_i2c_exec; bzero(&iba, sizeof iba); iba.iba_name = "iic"; iba.iba_tag = &sc->sc_i2c; config_found(&sc->sc_dev, &iba, iicbus_print); } void sociic_write(struct sociic_softc *sc, bus_addr_t addr, uint8_t data) { bus_space_write_1(sc->sc_iot, sc->sc_ioh, addr, data); } uint8_t sociic_read(struct sociic_softc *sc, bus_addr_t addr) { return (bus_space_read_1(sc->sc_iot, sc->sc_ioh, addr)); } int sociic_wait(struct sociic_softc *sc, int flags) { uint8_t sr; int i; for (i = 0; i < 1000; i++) { sr = sociic_read(sc, I2C_SR); if (sr & I2C_SR_MIF) { sociic_write(sc, I2C_SR, 0); if (sr & I2C_SR_MAL) return (EIO); if ((sr & I2C_SR_MCF) == 0) return (EIO); if ((flags & I2C_F_READ) == 0 && (sr & I2C_SR_RXAK)) return (EIO); return (0); } delay(100); } return (ETIMEDOUT); } int sociic_wait_bus(struct sociic_softc *sc) { uint8_t sr; int i; for (i = 0; i < 1000; i++) { sr = sociic_read(sc, I2C_SR); if ((sr & I2C_SR_MBB) == 0) return (0); delay(1000); } return (ETIMEDOUT); } int sociic_i2c_acquire_bus(void *arg, int flags) { struct sociic_softc *sc = arg; if (cold || (flags & I2C_F_POLL)) return (0); return (rw_enter(&sc->sc_lock, RW_WRITE | RW_INTR)); } void sociic_i2c_release_bus(void *arg, int flags) { struct sociic_softc *sc = arg; if (cold || (flags & I2C_F_POLL)) return; rw_exit(&sc->sc_lock); } int sociic_i2c_exec(void *arg, i2c_op_t op, i2c_addr_t addr, const void *vcmdbuf, size_t cmdlen, void *vbuf, size_t buflen, int flags) { struct sociic_softc *sc = arg; const uint8_t *cmdbuf = vcmdbuf; uint8_t *buf = vbuf; int err = 0; size_t len; uint8_t val; /* Clear the bus. */ sociic_write(sc, I2C_SR, 0); sociic_write(sc, I2C_CR, I2C_CR_MEN); err = sociic_wait_bus(sc); if (err) return (err); if (cmdlen > 0) { sociic_write(sc, I2C_CR, I2C_CR_MEN|I2C_CR_MSTA|I2C_CR_MTX); sociic_write(sc, I2C_DR, addr << 1); err = sociic_wait(sc, I2C_F_WRITE); if (err) goto out; len = cmdlen; while (len--) { sociic_write(sc, I2C_DR, *cmdbuf++); err = sociic_wait(sc, I2C_F_WRITE); if (err) goto out; } } if (I2C_OP_READ_P(op) && buflen > 0) { /* RESTART if we did write a command above. */ val = I2C_CR_MEN|I2C_CR_MSTA|I2C_CR_MTX; if (cmdlen > 0) val |= I2C_CR_RSTA; sociic_write(sc, I2C_CR, val); sociic_write(sc, I2C_DR, (addr << 1) | 1); err = sociic_wait(sc, I2C_F_WRITE); if (err) goto out; /* NACK if we're only sending one byte. */ val = I2C_CR_MEN|I2C_CR_MSTA; if (buflen == 1) val |= I2C_CR_TXAK; sociic_write(sc, I2C_CR, val); /* Dummy read. */ sociic_read(sc, I2C_DR); len = buflen; while (len--) { err = sociic_wait(sc, I2C_F_READ); if (err) goto out; /* NACK on last byte. */ if (len == 1) sociic_write(sc, I2C_CR, I2C_CR_MEN|I2C_CR_MSTA|I2C_CR_TXAK); /* STOP after last byte. */ if (len == 0) sociic_write(sc, I2C_CR, I2C_CR_MEN|I2C_CR_TXAK); *buf++ = sociic_read(sc, I2C_DR); } } if (I2C_OP_WRITE_P(op) && cmdlen == 0 && buflen > 0) { /* START if we didn't write a command. */ sociic_write(sc, I2C_CR, I2C_CR_MEN|I2C_CR_MSTA|I2C_CR_MTX); sociic_write(sc, I2C_DR, addr << 1); err = sociic_wait(sc, I2C_F_WRITE); if (err) goto out; } if (I2C_OP_WRITE_P(op) && buflen > 0) { len = buflen; while (len--) { sociic_write(sc, I2C_DR, *buf++); err = sociic_wait(sc, I2C_F_WRITE); if (err) goto out; } } out: /* STOP if we're still holding the bus. */ sociic_write(sc, I2C_CR, I2C_CR_MEN); return (err); }