diff options
author | Alexander Yurchenko <grange@cvs.openbsd.org> | 2004-05-23 17:41:10 +0000 |
---|---|---|
committer | Alexander Yurchenko <grange@cvs.openbsd.org> | 2004-05-23 17:41:10 +0000 |
commit | c3af576a7b133b4c2046eef9ddf14925c38ba25c (patch) | |
tree | 0cb54c1fdc2a67598086c8c20ba6195912df6a4b /sys/dev/isa/gscsio.c | |
parent | 71a75fdbc0855c8da85afd759f3d28e3e6a3c160 (diff) |
gscsio(4) -- National Semiconductor Geode SC1100 Super I/O driver.
For now it only supports I2C-compatible ACCESS.bus interface,
so we use it as an i2c master controller.
ACCESS.bus interface is enabled and used on the PC Engines WRAP.1C
board.
Diffstat (limited to 'sys/dev/isa/gscsio.c')
-rw-r--r-- | sys/dev/isa/gscsio.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/sys/dev/isa/gscsio.c b/sys/dev/isa/gscsio.c new file mode 100644 index 00000000000..1a448892af6 --- /dev/null +++ b/sys/dev/isa/gscsio.c @@ -0,0 +1,448 @@ +/* $OpenBSD: gscsio.c,v 1.1 2004/05/23 17:41:09 grange Exp $ */ +/* + * Copyright (c) 2004 Alexander Yurchenko <grange@openbsd.org> + * + * 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. + */ + +/* + * National Semiconductor Geode SC1100 Super I/O. + * Only ACCESS.bus logical device is supported. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kernel.h> +#include <sys/lock.h> +#include <sys/proc.h> + +#include <machine/bus.h> + +#include <dev/i2c/i2cvar.h> + +#include <dev/isa/isareg.h> +#include <dev/isa/isavar.h> + +#include <dev/isa/gscsioreg.h> + +struct gscsio_softc { + struct device sc_dev; + + bus_space_tag_t sc_iot; + bus_space_handle_t sc_ioh; + + int sc_ld_en[GSCSIO_LDNUM]; + bus_space_handle_t sc_ld_ioh0[GSCSIO_LDNUM]; + bus_space_handle_t sc_ld_ioh1[GSCSIO_LDNUM]; + + /* ACCESS.bus */ + struct gscsio_acb { + void *sc; + bus_space_handle_t ioh; + struct lock buslock; + } sc_acb[2]; + struct i2c_controller sc_acb1_tag; + struct i2c_controller sc_acb2_tag; +}; + +/* Supported logical devices description */ +static const struct { + const char *ld_name; + int ld_num; + int ld_iosize0; + int ld_iosize1; +} gscsio_ld[] = { + { "ACB1", GSCSIO_LDN_ACB1, 6, 0 }, + { "ACB2", GSCSIO_LDN_ACB2, 6, 0 }, +}; + +int gscsio_probe(struct device *, void *, void *); +void gscsio_attach(struct device *, struct device *, void *); + +void gscsio_acb_init(struct gscsio_acb *, i2c_tag_t); +int gscsio_acb_wait(struct gscsio_acb *, int, int); +void gscsio_acb_reset(struct gscsio_acb *acb); + +int gscsio_acb_acquire_bus(void *, int); +void gscsio_acb_release_bus(void *, int); +int gscsio_acb_send_start(void *, int); +int gscsio_acb_send_stop(void *, int); +int gscsio_acb_initiate_xfer(void *, uint16_t, int); +int gscsio_acb_read_byte(void *, uint8_t *, int); +int gscsio_acb_write_byte(void *, uint8_t, int); + +struct cfattach gscsio_ca = { + sizeof(struct gscsio_softc), + gscsio_probe, + gscsio_attach +}; + +struct cfdriver gscsio_cd = { + NULL, "gscsio", DV_DULL +}; + +#define ACB_READ(reg) \ + bus_space_read_1(sc->sc_iot, acb->ioh, (reg)) +#define ACB_WRITE(reg, val) \ + bus_space_write_1(sc->sc_iot, acb->ioh, (reg), (val)) + +static __inline u_int8_t +idxread(bus_space_tag_t iot, bus_space_handle_t ioh, int idx) +{ + bus_space_write_1(iot, ioh, GSCSIO_IDX, idx); + + return (bus_space_read_1(iot, ioh, GSCSIO_DAT)); +} + +static __inline void +idxwrite(bus_space_tag_t iot, bus_space_handle_t ioh, int idx, u_int8_t data) +{ + bus_space_write_1(iot, ioh, GSCSIO_IDX, idx); + bus_space_write_1(iot, ioh, GSCSIO_DAT, data); +} + +static int +ioprobe(bus_space_tag_t iot, int base) +{ + bus_space_handle_t ioh; + int rv = 0; + + if (bus_space_map(iot, base, GSCSIO_IOSIZE, 0, &ioh)) + return (0); + if (idxread(iot, ioh, GSCSIO_ID) == GSCSIO_ID_SC1100) + rv = 1; + bus_space_unmap(iot, ioh, GSCSIO_IOSIZE); + + return (rv); +} + +int +gscsio_probe(struct device *parent, void *match, void *aux) +{ + struct isa_attach_args *ia = aux; + int iobase; + + iobase = GSCSIO_IOBASE1; + if (ioprobe(ia->ia_iot, iobase)) + goto found; + iobase = GSCSIO_IOBASE2; + if (ioprobe(ia->ia_iot, iobase)) + goto found; + + return (0); + +found: + ia->ipa_nio = 1; + ia->ipa_io[0].base = iobase; + ia->ipa_io[0].length = GSCSIO_IOSIZE; + ia->ipa_nmem = 0; + ia->ipa_nirq = 0; + ia->ipa_ndrq = 0; + + return (1); +} + +void +gscsio_attach(struct device *parent, struct device *self, void *aux) +{ + struct gscsio_softc *sc = (void *)self; + struct isa_attach_args *ia = aux; + int i; + int iobase; + + sc->sc_iot = ia->ia_iot; + if (bus_space_map(sc->sc_iot, ia->ipa_io[0].base, GSCSIO_IOSIZE, + 0, &sc->sc_ioh)) { + printf(": can't map I/O space\n"); + return; + } + printf(": SC1100 SIO rev %d:", + idxread(sc->sc_iot, sc->sc_ioh, GSCSIO_REV)); + + /* Configure all supported logical devices */ + for (i = 0; i < sizeof (gscsio_ld) / sizeof(gscsio_ld[0]); i++) { + sc->sc_ld_en[gscsio_ld[i].ld_num] = 0; + + /* Select the device and check if it's activated */ + idxwrite(sc->sc_iot, sc->sc_ioh, GSCSIO_LDN, + gscsio_ld[i].ld_num); + if ((idxread(sc->sc_iot, sc->sc_ioh, GSCSIO_ACT) & + GSCSIO_ACT_EN) == 0) + continue; + + /* Map I/O space 0 if necessary */ + if (gscsio_ld[i].ld_iosize0 != 0) { + iobase = idxread(sc->sc_iot, sc->sc_ioh, + GSCSIO_IO0_MSB); + iobase <<= 8; + iobase |= idxread(sc->sc_iot, sc->sc_ioh, + GSCSIO_IO0_LSB); + if (bus_space_map(sc->sc_iot, iobase, + gscsio_ld[i].ld_iosize0, 0, + &sc->sc_ld_ioh0[gscsio_ld[i].ld_num])) + continue; + } + + /* Map I/O space 1 if necessary */ + if (gscsio_ld[i].ld_iosize1 != 0) { + iobase = idxread(sc->sc_iot, sc->sc_ioh, + GSCSIO_IO1_MSB); + iobase <<= 8; + iobase |= idxread(sc->sc_iot, sc->sc_ioh, + GSCSIO_IO1_LSB); + if (bus_space_map(sc->sc_iot, iobase, + gscsio_ld[i].ld_iosize1, 0, + &sc->sc_ld_ioh0[gscsio_ld[i].ld_num])) { + bus_space_unmap(sc->sc_iot, + sc->sc_ld_ioh0[gscsio_ld[i].ld_num], + gscsio_ld[i].ld_iosize0); + continue; + } + } + + sc->sc_ld_en[gscsio_ld[i].ld_num] = 1; + printf(" %s", gscsio_ld[i].ld_name); + } + printf("\n"); + + /* Initialize ACCESS.bus 1 */ + if (sc->sc_ld_en[GSCSIO_LDN_ACB1]) { + sc->sc_acb[0].sc = sc; + sc->sc_acb[0].ioh = sc->sc_ld_ioh0[GSCSIO_LDN_ACB1]; + gscsio_acb_init(&sc->sc_acb[0], &sc->sc_acb1_tag); + } + + /* Initialize ACCESS.bus 2 */ + if (sc->sc_ld_en[GSCSIO_LDN_ACB2]) { + sc->sc_acb[1].sc = sc; + sc->sc_acb[1].ioh = sc->sc_ld_ioh0[GSCSIO_LDN_ACB2]; + gscsio_acb_init(&sc->sc_acb[1], &sc->sc_acb2_tag); + } +} + +void +gscsio_acb_init(struct gscsio_acb *acb, i2c_tag_t tag) +{ + struct gscsio_softc *sc = acb->sc; + struct i2cbus_attach_args iba; + + /* Enable ACB and configure clock frequency */ + ACB_WRITE(GSCSIO_ACB_CTL2, GSCSIO_ACB_CTL2_EN | + (GSCSIO_ACB_FREQ << GSCSIO_ACB_CTL2_FREQ_SHIFT)); + + /* Select polling mode */ + ACB_WRITE(GSCSIO_ACB_CTL1, ACB_READ(GSCSIO_ACB_CTL1) & + ~GSCSIO_ACB_CTL1_INTEN); + + /* Disable slave address */ + ACB_WRITE(GSCSIO_ACB_ADDR, ACB_READ(GSCSIO_ACB_ADDR) & + ~GSCSIO_ACB_ADDR_SAEN); + + /* Attach I2C framework */ + tag->ic_cookie = acb; + tag->ic_acquire_bus = gscsio_acb_acquire_bus; + tag->ic_release_bus = gscsio_acb_release_bus; + tag->ic_send_start = gscsio_acb_send_start; + tag->ic_send_stop = gscsio_acb_send_stop; + tag->ic_initiate_xfer = gscsio_acb_initiate_xfer; + tag->ic_read_byte = gscsio_acb_read_byte; + tag->ic_write_byte = gscsio_acb_write_byte; + + iba.iba_name = "iic"; + iba.iba_tag = tag; + config_found(&sc->sc_dev, &iba, iicbus_print); +} + +int +gscsio_acb_wait(struct gscsio_acb *acb, int bits, int flags) +{ + struct gscsio_softc *sc = acb->sc; + u_int8_t st; + int i; + + for (i = 0; i < 100; i++) { + st = ACB_READ(GSCSIO_ACB_ST); + if (st & GSCSIO_ACB_ST_BER) { + printf("%s: bus error, flags=0x%x\n", + sc->sc_dev.dv_xname, flags); + gscsio_acb_reset(acb); + return (EIO); + } + if (st & GSCSIO_ACB_ST_NEGACK) { + printf("%s: negative ack, flags=0x%x\n", + sc->sc_dev.dv_xname, flags); + gscsio_acb_reset(acb); + return (EIO); + } + if ((st & bits) == bits) + break; + delay(10); + } + if ((st & bits) != bits) { + printf("%s: timeout, flags=0x%x\n", + sc->sc_dev.dv_xname, flags); + gscsio_acb_reset(acb); + return (ETIMEDOUT); + } + + return (0); +} + +void +gscsio_acb_reset(struct gscsio_acb *acb) +{ + struct gscsio_softc *sc = acb->sc; + u_int8_t st, ctl; + + /* Clear MASTER, NEGACK and BER */ + st = ACB_READ(GSCSIO_ACB_ST); + st |= GSCSIO_ACB_ST_MASTER | GSCSIO_ACB_ST_NEGACK | GSCSIO_ACB_ST_BER; + ACB_WRITE(GSCSIO_ACB_ST, st); + + /* Disable and re-enable ACB */ + ACB_WRITE(GSCSIO_ACB_CTL2, 0); + ACB_WRITE(GSCSIO_ACB_CTL2, GSCSIO_ACB_CTL2_EN | + (GSCSIO_ACB_FREQ << GSCSIO_ACB_CTL2_FREQ_SHIFT)); + + /* Send stop */ + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_STOP; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); +} + +int +gscsio_acb_acquire_bus(void *cookie, int flags) +{ + struct gscsio_acb *acb = cookie; + + if (flags & I2C_F_POLL) + return (0); + + return (lockmgr(&acb->buslock, LK_EXCLUSIVE, NULL, curproc)); +} + +void +gscsio_acb_release_bus(void *cookie, int flags) +{ + struct gscsio_acb *acb = cookie; + + if (flags & I2C_F_POLL) + return; + + lockmgr(&acb->buslock, LK_RELEASE, NULL, curproc); +} + +int +gscsio_acb_send_start(void *cookie, int flags) +{ + struct gscsio_acb *acb = cookie; + struct gscsio_softc *sc = acb->sc; + u_int8_t ctl; + + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_START; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); + + return (0); +} + +int +gscsio_acb_send_stop(void *cookie, int flags) +{ + struct gscsio_acb *acb = cookie; + struct gscsio_softc *sc = acb->sc; + u_int8_t ctl; + + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_STOP; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); + + return (0); +} + +int +gscsio_acb_initiate_xfer(void *cookie, uint16_t addr, int flags) +{ + struct gscsio_acb *acb = cookie; + struct gscsio_softc *sc = acb->sc; + u_int8_t ctl; + int dir; + int error; + + /* Issue start condition */ + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_START; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); + + /* Wait for bus mastership */ + if ((error = gscsio_acb_wait(acb, + GSCSIO_ACB_ST_MASTER | GSCSIO_ACB_ST_SDAST, flags))) + return (error); + + /* Send address byte */ + dir = (flags & I2C_F_READ ? 1 : 0); + ACB_WRITE(GSCSIO_ACB_SDA, (addr << 1) | dir); + + return (0); +} + +int +gscsio_acb_read_byte(void *cookie, uint8_t *bytep, int flags) +{ + struct gscsio_acb *acb = cookie; + struct gscsio_softc *sc = acb->sc; + u_int8_t ctl; + int error; + + /* Wait for the bus to be ready */ + if ((error = gscsio_acb_wait(acb, GSCSIO_ACB_ST_SDAST, flags))) + return (error); + + /* Acknowledge the last byte */ + if (flags & I2C_F_LAST) { + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_ACK; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); + } + + /* Read data byte */ + *bytep = ACB_READ(GSCSIO_ACB_SDA); + + return (0); +} + +int +gscsio_acb_write_byte(void *cookie, uint8_t byte, int flags) +{ + struct gscsio_acb *acb = cookie; + struct gscsio_softc *sc = acb->sc; + u_int8_t ctl; + int error; + + /* Wait for the bus to be ready */ + if ((error = gscsio_acb_wait(acb, GSCSIO_ACB_ST_SDAST, flags))) + return (error); + + /* Send stop after the last byte */ + if (flags & I2C_F_STOP) { + ctl = ACB_READ(GSCSIO_ACB_CTL1); + ctl |= GSCSIO_ACB_CTL1_STOP; + ACB_WRITE(GSCSIO_ACB_CTL1, ctl); + } + + /* Write data byte */ + ACB_WRITE(GSCSIO_ACB_SDA, byte); + + return (0); +} |