svn commit: r274641 - in head/sys: arm/freescale/imx arm/ti dev/iicbus dev/ofw

Ian Lepore ian at FreeBSD.org
Tue Nov 18 01:54:33 UTC 2014


Author: ian
Date: Tue Nov 18 01:54:31 2014
New Revision: 274641
URL: https://svnweb.freebsd.org/changeset/base/274641

Log:
  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

Modified:
  head/sys/arm/freescale/imx/imx_i2c.c
  head/sys/arm/ti/ti_i2c.c
  head/sys/dev/iicbus/iicbus.c
  head/sys/dev/iicbus/iicbus.h
  head/sys/dev/iicbus/iicbus_if.m
  head/sys/dev/ofw/ofw_iicbus.c

Modified: head/sys/arm/freescale/imx/imx_i2c.c
==============================================================================
--- head/sys/arm/freescale/imx/imx_i2c.c	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/arm/freescale/imx/imx_i2c.c	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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);

Modified: head/sys/arm/ti/ti_i2c.c
==============================================================================
--- head/sys/arm/ti/ti_i2c.c	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/arm/ti/ti_i2c.c	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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_oma
  * 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_
 {
 	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_
 	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

Modified: head/sys/dev/iicbus/iicbus.c
==============================================================================
--- head/sys/dev/iicbus/iicbus.c	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/dev/iicbus/iicbus.c	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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,
 	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
 };

Modified: head/sys/dev/iicbus/iicbus.h
==============================================================================
--- head/sys/dev/iicbus/iicbus.h	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/dev/iicbus/iicbus.h	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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;

Modified: head/sys/dev/iicbus/iicbus_if.m
==============================================================================
--- head/sys/dev/iicbus/iicbus_if.m	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/dev/iicbus/iicbus_if.m	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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;

Modified: head/sys/dev/ofw/ofw_iicbus.c
==============================================================================
--- head/sys/dev/ofw/ofw_iicbus.c	Tue Nov 18 01:39:23 2014	(r274640)
+++ head/sys/dev/ofw/ofw_iicbus.c	Tue Nov 18 01:54:31 2014	(r274641)
@@ -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


More information about the svn-src-all mailing list