summaryrefslogtreecommitdiffstats
path: root/sys/arm/freescale
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2014-05-16 02:21:51 +0000
committerian <ian@FreeBSD.org>2014-05-16 02:21:51 +0000
commit979d09322879c661433d370b8f8f44881d70ea2f (patch)
tree7d5e707d239991534fc6644bdee36afac3a7d110 /sys/arm/freescale
parent8e2dd9b5e30117e2cab1d46c1cc0c2dfb4b7647e (diff)
downloadFreeBSD-src-979d09322879c661433d370b8f8f44881d70ea2f.zip
FreeBSD-src-979d09322879c661433d370b8f8f44881d70ea2f.tar.gz
MFC r262534, r262548, r262549, r262552, r262568, r262581, r262583, r262584,
r262585, r262587, r262696, r262712 Replace many pasted identical definitions of cpu_initclocks() with a common implementation in arm/machdep.c. aicasm: Don't complain about missing prototypes to ease bootstrap issues. Vybrid: Add driver for Inter-Integrated Circuit (I2C). imx6: Initialize the Low Power Mode bits to keep the ARM cores running during WFI. All our current ARM multi-core systems have all cores in one package with a shared L2 cache, reflect that in the common cpu_topo() routine. mpcore timer: Supply a DELAY() implementation via weak linkage, so that SoC-specific code can supply a better implementation. imx6: Add some rudimentary voltage control. Add an armv7 implementation of cpu_sleep(). Add __used attribute so that the DELAY implementation doesn't get optimized away as unreferenced, causing linker errors when trying to resolve the weak reference to the missing function.
Diffstat (limited to 'sys/arm/freescale')
-rw-r--r--sys/arm/freescale/imx/imx6_anatop.c79
-rw-r--r--sys/arm/freescale/imx/imx6_ccm.c21
-rw-r--r--sys/arm/freescale/imx/imx6_ccmreg.h21
-rw-r--r--sys/arm/freescale/imx/imx_gpt.c11
-rw-r--r--sys/arm/freescale/vybrid/files.vybrid1
-rw-r--r--sys/arm/freescale/vybrid/vf_i2c.c471
6 files changed, 584 insertions, 20 deletions
diff --git a/sys/arm/freescale/imx/imx6_anatop.c b/sys/arm/freescale/imx/imx6_anatop.c
index c393656..c713d8a 100644
--- a/sys/arm/freescale/imx/imx6_anatop.c
+++ b/sys/arm/freescale/imx/imx6_anatop.c
@@ -66,6 +66,7 @@ __FBSDID("$FreeBSD$");
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
+#include <machine/fdt.h>
#include <arm/freescale/fsl_ocotpreg.h>
#include <arm/freescale/fsl_ocotpvar.h>
@@ -85,8 +86,11 @@ struct imx6_anatop_softc {
struct resource *res[2];
uint32_t cpu_curhz;
uint32_t cpu_curmhz;
+ uint32_t cpu_curmv;
uint32_t cpu_minhz;
+ uint32_t cpu_minmv;
uint32_t cpu_maxhz;
+ uint32_t cpu_maxmv;
uint32_t refosc_hz;
void *temp_intrhand;
uint32_t temp_high_val;
@@ -103,12 +107,15 @@ struct imx6_anatop_softc {
static struct imx6_anatop_softc *imx6_anatop_sc;
/*
- * Table of CPU max frequencies. This is indexed by the max frequency value
- * (0-3) from the ocotp CFG3 register.
+ * Tables of CPU max frequencies and corresponding voltages. This is indexed by
+ * the max frequency value (0-3) from the ocotp CFG3 register.
*/
static uint32_t imx6_cpu_maxhz_tab[] = {
792000000, 852000000, 996000000, 1200000000
};
+static uint32_t imx6_cpu_millivolt_tab[] = {
+ 1150, 1225, 1225, 1275
+};
#define TZ_ZEROC 2732 /* deci-Kelvin <-> deci-Celcius offset. */
@@ -130,6 +137,64 @@ imx6_anatop_write_4(bus_size_t offset, uint32_t value)
bus_write_4(imx6_anatop_sc->res[MEMRES], offset, value);
}
+static void
+vdd_set(struct imx6_anatop_softc *sc, int mv)
+{
+ int newtarg, oldtarg;
+ uint32_t delay, pmureg;
+ static boolean_t init_done = false;
+
+ /*
+ * The datasheet says VDD_PU and VDD_SOC must be equal, and VDD_ARM
+ * can't be more than 50mV above or 200mV below them. For now to keep
+ * things simple we set all three to the same value.
+ */
+
+ pmureg = imx6_anatop_read_4(IMX6_ANALOG_PMU_REG_CORE);
+ oldtarg = pmureg & IMX6_ANALOG_PMU_REG0_TARG_MASK;
+
+ /* Convert mV to target value. Clamp target to valid range. */
+ if (mv < 725)
+ newtarg = 0x00;
+ else if (mv > 1450)
+ newtarg = 0x1F;
+ else
+ newtarg = (mv - 700) / 25;
+
+ /*
+ * The first time through the 3 voltages might not be equal so use a
+ * long conservative delay. After that we need to delay 3uS for every
+ * 25mV step upward. No need to delay at all when lowering.
+ */
+ if (init_done) {
+ if (newtarg == oldtarg)
+ return;
+ else if (newtarg > oldtarg)
+ delay = (newtarg - oldtarg) * 3;
+ else
+ delay = 0;
+ } else {
+ delay = 700 / 25 * 3;
+ init_done = true;
+ }
+
+ /*
+ * Make the change and wait for it to take effect.
+ */
+ pmureg &= ~(IMX6_ANALOG_PMU_REG0_TARG_MASK |
+ IMX6_ANALOG_PMU_REG1_TARG_MASK |
+ IMX6_ANALOG_PMU_REG2_TARG_MASK);
+
+ pmureg |= newtarg << IMX6_ANALOG_PMU_REG0_TARG_SHIFT;
+ pmureg |= newtarg << IMX6_ANALOG_PMU_REG1_TARG_SHIFT;
+ pmureg |= newtarg << IMX6_ANALOG_PMU_REG2_TARG_SHIFT;
+
+ imx6_anatop_write_4(IMX6_ANALOG_PMU_REG_CORE, pmureg);
+ DELAY(delay);
+ sc->cpu_curmv = newtarg * 25 + 700;
+ device_printf(sc->dev, "voltage set to %u\n", sc->cpu_curmv);
+}
+
static inline uint32_t
cpufreq_hz_from_div(struct imx6_anatop_softc *sc, uint32_t div)
{
@@ -231,7 +296,9 @@ cpufreq_initialize(struct imx6_anatop_softc *sc)
FSL_OCOTP_CFG3_SPEED_MASK) >> FSL_OCOTP_CFG3_SPEED_SHIFT;
sc->cpu_minhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[0]);
+ sc->cpu_minmv = imx6_cpu_millivolt_tab[0];
sc->cpu_maxhz = cpufreq_actual_hz(sc, imx6_cpu_maxhz_tab[cfg3speed]);
+ sc->cpu_maxmv = imx6_cpu_millivolt_tab[cfg3speed];
/*
* Set the CPU to maximum speed.
@@ -241,6 +308,7 @@ cpufreq_initialize(struct imx6_anatop_softc *sc)
* basically assumes that a single core can't overheat before interrupts
* are enabled; empirical testing shows that to be a safe assumption.
*/
+ vdd_set(sc, sc->cpu_maxmv);
cpufreq_set_clock(sc, sc->cpu_maxhz);
device_printf(sc->dev, "CPU frequency %uMHz\n", sc->cpu_curmhz);
}
@@ -321,6 +389,7 @@ tempmon_gofast(struct imx6_anatop_softc *sc)
{
if (sc->cpu_curhz < sc->cpu_maxhz) {
+ vdd_set(sc, sc->cpu_maxmv);
cpufreq_set_clock(sc, sc->cpu_maxhz);
}
}
@@ -331,6 +400,7 @@ tempmon_goslow(struct imx6_anatop_softc *sc)
if (sc->cpu_curhz > sc->cpu_minhz) {
cpufreq_set_clock(sc, sc->cpu_minhz);
+ vdd_set(sc, sc->cpu_minmv);
}
}
@@ -451,6 +521,11 @@ imx6_anatop_attach(device_t dev)
if (err != 0)
goto out;
+ SYSCTL_ADD_UINT(device_get_sysctl_ctx(sc->dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)),
+ OID_AUTO, "cpu_voltage", CTLFLAG_RD,
+ &sc->cpu_curmv, 0, "Current CPU voltage in millivolts");
+
imx6_anatop_sc = sc;
/*
diff --git a/sys/arm/freescale/imx/imx6_ccm.c b/sys/arm/freescale/imx/imx6_ccm.c
index e42335a..2ed301f 100644
--- a/sys/arm/freescale/imx/imx6_ccm.c
+++ b/sys/arm/freescale/imx/imx6_ccm.c
@@ -92,6 +92,7 @@ ccm_attach(device_t dev)
{
struct ccm_softc *sc;
int err, rid;
+ uint32_t reg;
sc = device_get_softc(dev);
err = 0;
@@ -107,6 +108,26 @@ ccm_attach(device_t dev)
}
ccm_sc = sc;
+
+ /*
+ * Configure the Low Power Mode setting to leave the ARM core power on
+ * when a WFI instruction is executed. This lets the MPCore timers and
+ * GIC continue to run, which is helpful when the only thing that can
+ * wake you up is an MPCore Private Timer interrupt delivered via GIC.
+ *
+ * XXX Based on the docs, setting CCM_CGPR_INT_MEM_CLK_LPM shouldn't be
+ * required when the LPM bits are set to LPM_RUN. But experimentally
+ * I've experienced a fairly rare lockup when not setting it. I was
+ * unable to prove conclusively that the lockup was related to power
+ * management or that this definitively fixes it. Revisit this.
+ */
+ reg = RD4(sc, CCM_CGPR);
+ reg |= CCM_CGPR_INT_MEM_CLK_LPM;
+ WR4(sc, CCM_CGPR, reg);
+ reg = RD4(sc, CCM_CLPCR);
+ reg = (reg & ~CCM_CLPCR_LPM_MASK) | CCM_CLPCR_LPM_RUN;
+ WR4(sc, CCM_CLPCR, reg);
+
err = 0;
out:
diff --git a/sys/arm/freescale/imx/imx6_ccmreg.h b/sys/arm/freescale/imx/imx6_ccmreg.h
index 3c651dc..9cb7acc 100644
--- a/sys/arm/freescale/imx/imx6_ccmreg.h
+++ b/sys/arm/freescale/imx/imx6_ccmreg.h
@@ -29,13 +29,20 @@
#ifndef IMX6_CCMREG_H
#define IMX6_CCMREG_H
-#define CCM_CCGR1 0x06C
-#define CCM_CCGR2 0x070
-#define CCM_CCGR3 0x074
-#define CCM_CCGR4 0x078
-#define CCM_CCGR5 0x07C
-#define CCM_CCGR6 0x080
-#define CCM_CMEOR 0x088
+#define CCM_CLPCR 0x054
+#define CCM_CLPCR_LPM_MASK 0x03
+#define CCM_CLPCR_LPM_RUN 0x00
+#define CCM_CLPCR_LPM_WAIT 0x01
+#define CCM_CLPCR_LPM_STOP 0x02
+#define CCM_CGPR 0x064
+#define CCM_CGPR_INT_MEM_CLK_LPM (1 << 17)
+#define CCM_CCGR1 0x06C
+#define CCM_CCGR2 0x070
+#define CCM_CCGR3 0x074
+#define CCM_CCGR4 0x078
+#define CCM_CCGR5 0x07C
+#define CCM_CCGR6 0x080
+#define CCM_CMEOR 0x088
#endif
diff --git a/sys/arm/freescale/imx/imx_gpt.c b/sys/arm/freescale/imx/imx_gpt.c
index f792a97..73bc8ec 100644
--- a/sys/arm/freescale/imx/imx_gpt.c
+++ b/sys/arm/freescale/imx/imx_gpt.c
@@ -319,17 +319,6 @@ imx_gpt_get_timerfreq(struct imx_gpt_softc *sc)
return (sc->clkfreq);
}
-void
-cpu_initclocks(void)
-{
-
- if (imx_gpt_sc == NULL) {
- panic("%s: i.MX GPT driver has not been initialized!", __func__);
- }
-
- cpu_initclocks_bsp();
-}
-
static int
imx_gpt_intr(void *arg)
{
diff --git a/sys/arm/freescale/vybrid/files.vybrid b/sys/arm/freescale/vybrid/files.vybrid
index 890e23e..1103f13 100644
--- a/sys/arm/freescale/vybrid/files.vybrid
+++ b/sys/arm/freescale/vybrid/files.vybrid
@@ -23,6 +23,7 @@ arm/freescale/vybrid/vf_mscm.c standard
arm/freescale/vybrid/vf_src.c standard
arm/freescale/vybrid/vf_edma.c standard
arm/freescale/vybrid/vf_dmamux.c standard
+arm/freescale/vybrid/vf_i2c.c optional iicbus
arm/freescale/vybrid/vf_tcon.c optional vt
arm/freescale/vybrid/vf_dcu4.c optional vt
arm/freescale/vybrid/vf_nfc.c optional nand
diff --git a/sys/arm/freescale/vybrid/vf_i2c.c b/sys/arm/freescale/vybrid/vf_i2c.c
new file mode 100644
index 0000000..67254b4
--- /dev/null
+++ b/sys/arm/freescale/vybrid/vf_i2c.c
@@ -0,0 +1,471 @@
+/*-
+ * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
+ * 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.
+ */
+
+/*
+ * Vybrid Family Inter-Integrated Circuit (I2C)
+ * Chapter 48, Vybrid Reference Manual, Rev. 5, 07/2013
+ */
+
+/*
+ * This driver is based on the I2C driver for IMX (imx/i2c.c).
+ */
+
+#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/malloc.h>
+#include <sys/rman.h>
+#include <sys/timeet.h>
+#include <sys/timetc.h>
+
+#include <dev/iicbus/iiconf.h>
+#include <dev/iicbus/iicbus.h>
+
+#include "iicbus_if.h"
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <machine/bus.h>
+#include <machine/fdt.h>
+#include <machine/cpu.h>
+#include <machine/intr.h>
+
+#include <arm/freescale/vybrid/vf_common.h>
+
+#define I2C_IBAD 0x0 /* I2C Bus Address Register */
+#define I2C_IBFD 0x1 /* I2C Bus Frequency Divider Register */
+#define I2C_IBCR 0x2 /* I2C Bus Control Register */
+#define IBCR_MDIS (1 << 7) /* Module disable. */
+#define IBCR_IBIE (1 << 6) /* I-Bus Interrupt Enable. */
+#define IBCR_MSSL (1 << 5) /* Master/Slave mode select. */
+#define IBCR_TXRX (1 << 4) /* Transmit/Receive mode select. */
+#define IBCR_NOACK (1 << 3) /* Data Acknowledge disable. */
+#define IBCR_RSTA (1 << 2) /* Repeat Start. */
+#define IBCR_DMAEN (1 << 1) /* DMA Enable. */
+#define I2C_IBSR 0x3 /* I2C Bus Status Register */
+#define IBSR_TCF (1 << 7) /* Transfer complete. */
+#define IBSR_IAAS (1 << 6) /* Addressed as a slave. */
+#define IBSR_IBB (1 << 5) /* Bus busy. */
+#define IBSR_IBAL (1 << 4) /* Arbitration Lost. */
+#define IBSR_SRW (1 << 2) /* Slave Read/Write. */
+#define IBSR_IBIF (1 << 1) /* I-Bus Interrupt Flag. */
+#define IBSR_RXAK (1 << 0) /* Received Acknowledge. */
+#define I2C_IBDR 0x4 /* I2C Bus Data I/O Register */
+#define I2C_IBIC 0x5 /* I2C Bus Interrupt Config Register */
+#define IBIC_BIIE (1 << 7) /* Bus Idle Interrupt Enable bit. */
+#define I2C_IBDBG 0x6 /* I2C Bus Debug Register */
+
+#ifdef DEBUG
+#define vf_i2c_dbg(_sc, fmt, args...) \
+ device_printf((_sc)->dev, fmt, ##args)
+#else
+#define vf_i2c_dbg(_sc, fmt, args...)
+#endif
+
+static int i2c_repeated_start(device_t, u_char, int);
+static int i2c_start(device_t, u_char, int);
+static int i2c_stop(device_t);
+static int i2c_reset(device_t, u_char, u_char, u_char *);
+static int i2c_read(device_t, char *, int, int *, int, int);
+static int i2c_write(device_t, const char *, int, int *, int);
+
+struct i2c_softc {
+ struct resource *res[2];
+ bus_space_tag_t bst;
+ bus_space_handle_t bsh;
+ device_t dev;
+ device_t iicbus;
+ struct mtx mutex;
+};
+
+static struct resource_spec i2c_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE },
+ { -1, 0 }
+};
+
+static int
+i2c_probe(device_t dev)
+{
+
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_is_compatible(dev, "fsl,mvf600-i2c"))
+ return (ENXIO);
+
+ device_set_desc(dev, "Vybrid Family Inter-Integrated Circuit (I2C)");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+i2c_attach(device_t dev)
+{
+ struct i2c_softc *sc;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ mtx_init(&sc->mutex, device_get_nameunit(dev), "I2C", MTX_DEF);
+
+ if (bus_alloc_resources(dev, i2c_spec, sc->res)) {
+ device_printf(dev, "could not allocate resources\n");
+ return (ENXIO);
+ }
+
+ /* Memory interface */
+ sc->bst = rman_get_bustag(sc->res[0]);
+ sc->bsh = rman_get_bushandle(sc->res[0]);
+
+ WRITE1(sc, I2C_IBIC, IBIC_BIIE);
+
+ sc->iicbus = device_add_child(dev, "iicbus", -1);
+ if (sc->iicbus == NULL) {
+ device_printf(dev, "could not add iicbus child");
+ mtx_destroy(&sc->mutex);
+ return (ENXIO);
+ }
+
+ bus_generic_attach(dev);
+
+ return (0);
+}
+
+/* Wait for transfer interrupt flag */
+static int
+wait_for_iif(struct i2c_softc *sc)
+{
+ int retry;
+
+ retry = 1000;
+ while (retry --) {
+ if (READ1(sc, I2C_IBSR) & IBSR_IBIF) {
+ WRITE1(sc, I2C_IBSR, IBSR_IBIF);
+ return (IIC_NOERR);
+ }
+ DELAY(10);
+ }
+
+ return (IIC_ETIMEOUT);
+}
+
+/* Wait for free bus */
+static int
+wait_for_nibb(struct i2c_softc *sc)
+{
+ int retry;
+
+ retry = 1000;
+ while (retry --) {
+ if ((READ1(sc, I2C_IBSR) & IBSR_IBB) == 0)
+ return (IIC_NOERR);
+ DELAY(10);
+ }
+
+ return (IIC_ETIMEOUT);
+}
+
+/* Wait for transfer complete+interrupt flag */
+static int
+wait_for_icf(struct i2c_softc *sc)
+{
+ int retry;
+
+ retry = 1000;
+ while (retry --) {
+ if (READ1(sc, I2C_IBSR) & IBSR_TCF) {
+ if (READ1(sc, I2C_IBSR) & IBSR_IBIF) {
+ WRITE1(sc, I2C_IBSR, IBSR_IBIF);
+ return (IIC_NOERR);
+ }
+ }
+ DELAY(10);
+ }
+
+ return (IIC_ETIMEOUT);
+}
+
+static int
+i2c_repeated_start(device_t dev, u_char slave, int timeout)
+{
+ struct i2c_softc *sc;
+ int error;
+ int reg;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c repeated start\n");
+
+ mtx_lock(&sc->mutex);
+
+ WRITE1(sc, I2C_IBAD, slave);
+
+ if ((READ1(sc, I2C_IBSR) & IBSR_IBB) == 0) {
+ mtx_unlock(&sc->mutex);
+ return (IIC_EBUSBSY);
+ }
+
+ /* Set repeated start condition */
+ DELAY(10);
+
+ reg = READ1(sc, I2C_IBCR);
+ reg |= (IBCR_RSTA | IBCR_IBIE);
+ WRITE1(sc, I2C_IBCR, reg);
+
+ DELAY(10);
+
+ /* Write target address - LSB is R/W bit */
+ WRITE1(sc, I2C_IBDR, slave);
+
+ error = wait_for_iif(sc);
+
+ mtx_unlock(&sc->mutex);
+
+ if (error)
+ return (error);
+
+ return (IIC_NOERR);
+}
+
+static int
+i2c_start(device_t dev, u_char slave, int timeout)
+{
+ struct i2c_softc *sc;
+ int error;
+ int reg;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c start\n");
+
+ mtx_lock(&sc->mutex);
+
+ WRITE1(sc, I2C_IBAD, slave);
+
+ if (READ1(sc, I2C_IBSR) & IBSR_IBB) {
+ mtx_unlock(&sc->mutex);
+ vf_i2c_dbg(sc, "cant i2c start: IIC_EBUSBSY\n");
+ return (IIC_EBUSBSY);
+ }
+
+ /* Set start condition */
+ reg = (IBCR_MSSL | IBCR_NOACK | IBCR_IBIE);
+ WRITE1(sc, I2C_IBCR, reg);
+
+ DELAY(100);
+
+ reg |= (IBCR_TXRX);
+ WRITE1(sc, I2C_IBCR, reg);
+
+ /* Write target address - LSB is R/W bit */
+ WRITE1(sc, I2C_IBDR, slave);
+
+ error = wait_for_iif(sc);
+
+ mtx_unlock(&sc->mutex);
+ if (error) {
+ vf_i2c_dbg(sc, "cant i2c start: iif error\n");
+ return (error);
+ }
+
+ return (IIC_NOERR);
+}
+
+static int
+i2c_stop(device_t dev)
+{
+ struct i2c_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c stop\n");
+
+ mtx_lock(&sc->mutex);
+
+ WRITE1(sc, I2C_IBCR, IBCR_NOACK | IBCR_IBIE);
+
+ DELAY(100);
+
+ /* Reset controller if bus still busy after STOP */
+ if (wait_for_nibb(sc) == IIC_ETIMEOUT) {
+ WRITE1(sc, I2C_IBCR, IBCR_MDIS);
+ DELAY(1000);
+ WRITE1(sc, I2C_IBCR, IBCR_NOACK);
+ }
+ mtx_unlock(&sc->mutex);
+
+ return (IIC_NOERR);
+}
+
+static int
+i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldadr)
+{
+ struct i2c_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c reset\n");
+
+ switch (speed) {
+ case IIC_FAST:
+ case IIC_SLOW:
+ case IIC_UNKNOWN:
+ case IIC_FASTEST:
+ default:
+ break;
+ }
+
+ mtx_lock(&sc->mutex);
+ WRITE1(sc, I2C_IBCR, IBCR_MDIS);
+
+ DELAY(1000);
+
+ WRITE1(sc, I2C_IBFD, 20);
+ WRITE1(sc, I2C_IBCR, 0x0); /* Enable i2c */
+
+ DELAY(1000);
+
+ mtx_unlock(&sc->mutex);
+
+ return (IIC_NOERR);
+}
+
+static int
+i2c_read(device_t dev, char *buf, int len, int *read, int last, int delay)
+{
+ struct i2c_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c read\n");
+
+ *read = 0;
+
+ mtx_lock(&sc->mutex);
+
+ if (len) {
+ if (len == 1)
+ WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | \
+ IBCR_NOACK);
+ else
+ WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL);
+
+ /* dummy read */
+ READ1(sc, I2C_IBDR);
+ DELAY(1000);
+ }
+
+ while (*read < len) {
+ error = wait_for_icf(sc);
+ if (error) {
+ mtx_unlock(&sc->mutex);
+ return (error);
+ }
+
+ if ((*read == len - 2) && last) {
+ /* NO ACK on last byte */
+ WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_MSSL | \
+ IBCR_NOACK);
+ }
+
+ if ((*read == len - 1) && last) {
+ /* Transfer done, remove master bit */
+ WRITE1(sc, I2C_IBCR, IBCR_IBIE | IBCR_NOACK);
+ }
+
+ *buf++ = READ1(sc, I2C_IBDR);
+ (*read)++;
+ }
+ mtx_unlock(&sc->mutex);
+
+ return (IIC_NOERR);
+}
+
+static int
+i2c_write(device_t dev, const char *buf, int len, int *sent, int timeout)
+{
+ struct i2c_softc *sc;
+ int error;
+
+ sc = device_get_softc(dev);
+
+ vf_i2c_dbg(sc, "i2c write\n");
+
+ *sent = 0;
+
+ mtx_lock(&sc->mutex);
+ while (*sent < len) {
+
+ WRITE1(sc, I2C_IBDR, *buf++);
+
+ error = wait_for_iif(sc);
+ if (error) {
+ mtx_unlock(&sc->mutex);
+ return (error);
+ }
+
+ (*sent)++;
+ }
+ mtx_unlock(&sc->mutex);
+
+ return (IIC_NOERR);
+}
+
+static device_method_t i2c_methods[] = {
+ DEVMETHOD(device_probe, i2c_probe),
+ DEVMETHOD(device_attach, i2c_attach),
+
+ DEVMETHOD(iicbus_callback, iicbus_null_callback),
+ DEVMETHOD(iicbus_repeated_start, i2c_repeated_start),
+ DEVMETHOD(iicbus_start, i2c_start),
+ DEVMETHOD(iicbus_stop, i2c_stop),
+ DEVMETHOD(iicbus_reset, i2c_reset),
+ DEVMETHOD(iicbus_read, i2c_read),
+ DEVMETHOD(iicbus_write, i2c_write),
+ DEVMETHOD(iicbus_transfer, iicbus_transfer_gen),
+
+ { 0, 0 }
+};
+
+static driver_t i2c_driver = {
+ "i2c",
+ i2c_methods,
+ sizeof(struct i2c_softc),
+};
+
+static devclass_t i2c_devclass;
+
+DRIVER_MODULE(i2c, simplebus, i2c_driver, i2c_devclass, 0, 0);
+DRIVER_MODULE(iicbus, i2c, iicbus_driver, iicbus_devclass, 0, 0);
OpenPOWER on IntegriCloud