summaryrefslogtreecommitdiffstats
path: root/sys
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2014-12-27 02:37:52 +0000
committerian <ian@FreeBSD.org>2014-12-27 02:37:52 +0000
commitd082e488cb5d0ab8f76c2694943f4b1688869f8b (patch)
tree8da22701848310928d5201db5385f6480785c535 /sys
parent799361e237fdb276321038f0ef7f547ad2a0a863 (diff)
downloadFreeBSD-src-d082e488cb5d0ab8f76c2694943f4b1688869f8b.zip
FreeBSD-src-d082e488cb5d0ab8f76c2694943f4b1688869f8b.tar.gz
MFC r274641, r274644, r274822, r276049:
Allow i2c bus speed to be configured via hints, FDT data, and sysctl. Implement bus speed setting for OMAP4, AM335x, and imx5/6. Fix the i2c bus speed divisors for TI OMAP4 and AM335x to give the advertised 100, 400, and 1000 KHz speeds. PR: 195009
Diffstat (limited to 'sys')
-rw-r--r--sys/arm/freescale/imx/imx_i2c.c54
-rw-r--r--sys/arm/ti/ti_i2c.c52
-rw-r--r--sys/dev/iicbus/iicbus.c48
-rw-r--r--sys/dev/iicbus/iicbus.h4
-rw-r--r--sys/dev/iicbus/iicbus_if.m20
-rw-r--r--sys/dev/ofw/ofw_iicbus.c19
6 files changed, 161 insertions, 36 deletions
diff --git a/sys/arm/freescale/imx/imx_i2c.c b/sys/arm/freescale/imx/imx_i2c.c
index 3c192ea..abcb014 100644
--- a/sys/arm/freescale/imx/imx_i2c.c
+++ b/sys/arm/freescale/imx/imx_i2c.c
@@ -35,6 +35,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
+#include <sys/limits.h>
#include <sys/module.h>
#include <sys/resource.h>
@@ -45,6 +46,8 @@ __FBSDID("$FreeBSD$");
#include <sys/lock.h>
#include <sys/mutex.h>
+#include <arm/freescale/imx/imx_ccmvar.h>
+
#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include "iicbus_if.h"
@@ -79,6 +82,32 @@ __FBSDID("$FreeBSD$");
#define I2C_BAUD_RATE_DEF 0x3F
#define I2C_DFSSR_DIV 0x10
+/*
+ * A table of available divisors and the associated coded values to put in the
+ * FDR register to achieve that divisor.. There is no algorithmic relationship I
+ * can see between divisors and the codes that go into the register. The table
+ * begins and ends with entries that handle insane configuration values.
+ */
+struct clkdiv {
+ u_int divisor;
+ u_int regcode;
+};
+static struct clkdiv clkdiv_table[] = {
+ { 0, 0x20 }, { 22, 0x20 }, { 24, 0x21 }, { 26, 0x22 },
+ { 28, 0x23 }, { 30, 0x00 }, { 32, 0x24 }, { 36, 0x25 },
+ { 40, 0x26 }, { 42, 0x03 }, { 44, 0x27 }, { 48, 0x28 },
+ { 52, 0x05 }, { 56, 0x29 }, { 60, 0x06 }, { 64, 0x2a },
+ { 72, 0x2b }, { 80, 0x2c }, { 88, 0x09 }, { 96, 0x2d },
+ { 104, 0x0a }, { 112, 0x2e }, { 128, 0x2f }, { 144, 0x0c },
+ { 160, 0x30 }, { 192, 0x31 }, { 224, 0x32 }, { 240, 0x0f },
+ { 256, 0x33 }, { 288, 0x10 }, { 320, 0x34 }, { 384, 0x35 },
+ { 448, 0x36 }, { 480, 0x13 }, { 512, 0x37 }, { 576, 0x14 },
+ { 640, 0x38 }, { 768, 0x39 }, { 896, 0x3a }, { 960, 0x17 },
+ { 1024, 0x3b }, { 1152, 0x18 }, { 1280, 0x3c }, { 1536, 0x3d },
+ { 1792, 0x3e }, { 1920, 0x1b }, { 2048, 0x3f }, { 2304, 0x1c },
+ { 2560, 0x1d }, { 3072, 0x1e }, { 3840, 0x1f }, {UINT_MAX, 0x1f}
+};
+
#ifdef DEBUG
#define debugf(fmt, args...) do { printf("%s(): ", __func__); \
printf(fmt,##args); } while (0)
@@ -390,28 +419,29 @@ static int
i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldadr)
{
struct i2c_softc *sc;
- uint8_t baud_rate;
+ u_int busfreq, div, i, ipgfreq;
sc = device_get_softc(dev);
- switch (speed) {
- case IIC_FAST:
- baud_rate = I2C_BAUD_RATE_FAST;
- break;
- case IIC_SLOW:
- case IIC_UNKNOWN:
- case IIC_FASTEST:
- default:
- baud_rate = I2C_BAUD_RATE_DEF;
- break;
+ /*
+ * Look up the divisor that gives the nearest speed that doesn't exceed
+ * the configured value for the bus.
+ */
+ ipgfreq = imx_ccm_ipg_hz();
+ busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed);
+ div = (ipgfreq + busfreq - 1) / busfreq;
+ for (i = 0; i < nitems(clkdiv_table); i++) {
+ if (clkdiv_table[i].divisor >= div)
+ break;
}
+ div = clkdiv_table[i].regcode;
mtx_lock(&sc->mutex);
i2c_write_reg(sc, I2C_CONTROL_REG, 0x0);
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
DELAY(1000);
- i2c_write_reg(sc, I2C_FDR_REG, 20);
+ i2c_write_reg(sc, I2C_FDR_REG, (uint8_t)div);
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN);
DELAY(1000);
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
diff --git a/sys/arm/ti/ti_i2c.c b/sys/arm/ti/ti_i2c.c
index e865f91..22a1877 100644
--- a/sys/arm/ti/ti_i2c.c
+++ b/sys/arm/ti/ti_i2c.c
@@ -102,8 +102,7 @@ struct ti_i2c_softc
struct ti_i2c_clock_config
{
- int speed;
- int bitrate;
+ u_int frequency; /* Bus frequency in Hz */
uint8_t psc; /* Fast/Standard mode prescale divider */
uint8_t scll; /* Fast/Standard mode SCL low time */
uint8_t sclh; /* Fast/Standard mode SCL high time */
@@ -116,27 +115,30 @@ struct ti_i2c_clock_config
#endif
#if defined(SOC_OMAP4)
+/*
+ * OMAP4 i2c bus clock is 96MHz / ((psc + 1) * (scll + 7 + sclh + 5)).
+ * The prescaler values for 100KHz and 400KHz modes come from the table in the
+ * OMAP4 TRM. The table doesn't list 1MHz; these values should give that speed.
+ */
static struct ti_i2c_clock_config ti_omap4_i2c_clock_configs[] = {
- { IIC_UNKNOWN, 100000, 23, 13, 15, 0, 0},
- { IIC_SLOW, 100000, 23, 13, 15, 0, 0},
- { IIC_FAST, 400000, 9, 5, 7, 0, 0},
- { IIC_FASTEST, 1000000, 5, 3, 4, 0, 0},
- /* { IIC_FASTEST, 3200000, 1, 113, 115, 7, 10}, - HS mode */
- { -1, 0 }
+ { 100000, 23, 13, 15, 0, 0},
+ { 400000, 9, 5, 7, 0, 0},
+ { 1000000, 3, 5, 7, 0, 0},
+/* { 3200000, 1, 113, 115, 7, 10}, - HS mode */
+ { 0 /* Table terminator */ }
};
#endif
#if defined(SOC_TI_AM335X)
/*
- * AM335X doesn't support HS mode. For 100kHz I2C clock set the internal
- * clock to 12Mhz, for 400kHz I2C clock set the internal clock to 24Mhz.
+ * AM335x i2c bus clock is 48MHZ / ((psc + 1) * (scll + 7 + sclh + 5))
+ * In all cases we prescale the clock to 24MHz as recommended in the manual.
*/
static struct ti_i2c_clock_config ti_am335x_i2c_clock_configs[] = {
- { IIC_UNKNOWN, 100000, 7, 59, 61, 0, 0},
- { IIC_SLOW, 100000, 7, 59, 61, 0, 0},
- { IIC_FAST, 400000, 3, 23, 25, 0, 0},
- { IIC_FASTEST, 400000, 3, 23, 25, 0, 0},
- { -1, 0 }
+ { 100000, 1, 111, 117, 0, 0},
+ { 400000, 1, 23, 25, 0, 0},
+ { 1000000, 1, 5, 7, 0, 0},
+ { 0 /* Table terminator */ }
};
#endif
@@ -512,6 +514,7 @@ ti_i2c_reset(struct ti_i2c_softc *sc, u_char speed)
{
int timeout;
struct ti_i2c_clock_config *clkcfg;
+ u_int busfreq;
uint16_t fifo_trsh, reg, scll, sclh;
switch (ti_chip()) {
@@ -528,13 +531,24 @@ ti_i2c_reset(struct ti_i2c_softc *sc, u_char speed)
default:
panic("Unknown Ti SoC, unable to reset the i2c");
}
- while (clkcfg->speed != -1) {
- if (clkcfg->speed == speed)
+
+ /*
+ * If we haven't attached the bus yet, just init at the default slow
+ * speed. This lets us get the hardware initialized enough to attach
+ * the bus which is where the real speed configuration is handled. After
+ * the bus is attached, get the configured speed from it. Search the
+ * configuration table for the best speed we can do that doesn't exceed
+ * the requested speed.
+ */
+ if (sc->sc_iicbus == NULL)
+ busfreq = 100000;
+ else
+ busfreq = IICBUS_GET_FREQUENCY(sc->sc_iicbus, speed);
+ for (;;) {
+ if (clkcfg[1].frequency == 0 || clkcfg[1].frequency > busfreq)
break;
clkcfg++;
}
- if (clkcfg->speed == -1)
- return (EINVAL);
/*
* 23.1.4.3 - HS I2C Software Reset
diff --git a/sys/dev/iicbus/iicbus.c b/sys/dev/iicbus/iicbus.c
index d919277..dc56760 100644
--- a/sys/dev/iicbus/iicbus.c
+++ b/sys/dev/iicbus/iicbus.c
@@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
+#include <sys/sysctl.h>
#include <sys/bus.h>
#include <dev/iicbus/iiconf.h>
@@ -96,6 +97,7 @@ iicbus_attach(device_t dev)
sc->dev = dev;
mtx_init(&sc->lock, "iicbus", NULL, MTX_DEF);
+ iicbus_init_frequency(dev, 0);
iicbus_reset(dev, IIC_FASTEST, 0, NULL);
if (resource_int_value(device_get_name(dev),
device_get_unit(dev), "strict", &strict) == 0)
@@ -243,6 +245,51 @@ iicbus_null_repeated_start(device_t dev, u_char addr)
return (IIC_ENOTSUPP);
}
+void
+iicbus_init_frequency(device_t dev, u_int bus_freq)
+{
+ struct iicbus_softc *sc = IICBUS_SOFTC(dev);
+
+ /*
+ * If a bus frequency value was passed in, use it. Otherwise initialize
+ * it first to the standard i2c 100KHz frequency, then override that
+ * from a hint if one exists.
+ */
+ if (bus_freq > 0)
+ sc->bus_freq = bus_freq;
+ else {
+ sc->bus_freq = 100000;
+ resource_int_value(device_get_name(dev), device_get_unit(dev),
+ "frequency", (int *)&sc->bus_freq);
+ }
+ /*
+ * Set up the sysctl that allows the bus frequency to be changed.
+ * It is flagged as a tunable so that the user can set the value in
+ * loader(8), and that will override any other setting from any source.
+ * The sysctl tunable/value is the one most directly controlled by the
+ * user and thus the one that always takes precedence.
+ */
+ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "frequency", CTLFLAG_RW | CTLFLAG_TUN, &sc->bus_freq,
+ sc->bus_freq, "Bus frequency in Hz");
+}
+
+static u_int
+iicbus_get_frequency(device_t dev, u_char speed)
+{
+ struct iicbus_softc *sc = IICBUS_SOFTC(dev);
+
+ /*
+ * If the frequency has not been configured for the bus, or the request
+ * is specifically for SLOW speed, use the standard 100KHz rate, else
+ * use the configured bus speed.
+ */
+ if (sc->bus_freq == 0 || speed == IIC_SLOW)
+ return (100000);
+ return (sc->bus_freq);
+}
+
static device_method_t iicbus_methods[] = {
/* device interface */
DEVMETHOD(device_probe, iicbus_probe),
@@ -260,6 +307,7 @@ static device_method_t iicbus_methods[] = {
/* iicbus interface */
DEVMETHOD(iicbus_transfer, iicbus_transfer),
+ DEVMETHOD(iicbus_get_frequency, iicbus_get_frequency),
DEVMETHOD_END
};
diff --git a/sys/dev/iicbus/iicbus.h b/sys/dev/iicbus/iicbus.h
index 54fe980..b5905ad 100644
--- a/sys/dev/iicbus/iicbus.h
+++ b/sys/dev/iicbus/iicbus.h
@@ -44,6 +44,7 @@ struct iicbus_softc
u_char strict; /* deny operations that violate the
* I2C protocol */
struct mtx lock;
+ u_int bus_freq; /* Configured bus Hz. */
};
struct iicbus_ivar
@@ -67,7 +68,8 @@ IICBUS_ACCESSOR(nostop, NOSTOP, bool)
#define IICBUS_UNLOCK(sc) mtx_unlock(&(sc)->lock)
#define IICBUS_ASSERT_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED)
-extern int iicbus_generic_intr(device_t dev, int event, char *buf);
+int iicbus_generic_intr(device_t dev, int event, char *buf);
+void iicbus_init_frequency(device_t dev, u_int bus_freq);
extern driver_t iicbus_driver;
extern devclass_t iicbus_devclass;
diff --git a/sys/dev/iicbus/iicbus_if.m b/sys/dev/iicbus/iicbus_if.m
index c57fac5..120b5e7 100644
--- a/sys/dev/iicbus/iicbus_if.m
+++ b/sys/dev/iicbus/iicbus_if.m
@@ -31,6 +31,15 @@
INTERFACE iicbus;
+CODE {
+ static u_int
+ iicbus_default_frequency(device_t bus, u_char speed)
+ {
+
+ return (100000);
+ }
+};
+
#
# Interpret interrupt
#
@@ -115,3 +124,14 @@ METHOD int transfer {
struct iic_msg *msgs;
uint32_t nmsgs;
};
+
+#
+# Return the frequency in Hz for the bus running at the given
+# symbolic speed. Only the IIC_SLOW speed has meaning, it is always
+# 100KHz. The UNKNOWN, FAST, and FASTEST rates all map to the
+# configured bus frequency, or 100KHz when not otherwise configured.
+#
+METHOD u_int get_frequency {
+ device_t dev;
+ u_char speed;
+} DEFAULT iicbus_default_frequency;
diff --git a/sys/dev/ofw/ofw_iicbus.c b/sys/dev/ofw/ofw_iicbus.c
index 1574f6e..f6d1be5 100644
--- a/sys/dev/ofw/ofw_iicbus.c
+++ b/sys/dev/ofw/ofw_iicbus.c
@@ -101,12 +101,24 @@ ofw_iicbus_attach(device_t dev)
{
struct iicbus_softc *sc = IICBUS_SOFTC(dev);
struct ofw_iicbus_devinfo *dinfo;
- phandle_t child;
- pcell_t paddr;
+ phandle_t child, node;
+ pcell_t freq, paddr;
device_t childdev;
sc->dev = dev;
mtx_init(&sc->lock, "iicbus", NULL, MTX_DEF);
+
+ /*
+ * If there is a clock-frequency property for the device node, use it as
+ * the starting value for the bus frequency. Then call the common
+ * routine that handles the tunable/sysctl which allows the FDT value to
+ * be overridden by the user.
+ */
+ node = ofw_bus_get_node(dev);
+ freq = 0;
+ OF_getencprop(node, "clock-frequency", &freq, sizeof(freq));
+ iicbus_init_frequency(dev, freq);
+
iicbus_reset(dev, IIC_FASTEST, 0, NULL);
bus_generic_probe(dev);
@@ -115,8 +127,7 @@ ofw_iicbus_attach(device_t dev)
/*
* Attach those children represented in the device tree.
*/
- for (child = OF_child(ofw_bus_get_node(dev)); child != 0;
- child = OF_peer(child)) {
+ for (child = OF_child(node); child != 0; child = OF_peer(child)) {
/*
* Try to get the I2C address first from the i2c-address
* property, then try the reg property. It moves around
OpenPOWER on IntegriCloud