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
This commit is contained in:
ian 2014-12-27 02:37:52 +00:00
parent 799361e237
commit d082e488cb
7 changed files with 210 additions and 38 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd June 24, 2014
.Dd November 17, 2014
.Dt IICBUS 4
.Os
.Sh NAME
@ -103,11 +103,58 @@ Some I2C interfaces are available:
.It Sy lpbb Ta "parallel port specific bit-banging interface"
.It Sy bktr Ta "Brooktree848 video chipset, hardware and software master-only interface"
.El
.Sh BUS FREQUENCY CONFIGURATION
The operating frequency of an I2C bus may be fixed or configurable.
The bus may be used as part of some larger standard interface, and that
interface specification may require a fixed frequency.
The driver for that hardware would not honor an attempt to configure a
different speed.
A general purpose I2C bus, such as those found in many embedded systems,
will often support multiple bus frequencies.
.Pp
When a system supports multiple I2C busses, a different frequency can
be configured for each bus by number, represented by the
.Va %d
in the variable names below.
Busses can be configured using any combination of device hints,
Flattened Device Tree (FDT) data, tunables set via
.Xr loader 8 ,
or at runtime using
.Xr sysctl 8 .
When configuration is supplied using more than one method, FDT and
hint data will be overridden by a tunable, which can be overriden by
.Xr sysctl 8 .
.Ss Device Hints
Set
.Va hint.iicbus.%d.frequency
to the frequency in Hz, on systems that use device hints to configure
I2C devices.
The hint is also honored by systems that use FDT data if
no frequency is configured using FDT.
.Ss Flattened Device Tree Data
Configure the I2C bus speed using the FDT standard
.Va clock-frequency
property of the node describing the I2C controller hardware.
.Ss Sysctl and Tunable
Set
.Va dev.iicbus.%d.frequency
in
.Xr loader.conf 5 .
The same variable can be changed at any time with
.Xr sysctl 8 .
Reset the bus using
.Xr i2c 8
or the
.Xr iic 4
.Va I2CRSTCARD
ioctl to make the change take effect.
.Sh SEE ALSO
.Xr fdt 4 ,
.Xr iic 4 ,
.Xr iicbb 4 ,
.Xr lpbb 4 ,
.Xr pcf 4
.Xr pcf 4 ,
.Xr i2c 8
.Sh HISTORY
The
.Nm

View File

@ -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);

View File

@ -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

View File

@ -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
};

View File

@ -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;

View File

@ -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;

View File

@ -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