aboutsummaryrefslogtreecommitdiff
path: root/sys
diff options
context:
space:
mode:
authorIan Lepore <ian@FreeBSD.org>2014-11-18 01:54:31 +0000
committerIan Lepore <ian@FreeBSD.org>2014-11-18 01:54:31 +0000
commit844aff82a6599a8d96545dfb89663809e71719fc (patch)
tree12552f97db5b3d3d363057c8e7968cc7eea6aa65 /sys
parent668ed8aa024461af37a6c16a2ccbf1b6d7208aed (diff)
downloadsrc-844aff82a6599a8d96545dfb89663809e71719fc.tar.gz
src-844aff82a6599a8d96545dfb89663809e71719fc.zip
Allow i2c bus speed to be configured via hints, FDT data, and sysctl.
The current support for controlling i2c bus speed is an inconsistant mess. There are 4 symbolic speed values defined, UNKNOWN, SLOW, FAST, FASTEST. It seems to be universally assumed that SLOW means the standard 100KHz rate from the original spec. Nothing ever calls iicbus_reset() with a speed of FAST, although some drivers would treat it as the 400KHz standard speed. Mostly iicbus_reset() is called with the speed set to UNKNOWN or FASTEST, and there's really no telling what any individual driver will do with those. The speed of an i2c bus is limited by the speed of the slowest device on the bus. This means that generally the bus speed needs to be configured based on the board/system and the components within it. Historically for i2c we've configured with device hints. Newer systems use FDT data and it documents a clock-frequency property for i2c busses. Hobbyists and developers are likely to want on the fly changes. These changes provide all 3 methods, but do not require any existing drivers to change to use the new facilities. This adds an iicbus method, iicbus_get_frequency(dev, speed) that gets the frequency for the requested symbolic speed. If the symbolic speed is SLOW or if there is no speed configured for the bus, the returned value is 100KHz, always. Otherwise, if bus speed is configured by hints, fdt, tunable, or sysctl, that speed is returned. It also adds a helper function, iicbus_init_frequency() that any bus driver subclassed from iicbus can initialize the frequency from some other source of info. Initial driver implementations are provided for Freescale and TI. Differential Revision: https://reviews.freebsd.org/D1174 PR: 195009
Notes
Notes: svn path=/head/; revision=274641
Diffstat (limited to 'sys')
-rw-r--r--sys/arm/freescale/imx/imx_i2c.c54
-rw-r--r--sys/arm/ti/ti_i2c.c42
-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, 153 insertions, 34 deletions
diff --git a/sys/arm/freescale/imx/imx_i2c.c b/sys/arm/freescale/imx/imx_i2c.c
index 3c192ead291b..abcb01474dcf 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 b479ee552249..5be1ad2f9ff5 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 */
@@ -113,12 +112,11 @@ struct ti_i2c_clock_config
#if defined(SOC_OMAP4)
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, 5, 3, 4, 0, 0},
+/* { 3200000, 1, 113, 115, 7, 10}, - HS mode */
+ { 0 /* Table terminator */ }
};
#endif
@@ -128,11 +126,9 @@ static struct ti_i2c_clock_config ti_omap4_i2c_clock_configs[] = {
* clock to 12Mhz, for 400kHz I2C clock set the internal clock to 24Mhz.
*/
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, 7, 59, 61, 0, 0},
+ { 400000, 3, 23, 25, 0, 0},
+ { 0 /* Table terminator */ }
};
#endif
@@ -508,6 +504,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()) {
@@ -524,13 +521,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 d919277c12eb..dc567604be95 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 54fe9804eb2d..b5905ad8bc31 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 c57fac584216..120b5e702dea 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 1574f6e9674a..f6d1be58612f 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