summaryrefslogtreecommitdiff
path: root/sys/dev/pci/nviic.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/dev/pci/nviic.c')
-rw-r--r--sys/dev/pci/nviic.c289
1 files changed, 289 insertions, 0 deletions
diff --git a/sys/dev/pci/nviic.c b/sys/dev/pci/nviic.c
new file mode 100644
index 00000000000..bd81b00d0d9
--- /dev/null
+++ b/sys/dev/pci/nviic.c
@@ -0,0 +1,289 @@
+/* $OpenBSD: nviic.c,v 1.1 2006/01/04 00:40:08 dlg Exp $ */
+
+/*
+ * Copyright (c) 2005 David Gwynne <dlg@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.
+ */
+
+#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/pci/pcidevs.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <dev/i2c/i2cvar.h>
+
+/* PCI Configuration space registers */
+#define NVI_PCI_SMBASE1 0x50
+#define NVI_PCI_SMBASE2 0x54
+
+#define NVI_SMBASE(x) ((x) & 0xfffc)
+#define NVI_SMBASE_SIZE 8
+
+/* SMBus 2.0 registers */
+#define NVI_SMB_PRTCL 0x00 /* protocol, PEC */
+#define NVI_SMB_STS 0x01 /* status */
+#define NVI_SMB_ADDR 0x02 /* address */
+#define NVI_SMB_CMD 0x03 /* command */
+#define NVI_SMB_DATA(o) (0x04 + (o)) /* 32 data registers */
+#define NVI_SMB_BCNT 0x24 /* number of data bytes */
+#define NVI_SMB_ALRM_A 0x25 /* alarm address */
+#define NVI_SMB_ALRM_D 0x26 /* 2 bytes alarm data */
+
+#define NVI_SMB_STS_DONE 0x80
+#define NVI_SMB_STS_ALRM 0x40
+#define NVI_SMB_STS_RES 0x20
+#define NVI_SMB_STS_STATUS 0x1f
+
+#define NVI_SMB_PRTCL_WRITE 0x00
+#define NVI_SMB_PRTCL_READ 0x01
+#define NVI_SMB_PRTCL_QUICK 0x02
+#define NVI_SMB_PRTCL_BYTE 0x04
+#define NVI_SMB_PRTCL_BYTE_DATA 0x06
+#define NVI_SMB_PRTCL_WORD_DATA 0x08
+#define NVI_SMB_PRTCL_BLOCK_DATA 0x0a
+#define NVI_SMB_PRTCL_PROC_CALL 0x0c
+#define NVI_SMB_PRTCL_BLOCK_PROC_CALL 0x0d
+#define NVI_SMB_PRTCL_PEC 0x80
+
+#ifdef NVIIC_DEBUG
+#define DPRINTF(x...) do { if (nviic_debug) printf(x); } while (0)
+int nviic_debug = 1;
+#else
+#define DPRINTF(x...) /* x */
+#endif
+
+/* there are two iic busses on this pci device */
+#define NVIIC_NBUS 2
+
+int nviic_match(struct device *, void *, void *);
+void nviic_attach(struct device *, struct device *, void *);
+
+struct nviic_softc;
+
+struct nviic_controller {
+ struct nviic_softc *nc_sc;
+ bus_space_handle_t nc_ioh;
+ struct lock nc_lock;
+ struct i2c_controller nc_i2c;
+};
+
+struct nviic_softc {
+ struct device sc_dev;
+ bus_space_tag_t sc_iot;
+ struct nviic_controller sc_nc[NVIIC_NBUS];
+};
+
+struct cfattach nviic_ca = {
+ sizeof(struct nviic_softc), nviic_match, nviic_attach
+};
+
+struct cfdriver nviic_cd = {
+ NULL, "nviic", DV_DULL
+};
+
+int nviic_i2c_acquire_bus(void *, int);
+void nviic_i2c_release_bus(void *, int);
+int nviic_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *,
+ size_t, void *, size_t, int);
+
+u_int8_t nviic_read(struct nviic_controller *, bus_size_t);
+void nviic_write(struct nviic_controller *, bus_size_t, u_int8_t);
+
+#define DEVNAME(s) ((sc)->sc_dev.dv_xname)
+
+const struct pci_matchid nviic_ids[] = {
+ { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_SMB },
+ { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_SMB },
+ { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_SMB },
+ { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_250_SMB },
+ { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_SMB }
+};
+
+int
+nviic_match(struct device *parent, void *match, void *aux)
+{
+ return (pci_matchbyid(aux, nviic_ids,
+ sizeof(nviic_ids) / sizeof(nviic_ids[0])));
+}
+
+void
+nviic_attach(struct device *parent, struct device *self, void *aux)
+{
+ struct nviic_softc *sc = (struct nviic_softc *)self;
+ struct pci_attach_args *pa = aux;
+ struct nviic_controller *nc;
+ struct i2cbus_attach_args iba;
+ int baseregs[NVIIC_NBUS];
+ pcireg_t reg;
+ int i;
+
+ sc->sc_iot = pa->pa_iot;
+
+ printf("\n");
+
+ baseregs[0] = NVI_PCI_SMBASE1;
+ baseregs[1] = NVI_PCI_SMBASE2;
+ for (i = 0; i < NVIIC_NBUS; i++) {
+ nc = &sc->sc_nc[i];
+
+ reg = pci_conf_read(pa->pa_pc, pa->pa_tag, baseregs[i]);
+ if (bus_space_map(sc->sc_iot, NVI_SMBASE(reg), NVI_SMBASE_SIZE,
+ 0, &nc->nc_ioh)) {
+ printf("%s: unable to map space for bus %d\n",
+ DEVNAME(sc), i);
+ continue;
+ }
+
+ nc->nc_sc = sc;
+ lockinit(&nc->nc_lock, PRIBIO | PCATCH, "iiclk", 0, 0);
+ nc->nc_i2c.ic_cookie = nc;
+ nc->nc_i2c.ic_acquire_bus = nviic_i2c_acquire_bus;
+ nc->nc_i2c.ic_release_bus = nviic_i2c_release_bus;
+ nc->nc_i2c.ic_exec = nviic_i2c_exec;
+
+ bzero(&iba, sizeof(iba));
+ iba.iba_name = "iic";
+ iba.iba_tag = &nc->nc_i2c;
+ config_found(self, &iba, iicbus_print);
+ }
+}
+
+int
+nviic_i2c_acquire_bus(void *arg, int flags)
+{
+ struct nviic_controller *nc = arg;
+
+ if (cold || (flags & I2C_F_POLL))
+ return (0);
+
+ return (lockmgr(&nc->nc_lock, LK_EXCLUSIVE, NULL));
+}
+
+void
+nviic_i2c_release_bus(void *arg, int flags)
+{
+ struct nviic_controller *nc = arg;
+
+ if (cold || (flags & I2C_F_POLL))
+ return;
+
+ lockmgr(&nc->nc_lock, LK_EXCLUSIVE, NULL);
+}
+
+int
+nviic_i2c_exec(void *arg, i2c_op_t op, i2c_addr_t addr,
+ const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags)
+{
+ struct nviic_controller *nc = arg;
+ struct nviic_softc *sc = nc->nc_sc;
+ u_int8_t protocol;
+ u_int8_t *b;
+ u_int8_t sts;
+ int i;
+
+ DPRINTF("%s: exec op: %d addr: 0x%x cmdlen: %d len: %d flags 0x%x\n",
+ DEVNAME(sc), op, addr, cmdlen, len, flags);
+
+ if (cold)
+ flags |= I2C_F_POLL;
+
+ if (I2C_OP_STOP_P(op) == 0 || cmdlen > 1 || len > 2)
+ return (1);
+
+ /* set slave address */
+ nviic_write(nc, NVI_SMB_ADDR, addr << 1);
+
+ /* set command byte */
+ if (cmdlen > 0) {
+ b = (u_int8_t *)cmdbuf;
+ nviic_write(nc, NVI_SMB_CMD, b[0]);
+ }
+
+ b = (u_int8_t *)buf;
+
+ /* write data */
+ if (I2C_OP_WRITE_P(op)) {
+ for (i = 0; i < len; i++)
+ nviic_write(nc, NVI_SMB_DATA(i), b[i]);
+ }
+
+ switch (len) {
+ case 0:
+ protocol = NVI_SMB_PRTCL_BYTE;
+ break;
+ case 1:
+ protocol = NVI_SMB_PRTCL_BYTE_DATA;
+ break;
+ case 2:
+ protocol = NVI_SMB_PRTCL_WORD_DATA;
+ break;
+ }
+
+ /* set direction */
+ if (I2C_OP_READ_P(op))
+ protocol |= NVI_SMB_PRTCL_READ;
+
+ /* start transaction */
+ nviic_write(nc, NVI_SMB_PRTCL, protocol);
+
+ for (i = 10000; i > 0; i--) {
+ delay(500);
+ if (nviic_read(nc, NVI_SMB_PRTCL) == 0)
+ break;
+ }
+ if (i == 0) {
+ printf("%s: timeout\n", DEVNAME(sc));
+ return (1);
+ }
+
+ sts = nviic_read(nc, NVI_SMB_STS);
+ if (sts & NVI_SMB_STS_STATUS)
+ return (1);
+
+ /* read data */
+ if (I2C_OP_READ_P(op)) {
+ for (i = 0; i < len; i++)
+ b[i] = nviic_read(nc, NVI_SMB_DATA(i));
+ }
+
+ return (0);
+}
+
+u_int8_t
+nviic_read(struct nviic_controller *nc, bus_size_t r)
+{
+ struct nviic_softc *sc = nc->nc_sc;
+
+ bus_space_barrier(sc->sc_iot, nc->nc_ioh, r, 1,
+ BUS_SPACE_BARRIER_READ);
+ return (bus_space_read_1(sc->sc_iot, nc->nc_ioh, r));
+}
+
+void
+nviic_write(struct nviic_controller *nc, bus_size_t r, u_int8_t v)
+{
+ struct nviic_softc *sc = nc->nc_sc;
+
+ bus_space_write_1(sc->sc_iot, nc->nc_ioh, r, v);
+ bus_space_barrier(sc->sc_iot, nc->nc_ioh, r, 1,
+ BUS_SPACE_BARRIER_WRITE);
+}