/* $NetBSD: viapcib.c,v 1.15 2016/02/14 19:54:20 chs Exp $ */ /* $FreeBSD: src/sys/pci/viapm.c,v 1.10 2005/05/29 04:42:29 nyan Exp $ */ /*- * Copyright (c) 2005, 2006 Jared D. McNeill * All rights reserved. * * 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 the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR 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. */ /*- * Copyright (c) 2001 Alcove - Nicolas Souchu * All rights reserved. * * 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. * * THIS SOFTWARE IS PROVIDED BY THE 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 THE 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. */ #include __KERNEL_RCSID(0, "$NetBSD: viapcib.c,v 1.15 2016/02/14 19:54:20 chs Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include /*#define VIAPCIB_DEBUG*/ #ifdef VIAPCIB_DEBUG #define DPRINTF(x) printf x #else #define DPRINTF(x) #endif struct viapcib_softc { /* we call pcibattach(), which assumes softc starts like this: */ struct pcib_softc sc_pcib; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; struct i2c_controller sc_i2c; int sc_revision; kmutex_t sc_lock; }; static int viapcib_match(device_t, cfdata_t, void *); static void viapcib_attach(device_t, device_t, void *); static int viapcib_clear(struct viapcib_softc *); static int viapcib_busy(struct viapcib_softc *); #define viapcib_smbus_read(sc, o) \ bus_space_read_1((sc)->sc_iot, (sc)->sc_ioh, (o)) #define viapcib_smbus_write(sc, o, v) \ bus_space_write_1((sc)->sc_iot, (sc)->sc_ioh, (o), (v)) #define VIAPCIB_SMBUS_TIMEOUT 10000 static int viapcib_acquire_bus(void *, int); static void viapcib_release_bus(void *, int); static int viapcib_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, void *, size_t, int); /* SMBus operations */ static int viapcib_smbus_quick_write(void *, i2c_addr_t); static int viapcib_smbus_quick_read(void *, i2c_addr_t); static int viapcib_smbus_send_byte(void *, i2c_addr_t, uint8_t); static int viapcib_smbus_receive_byte(void *, i2c_addr_t, uint8_t *); static int viapcib_smbus_read_byte(void *, i2c_addr_t, uint8_t, int8_t *); static int viapcib_smbus_write_byte(void *, i2c_addr_t, uint8_t, int8_t); static int viapcib_smbus_read_word(void *, i2c_addr_t, uint8_t, int16_t *); static int viapcib_smbus_write_word(void *, i2c_addr_t, uint8_t, int16_t); static int viapcib_smbus_block_write(void *, i2c_addr_t, uint8_t, int, void *); static int viapcib_smbus_block_read(void *, i2c_addr_t, uint8_t, int, void *); /* XXX Should be moved to smbus layer */ #define SMB_MAXBLOCKSIZE 32 CFATTACH_DECL_NEW(viapcib, sizeof(struct viapcib_softc), viapcib_match, viapcib_attach, NULL, NULL); static int viapcib_match(device_t parent, cfdata_t match, void *opaque) { struct pci_attach_args *pa = opaque; if (PCI_VENDOR(pa->pa_id) != PCI_VENDOR_VIATECH) return 0; switch (PCI_PRODUCT(pa->pa_id)) { case PCI_PRODUCT_VIATECH_VT8235: case PCI_PRODUCT_VIATECH_VT8237: case PCI_PRODUCT_VIATECH_VT8237A_ISA: return 2; /* match above generic pcib(4) */ } return 0; } static void viapcib_attach(device_t parent, device_t self, void *opaque) { struct viapcib_softc *sc = device_private(self); struct pci_attach_args *pa = opaque; pcireg_t addr, val; /* XXX Only the 8235 is supported for now */ sc->sc_iot = pa->pa_iot; addr = pci_conf_read(pa->pa_pc, pa->pa_tag, SMB_BASE3); addr &= 0xfff0; if (bus_space_map(sc->sc_iot, addr, 8, 0, &sc->sc_ioh)) { printf(": failed to map SMBus I/O space\n"); addr = 0; goto core_pcib; } mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_NONE); val = pci_conf_read(pa->pa_pc, pa->pa_tag, SMB_HOST_CONFIG); if ((val & 0x10000) == 0) { printf(": SMBus is disabled\n"); addr = 0; /* XXX We can enable the SMBus here by writing 1 to * SMB_HOST_CONFIG, but do we want to? */ goto core_pcib; } #ifdef VIAPCIB_DEBUG switch (val & 0x0e) { case 8: printf(": interrupting at irq 9\n"); break; case 0: printf(": interrupting at SMI#\n"); break; default: printf(": interrupt misconfigured\n"); break; } #endif /* !VIAPCIB_DEBUG */ val = pci_conf_read(pa->pa_pc, pa->pa_tag, SMB_REVISION); sc->sc_revision = val >> 16; core_pcib: pcibattach(parent, self, opaque); if (addr != 0) { struct i2cbus_attach_args iba; uint8_t b; printf("%s: SMBus found at 0x%x (revision 0x%x)\n", device_xname(self), addr, sc->sc_revision); /* Disable slave function */ b = viapcib_smbus_read(sc, SMBSLVCNT); viapcib_smbus_write(sc, SMBSLVCNT, b & ~1); memset(&sc->sc_i2c, 0, sizeof(sc->sc_i2c)); memset(&iba, 0, sizeof(iba)); #ifdef I2C_TYPE_SMBUS iba.iba_type = I2C_TYPE_SMBUS; #endif iba.iba_tag = &sc->sc_i2c; iba.iba_tag->ic_cookie = (void *)sc; iba.iba_tag->ic_acquire_bus = viapcib_acquire_bus; iba.iba_tag->ic_release_bus = viapcib_release_bus; iba.iba_tag->ic_exec = viapcib_exec; config_found_ia(self, "i2cbus", &iba, iicbus_print); } } static int viapcib_wait(struct viapcib_softc *sc) { int rv, timeout; uint8_t val = 0; timeout = VIAPCIB_SMBUS_TIMEOUT; rv = 0; while (timeout--) { val = viapcib_smbus_read(sc, SMBHSTSTS); if (!(val & SMBHSTSTS_BUSY) && (val & SMBHSTSTS_INTR)) break; DELAY(10); } if (timeout == 0) rv = EBUSY; if ((val & SMBHSTSTS_FAILED) || (val & SMBHSTSTS_COLLISION) || (val & SMBHSTSTS_ERROR)) rv = EIO; viapcib_clear(sc); return rv; } static int viapcib_clear(struct viapcib_softc *sc) { viapcib_smbus_write(sc, SMBHSTSTS, (SMBHSTSTS_FAILED | SMBHSTSTS_COLLISION | SMBHSTSTS_ERROR | SMBHSTSTS_INTR)); DELAY(10); return 0; } static int viapcib_busy(struct viapcib_softc *sc) { uint8_t val; val = viapcib_smbus_read(sc, SMBHSTSTS); return (val & SMBHSTSTS_BUSY); } static int viapcib_acquire_bus(void *opaque, int flags) { struct viapcib_softc *sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_i2c_acquire_bus(%p, 0x%x)\n", opaque, flags)); mutex_enter(&sc->sc_lock); return 0; } static void viapcib_release_bus(void *opaque, int flags) { struct viapcib_softc *sc = (struct viapcib_softc *)opaque; mutex_exit(&sc->sc_lock); DPRINTF(("viapcib_i2c_release_bus(%p, 0x%x)\n", opaque, flags)); } static int viapcib_exec(void *opaque, i2c_op_t op, i2c_addr_t addr, const void *vcmd, size_t cmdlen, void *vbuf, size_t buflen, int flags) { struct viapcib_softc *sc; uint8_t cmd; int rv = -1; DPRINTF(("viapcib_exec(%p, 0x%x, 0x%x, %p, %d, %p, %d, 0x%x)\n", opaque, op, addr, vcmd, cmdlen, vbuf, buflen, flags)); sc = (struct viapcib_softc *)opaque; if (op != I2C_OP_READ_WITH_STOP && op != I2C_OP_WRITE_WITH_STOP) return -1; if (cmdlen > 0) cmd = *(uint8_t *)(__UNCONST(vcmd)); /* XXX */ switch (cmdlen) { case 0: switch (buflen) { case 0: /* SMBus quick read/write */ if (I2C_OP_READ_P(op)) rv = viapcib_smbus_quick_read(sc, addr); else rv = viapcib_smbus_quick_write(sc, addr); return rv; case 1: /* SMBus send/receive byte */ if (I2C_OP_READ_P(op)) rv = viapcib_smbus_send_byte(sc, addr, *(uint8_t *)vbuf); else rv = viapcib_smbus_receive_byte(sc, addr, (uint8_t *)vbuf); return rv; default: return -1; } case 1: switch (buflen) { case 0: return -1; case 1: /* SMBus read/write byte */ if (I2C_OP_READ_P(op)) rv = viapcib_smbus_read_byte(sc, addr, cmd, (uint8_t *)vbuf); else rv = viapcib_smbus_write_byte(sc, addr, cmd, *(uint8_t *)vbuf); return rv; case 2: /* SMBus read/write word */ if (I2C_OP_READ_P(op)) rv = viapcib_smbus_read_word(sc, addr, cmd, (uint16_t *)vbuf); else rv = viapcib_smbus_write_word(sc, addr, cmd, *(uint16_t *)vbuf); return rv; default: /* SMBus read/write block */ if (I2C_OP_READ_P(op)) rv = viapcib_smbus_block_read(sc, addr, cmd, buflen, vbuf); else rv = viapcib_smbus_block_write(sc, addr, cmd, buflen, vbuf); return rv; } } return -1; /* XXX ??? */ } static int viapcib_smbus_quick_write(void *opaque, i2c_addr_t slave) { struct viapcib_softc *sc; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_quick_write(%p, 0x%x)\n", opaque, slave)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, (slave & 0x7f) << 1); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_QUICK); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_QUICK | SMBHSTCNT_START); return viapcib_wait(sc); } static int viapcib_smbus_quick_read(void *opaque, i2c_addr_t slave) { struct viapcib_softc *sc; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_quick_read(%p, 0x%x)\n", opaque, slave)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, ((slave & 0x7f) << 1) | 1); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_QUICK); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_QUICK | SMBHSTCNT_START); return viapcib_wait(sc); } static int viapcib_smbus_send_byte(void *opaque, i2c_addr_t slave, uint8_t byte) { struct viapcib_softc *sc; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_send_byte(%p, 0x%x, 0x%x)\n", opaque, slave, byte)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, (slave & 0x7f) << 1); viapcib_smbus_write(sc, SMBHSTCMD, byte); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_SENDRECV); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_SENDRECV); return viapcib_wait(sc); } static int viapcib_smbus_receive_byte(void *opaque, i2c_addr_t slave, uint8_t *pbyte) { struct viapcib_softc *sc; int rv; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_receive_byte(%p, 0x%x, %p)\n", opaque, slave, pbyte)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, ((slave & 0x7f) << 1) | 1); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_SENDRECV); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_SENDRECV); rv = viapcib_wait(sc); if (rv == 0) *pbyte = viapcib_smbus_read(sc, SMBHSTDAT0); return rv; } static int viapcib_smbus_write_byte(void *opaque, i2c_addr_t slave, uint8_t cmd, int8_t byte) { struct viapcib_softc *sc; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_write_byte(%p, 0x%x, 0x%x, 0x%x)\n", opaque, slave, cmd, byte)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, (slave & 0x7f) << 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTDAT0, byte); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_BYTE); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_BYTE); return viapcib_wait(sc); } static int viapcib_smbus_read_byte(void *opaque, i2c_addr_t slave, uint8_t cmd, int8_t *pbyte) { struct viapcib_softc *sc; int rv; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_read_byte(%p, 0x%x, 0x%x, %p)\n", opaque, slave, cmd, pbyte)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, ((slave & 0x7f) << 1) | 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_BYTE); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_BYTE); rv = viapcib_wait(sc); if (rv == 0) *pbyte = viapcib_smbus_read(sc, SMBHSTDAT0); return rv; } static int viapcib_smbus_write_word(void *opaque, i2c_addr_t slave, uint8_t cmd, int16_t word) { struct viapcib_softc *sc; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_write_word(%p, 0x%x, 0x%x, 0x%x)\n", opaque, slave, cmd, word)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, (slave & 0x7f) << 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTDAT0, word & 0x00ff); viapcib_smbus_write(sc, SMBHSTDAT1, (word & 0xff00) >> 8); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_WORD); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_WORD); return viapcib_wait(sc); } static int viapcib_smbus_read_word(void *opaque, i2c_addr_t slave, uint8_t cmd, int16_t *pword) { struct viapcib_softc *sc; int rv; int8_t high, low; sc = (struct viapcib_softc *)opaque; DPRINTF(("viapcib_smbus_read_word(%p, 0x%x, 0x%x, %p)\n", opaque, slave, cmd, pword)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTADD, ((slave & 0x7f) << 1) | 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_WORD); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_WORD); rv = viapcib_wait(sc); if (rv == 0) { low = viapcib_smbus_read(sc, SMBHSTDAT0); high = viapcib_smbus_read(sc, SMBHSTDAT1); *pword = ((high & 0xff) << 8) | (low & 0xff); } return rv; } static int viapcib_smbus_block_write(void *opaque, i2c_addr_t slave, uint8_t cmd, int cnt, void *data) { struct viapcib_softc *sc; int8_t *buf; int remain, len, i; int rv; sc = (struct viapcib_softc *)opaque; buf = (int8_t *)data; rv = 0; DPRINTF(("viapcib_smbus_block_write(%p, 0x%x, 0x%x, %d, %p)\n", opaque, slave, cmd, cnt, data)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; remain = cnt; while (remain) { len = min(remain, SMB_MAXBLOCKSIZE); viapcib_smbus_write(sc, SMBHSTADD, (slave & 0x7f) << 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTDAT0, len); i = viapcib_smbus_read(sc, SMBHSTCNT); /* XXX FreeBSD does this, but it looks wrong */ for (i = 0; i < len; i++) { viapcib_smbus_write(sc, SMBBLKDAT, buf[cnt - remain + i]); } viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_BLOCK); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_BLOCK); if (viapcib_wait(sc)) return EBUSY; remain -= len; } return rv; } static int viapcib_smbus_block_read(void *opaque, i2c_addr_t slave, uint8_t cmd, int cnt, void *data) { struct viapcib_softc *sc; int8_t *buf; int remain, len, i; int rv; sc = (struct viapcib_softc *)opaque; buf = (int8_t *)data; rv = 0; DPRINTF(("viapcib_smbus_block_read(%p, 0x%x, 0x%x, %d, %p)\n", opaque, slave, cmd, cnt, data)); viapcib_clear(sc); if (viapcib_busy(sc)) return EBUSY; remain = cnt; while (remain) { viapcib_smbus_write(sc, SMBHSTADD, ((slave & 0x7f) << 1) | 1); viapcib_smbus_write(sc, SMBHSTCMD, cmd); viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_BLOCK); if (viapcib_wait(sc)) return EBUSY; viapcib_smbus_write(sc, SMBHSTCNT, SMBHSTCNT_START | SMBHSTCNT_BLOCK); if (viapcib_wait(sc)) return EBUSY; len = viapcib_smbus_read(sc, SMBHSTDAT0); i = viapcib_smbus_read(sc, SMBHSTCNT); len = min(len, remain); /* FreeBSD does this too... */ for (i = 0; i < len; i++) { buf[cnt - remain + i] = viapcib_smbus_read(sc, SMBBLKDAT); } remain -= len; } return rv; }