844aff82a6
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
532 lines
13 KiB
C
532 lines
13 KiB
C
/*-
|
|
* Copyright (C) 2008-2009 Semihalf, Michal Hajduk
|
|
* Copyright (c) 2012, 2013 The FreeBSD Foundation
|
|
* All rights reserved.
|
|
*
|
|
* Portions of this software were developed by Oleksandr Rybalko
|
|
* under sponsorship from the FreeBSD Foundation.
|
|
*
|
|
* 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 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 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.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/limits.h>
|
|
#include <sys/module.h>
|
|
#include <sys/resource.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
#include <sys/rman.h>
|
|
|
|
#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"
|
|
|
|
#include <dev/fdt/fdt_common.h>
|
|
#include <dev/ofw/openfirm.h>
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
|
|
#define I2C_ADDR_REG 0x00 /* I2C slave address register */
|
|
#define I2C_FDR_REG 0x04 /* I2C frequency divider register */
|
|
#define I2C_CONTROL_REG 0x08 /* I2C control register */
|
|
#define I2C_STATUS_REG 0x0C /* I2C status register */
|
|
#define I2C_DATA_REG 0x10 /* I2C data register */
|
|
#define I2C_DFSRR_REG 0x14 /* I2C Digital Filter Sampling rate */
|
|
|
|
#define I2CCR_MEN (1 << 7) /* Module enable */
|
|
#define I2CCR_MSTA (1 << 5) /* Master/slave mode */
|
|
#define I2CCR_MTX (1 << 4) /* Transmit/receive mode */
|
|
#define I2CCR_TXAK (1 << 3) /* Transfer acknowledge */
|
|
#define I2CCR_RSTA (1 << 2) /* Repeated START */
|
|
|
|
#define I2CSR_MCF (1 << 7) /* Data transfer */
|
|
#define I2CSR_MASS (1 << 6) /* Addressed as a slave */
|
|
#define I2CSR_MBB (1 << 5) /* Bus busy */
|
|
#define I2CSR_MAL (1 << 4) /* Arbitration lost */
|
|
#define I2CSR_SRW (1 << 2) /* Slave read/write */
|
|
#define I2CSR_MIF (1 << 1) /* Module interrupt */
|
|
#define I2CSR_RXAK (1 << 0) /* Received acknowledge */
|
|
|
|
#define I2C_BAUD_RATE_FAST 0x31
|
|
#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)
|
|
#else
|
|
#define debugf(fmt, args...)
|
|
#endif
|
|
|
|
static struct ofw_compat_data compat_data[] = {
|
|
{"fsl,imx6q-i2c", 1},
|
|
{"fsl,imx-i2c", 1},
|
|
{NULL, 0}
|
|
};
|
|
|
|
struct i2c_softc {
|
|
device_t dev;
|
|
device_t iicbus;
|
|
struct resource *res;
|
|
struct mtx mutex;
|
|
int rid;
|
|
bus_space_handle_t bsh;
|
|
bus_space_tag_t bst;
|
|
};
|
|
|
|
static phandle_t i2c_get_node(device_t, device_t);
|
|
static int i2c_probe(device_t);
|
|
static int i2c_attach(device_t);
|
|
|
|
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);
|
|
|
|
static device_method_t i2c_methods[] = {
|
|
DEVMETHOD(device_probe, i2c_probe),
|
|
DEVMETHOD(device_attach, i2c_attach),
|
|
|
|
/* OFW methods */
|
|
DEVMETHOD(ofw_bus_get_node, i2c_get_node),
|
|
|
|
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 = {
|
|
"iichb",
|
|
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);
|
|
|
|
static phandle_t
|
|
i2c_get_node(device_t bus, device_t dev)
|
|
{
|
|
/*
|
|
* Share controller node with iicbus device
|
|
*/
|
|
return ofw_bus_get_node(bus);
|
|
}
|
|
|
|
static __inline void
|
|
i2c_write_reg(struct i2c_softc *sc, bus_size_t off, uint8_t val)
|
|
{
|
|
|
|
bus_space_write_1(sc->bst, sc->bsh, off, val);
|
|
}
|
|
|
|
static __inline uint8_t
|
|
i2c_read_reg(struct i2c_softc *sc, bus_size_t off)
|
|
{
|
|
|
|
return (bus_space_read_1(sc->bst, sc->bsh, off));
|
|
}
|
|
|
|
static __inline void
|
|
i2c_flag_set(struct i2c_softc *sc, bus_size_t off, uint8_t mask)
|
|
{
|
|
uint8_t status;
|
|
|
|
status = i2c_read_reg(sc, off);
|
|
status |= mask;
|
|
i2c_write_reg(sc, off, status);
|
|
}
|
|
|
|
/* Wait for transfer interrupt flag */
|
|
static int
|
|
wait_for_iif(struct i2c_softc *sc)
|
|
{
|
|
int retry;
|
|
|
|
retry = 1000;
|
|
while (retry --) {
|
|
if (i2c_read_reg(sc, I2C_STATUS_REG) & I2CSR_MIF)
|
|
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 ((i2c_read_reg(sc, I2C_STATUS_REG) & I2CSR_MBB) == 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 ((i2c_read_reg(sc, I2C_STATUS_REG) &
|
|
(I2CSR_MCF|I2CSR_MIF)) == (I2CSR_MCF|I2CSR_MIF))
|
|
return (IIC_NOERR);
|
|
DELAY(10);
|
|
}
|
|
|
|
return (IIC_ETIMEOUT);
|
|
}
|
|
|
|
static int
|
|
i2c_probe(device_t dev)
|
|
{
|
|
struct i2c_softc *sc;
|
|
|
|
if (!ofw_bus_status_okay(dev))
|
|
return (ENXIO);
|
|
|
|
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
|
|
return (ENXIO);
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->rid = 0;
|
|
|
|
sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid,
|
|
RF_ACTIVE);
|
|
if (sc->res == NULL) {
|
|
device_printf(dev, "could not allocate resources\n");
|
|
return (ENXIO);
|
|
}
|
|
|
|
sc->bst = rman_get_bustag(sc->res);
|
|
sc->bsh = rman_get_bushandle(sc->res);
|
|
|
|
/* Enable I2C */
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN);
|
|
bus_release_resource(dev, SYS_RES_MEMORY, sc->rid, sc->res);
|
|
device_set_desc(dev, "Freescale i.MX I2C bus controller");
|
|
|
|
return (BUS_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
i2c_attach(device_t dev)
|
|
{
|
|
struct i2c_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
sc->dev = dev;
|
|
sc->rid = 0;
|
|
|
|
mtx_init(&sc->mutex, device_get_nameunit(dev), "I2C", MTX_DEF);
|
|
|
|
sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->rid,
|
|
RF_ACTIVE);
|
|
if (sc->res == NULL) {
|
|
device_printf(dev, "could not allocate resources");
|
|
mtx_destroy(&sc->mutex);
|
|
return (ENXIO);
|
|
}
|
|
|
|
sc->bst = rman_get_bustag(sc->res);
|
|
sc->bsh = rman_get_bushandle(sc->res);
|
|
|
|
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 (IIC_NOERR);
|
|
}
|
|
|
|
static int
|
|
i2c_repeated_start(device_t dev, u_char slave, int timeout)
|
|
{
|
|
struct i2c_softc *sc;
|
|
int error;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
mtx_lock(&sc->mutex);
|
|
|
|
i2c_write_reg(sc, I2C_ADDR_REG, slave);
|
|
if ((i2c_read_reg(sc, I2C_STATUS_REG) & I2CSR_MBB) == 0) {
|
|
mtx_unlock(&sc->mutex);
|
|
return (IIC_EBUSBSY);
|
|
}
|
|
|
|
/* Set repeated start condition */
|
|
DELAY(10);
|
|
i2c_flag_set(sc, I2C_CONTROL_REG, I2CCR_RSTA);
|
|
DELAY(10);
|
|
/* Clear status */
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
/* Write target address - LSB is R/W bit */
|
|
i2c_write_reg(sc, I2C_DATA_REG, slave);
|
|
|
|
error = wait_for_iif(sc);
|
|
|
|
/* Clear status */
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
|
|
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;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
mtx_lock(&sc->mutex);
|
|
i2c_write_reg(sc, I2C_ADDR_REG, slave);
|
|
if (i2c_read_reg(sc, I2C_STATUS_REG) & I2CSR_MBB) {
|
|
mtx_unlock(&sc->mutex);
|
|
return (IIC_EBUSBSY);
|
|
}
|
|
|
|
/* Set start condition */
|
|
i2c_write_reg(sc, I2C_CONTROL_REG,
|
|
I2CCR_MEN | I2CCR_MSTA | I2CCR_TXAK);
|
|
DELAY(100);
|
|
i2c_write_reg(sc, I2C_CONTROL_REG,
|
|
I2CCR_MEN | I2CCR_MSTA | I2CCR_MTX | I2CCR_TXAK);
|
|
/* Clear status */
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
/* Write target address - LSB is R/W bit */
|
|
i2c_write_reg(sc, I2C_DATA_REG, slave);
|
|
|
|
error = wait_for_iif(sc);
|
|
|
|
mtx_unlock(&sc->mutex);
|
|
if (error)
|
|
return (error);
|
|
|
|
return (IIC_NOERR);
|
|
}
|
|
|
|
|
|
static int
|
|
i2c_stop(device_t dev)
|
|
{
|
|
struct i2c_softc *sc;
|
|
|
|
sc = device_get_softc(dev);
|
|
mtx_lock(&sc->mutex);
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_TXAK);
|
|
DELAY(100);
|
|
/* Reset controller if bus still busy after STOP */
|
|
if (wait_for_nibb(sc) == IIC_ETIMEOUT) {
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, 0);
|
|
DELAY(1000);
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN | I2CCR_TXAK);
|
|
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
}
|
|
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;
|
|
u_int busfreq, div, i, ipgfreq;
|
|
|
|
sc = device_get_softc(dev);
|
|
|
|
/*
|
|
* 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, (uint8_t)div);
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN);
|
|
DELAY(1000);
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
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, reg;
|
|
|
|
sc = device_get_softc(dev);
|
|
*read = 0;
|
|
|
|
mtx_lock(&sc->mutex);
|
|
|
|
if (len) {
|
|
if (len == 1)
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN |
|
|
I2CCR_MSTA | I2CCR_TXAK);
|
|
|
|
else
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN |
|
|
I2CCR_MSTA);
|
|
|
|
/* dummy read */
|
|
i2c_read_reg(sc, I2C_DATA_REG);
|
|
DELAY(1000);
|
|
}
|
|
|
|
while (*read < len) {
|
|
error = wait_for_icf(sc);
|
|
if (error) {
|
|
mtx_unlock(&sc->mutex);
|
|
return (error);
|
|
}
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
if ((*read == len - 2) && last) {
|
|
/* NO ACK on last byte */
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN |
|
|
I2CCR_MSTA | I2CCR_TXAK);
|
|
}
|
|
|
|
if ((*read == len - 1) && last) {
|
|
/* Transfer done, remove master bit */
|
|
i2c_write_reg(sc, I2C_CONTROL_REG, I2CCR_MEN |
|
|
I2CCR_TXAK);
|
|
}
|
|
|
|
reg = i2c_read_reg(sc, I2C_DATA_REG);
|
|
*buf++ = reg;
|
|
(*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);
|
|
*sent = 0;
|
|
|
|
mtx_lock(&sc->mutex);
|
|
while (*sent < len) {
|
|
i2c_write_reg(sc, I2C_STATUS_REG, 0x0);
|
|
i2c_write_reg(sc, I2C_DATA_REG, *buf++);
|
|
|
|
error = wait_for_iif(sc);
|
|
if (error) {
|
|
mtx_unlock(&sc->mutex);
|
|
return (error);
|
|
}
|
|
|
|
(*sent)++;
|
|
}
|
|
mtx_unlock(&sc->mutex);
|
|
|
|
return (IIC_NOERR);
|
|
}
|