459dfa0dd2
First, SCL low timeout is set to 25 milliseconds by default as opposed to 1 millisecond before. The new value is based on the SMBus specification. The timeout can be changed on a per bus basis using dev.iicbb.N.scl_low_timeout sysctl. The driver uses DELAY to wait for high SCL up to 1 millisecond, then it switches to pause_sbt(SBT_1MS) for the rest of the timeout. While here I made a number of other changes. 'udelay' that's used for timing clock and data signals is now calculated based on the requested bus frequency (dev.iicbus.N.frequency) instead of being hardcoded to 10 microseconds. The calculations are done in such a fashion that the default bus frequency of 100000 is converted to udelay of 10 us. This is for backward compatibility. The actual frequency will be less than a quarter (I think) of the requested frequency. Also, I added detection of stuck low SCL in a few places. Previously, the code would just carry on after the SCL low timeout and that might potentially lead to misinterpreted bits. Finally, I fixed several style issues near the code that I changed. Many more are still remaining. Tested by accessing HTU21 temperature and humidity sensor in this setup: superio0: <Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. B+)> at port 0x2e-0x2f on isa0 gpio1: <Nuvoton GPIO controller> at GPIO ldn 0x07 on superio0 pcib0: allocated type 4 (0x220-0x226) for rid 0 of gpio1 gpiobus1: <GPIO bus> on gpio1 gpioiic0: <GPIO I2C bit-banging driver> at pins 14-15 on gpiobus1 gpioiic0: SCL pin: 14, SDA pin: 15 iicbb0: <I2C bit-banging driver> on gpioiic0 iicbus0: <Philips I2C bus> on iicbb0 master-only iic0: <I2C generic I/O> on iicbus0 Discussed with: ian, imp MFC after: 3 weeks Differential Revision: https://reviews.freebsd.org/D22109
514 lines
12 KiB
C
514 lines
12 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 1998, 2001 Nicolas Souchu
|
|
* All rights reserved.
|
|
*
|
|
* 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 THE 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 THE 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$");
|
|
|
|
/*
|
|
* Generic I2C bit-banging code
|
|
*
|
|
* Example:
|
|
*
|
|
* iicbus
|
|
* / \
|
|
* iicbb pcf
|
|
* | \
|
|
* bti2c lpbb
|
|
*
|
|
* From Linux I2C generic interface
|
|
* (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
|
|
*
|
|
*/
|
|
|
|
#include "opt_platform.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/module.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/uio.h>
|
|
|
|
#ifdef FDT
|
|
#include <dev/ofw/ofw_bus.h>
|
|
#include <dev/ofw/ofw_bus_subr.h>
|
|
#include <dev/fdt/fdt_common.h>
|
|
#endif
|
|
|
|
#include <dev/iicbus/iiconf.h>
|
|
#include <dev/iicbus/iicbus.h>
|
|
|
|
#include <dev/smbus/smbconf.h>
|
|
|
|
#include "iicbus_if.h"
|
|
#include "iicbb_if.h"
|
|
|
|
/* Based on the SMBus specification. */
|
|
#define DEFAULT_SCL_LOW_TIMEOUT (25 * 1000)
|
|
|
|
struct iicbb_softc {
|
|
device_t iicbus;
|
|
u_int udelay; /* signal toggle delay in usec */
|
|
u_int scl_low_timeout;
|
|
};
|
|
|
|
static int iicbb_attach(device_t);
|
|
static void iicbb_child_detached(device_t, device_t);
|
|
static int iicbb_detach(device_t);
|
|
static int iicbb_print_child(device_t, device_t);
|
|
static int iicbb_probe(device_t);
|
|
|
|
static int iicbb_callback(device_t, int, caddr_t);
|
|
static int iicbb_start(device_t, u_char, int);
|
|
static int iicbb_stop(device_t);
|
|
static int iicbb_write(device_t, const char *, int, int *, int);
|
|
static int iicbb_read(device_t, char *, int, int *, int, int);
|
|
static int iicbb_reset(device_t, u_char, u_char, u_char *);
|
|
static int iicbb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs);
|
|
static void iicbb_set_speed(struct iicbb_softc *sc, u_char);
|
|
#ifdef FDT
|
|
static phandle_t iicbb_get_node(device_t, device_t);
|
|
#endif
|
|
|
|
static device_method_t iicbb_methods[] = {
|
|
/* device interface */
|
|
DEVMETHOD(device_probe, iicbb_probe),
|
|
DEVMETHOD(device_attach, iicbb_attach),
|
|
DEVMETHOD(device_detach, iicbb_detach),
|
|
|
|
/* bus interface */
|
|
DEVMETHOD(bus_child_detached, iicbb_child_detached),
|
|
DEVMETHOD(bus_print_child, iicbb_print_child),
|
|
|
|
/* iicbus interface */
|
|
DEVMETHOD(iicbus_callback, iicbb_callback),
|
|
DEVMETHOD(iicbus_start, iicbb_start),
|
|
DEVMETHOD(iicbus_repeated_start, iicbb_start),
|
|
DEVMETHOD(iicbus_stop, iicbb_stop),
|
|
DEVMETHOD(iicbus_write, iicbb_write),
|
|
DEVMETHOD(iicbus_read, iicbb_read),
|
|
DEVMETHOD(iicbus_reset, iicbb_reset),
|
|
DEVMETHOD(iicbus_transfer, iicbb_transfer),
|
|
|
|
#ifdef FDT
|
|
/* ofw_bus interface */
|
|
DEVMETHOD(ofw_bus_get_node, iicbb_get_node),
|
|
#endif
|
|
|
|
{ 0, 0 }
|
|
};
|
|
|
|
driver_t iicbb_driver = {
|
|
"iicbb",
|
|
iicbb_methods,
|
|
sizeof(struct iicbb_softc),
|
|
};
|
|
|
|
devclass_t iicbb_devclass;
|
|
|
|
static int
|
|
iicbb_probe(device_t dev)
|
|
{
|
|
device_set_desc(dev, "I2C bit-banging driver");
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iicbb_attach(device_t dev)
|
|
{
|
|
struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev);
|
|
|
|
sc->iicbus = device_add_child(dev, "iicbus", -1);
|
|
if (!sc->iicbus)
|
|
return (ENXIO);
|
|
|
|
sc->scl_low_timeout = DEFAULT_SCL_LOW_TIMEOUT;
|
|
|
|
SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"delay", CTLFLAG_RD, &sc->udelay,
|
|
0, "Signal change delay controlled by bus frequency, microseconds");
|
|
|
|
SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev),
|
|
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
|
|
"scl_low_timeout", CTLFLAG_RWTUN, &sc->scl_low_timeout,
|
|
0, "SCL low timeout, microseconds");
|
|
bus_generic_attach(dev);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iicbb_detach(device_t dev)
|
|
{
|
|
|
|
bus_generic_detach(dev);
|
|
device_delete_children(dev);
|
|
|
|
return (0);
|
|
}
|
|
|
|
#ifdef FDT
|
|
static phandle_t
|
|
iicbb_get_node(device_t bus, device_t dev)
|
|
{
|
|
|
|
/* We only have one child, the I2C bus, which needs our own node. */
|
|
return (ofw_bus_get_node(bus));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
iicbb_child_detached( device_t dev, device_t child )
|
|
{
|
|
struct iicbb_softc *sc = (struct iicbb_softc *)device_get_softc(dev);
|
|
|
|
if (child == sc->iicbus)
|
|
sc->iicbus = NULL;
|
|
}
|
|
|
|
static int
|
|
iicbb_print_child(device_t bus, device_t dev)
|
|
{
|
|
int error;
|
|
int retval = 0;
|
|
u_char oldaddr;
|
|
|
|
retval += bus_print_child_header(bus, dev);
|
|
/* retrieve the interface I2C address */
|
|
error = IICBB_RESET(device_get_parent(bus), IIC_FASTEST, 0, &oldaddr);
|
|
if (error == IIC_ENOADDR) {
|
|
retval += printf(" on %s master-only\n",
|
|
device_get_nameunit(bus));
|
|
} else {
|
|
/* restore the address */
|
|
IICBB_RESET(device_get_parent(bus), IIC_FASTEST, oldaddr, NULL);
|
|
|
|
retval += printf(" on %s addr 0x%x\n",
|
|
device_get_nameunit(bus), oldaddr & 0xff);
|
|
}
|
|
|
|
return (retval);
|
|
}
|
|
|
|
#define I2C_GETSDA(dev) (IICBB_GETSDA(device_get_parent(dev)))
|
|
#define I2C_SETSDA(dev, x) (IICBB_SETSDA(device_get_parent(dev), x))
|
|
#define I2C_GETSCL(dev) (IICBB_GETSCL(device_get_parent(dev)))
|
|
#define I2C_SETSCL(dev, x) (IICBB_SETSCL(device_get_parent(dev), x))
|
|
|
|
#define I2C_SET(sc, dev, ctrl, val) do { \
|
|
iicbb_setscl(dev, ctrl); \
|
|
I2C_SETSDA(dev, val); \
|
|
DELAY(sc->udelay); \
|
|
} while (0)
|
|
|
|
static int i2c_debug = 0;
|
|
#define I2C_DEBUG(x) do { \
|
|
if (i2c_debug) (x); \
|
|
} while (0)
|
|
|
|
#define I2C_LOG(format,args...) do { \
|
|
printf(format, args); \
|
|
} while (0)
|
|
|
|
static void
|
|
iicbb_setscl(device_t dev, int val)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
sbintime_t now, end;
|
|
int fast_timeout;
|
|
|
|
I2C_SETSCL(dev, val);
|
|
DELAY(sc->udelay);
|
|
|
|
/* Pulling low cannot fail. */
|
|
if (!val)
|
|
return;
|
|
|
|
/* Use DELAY for up to 1 ms, then switch to pause. */
|
|
end = sbinuptime() + sc->scl_low_timeout * SBT_1US;
|
|
fast_timeout = MIN(sc->scl_low_timeout, 1000);
|
|
while (fast_timeout > 0) {
|
|
if (I2C_GETSCL(dev))
|
|
return;
|
|
I2C_SETSCL(dev, 1); /* redundant ? */
|
|
DELAY(sc->udelay);
|
|
fast_timeout -= sc->udelay;
|
|
}
|
|
|
|
while (!I2C_GETSCL(dev)) {
|
|
now = sbinuptime();
|
|
if (now >= end)
|
|
break;
|
|
pause_sbt("iicbb-scl-low", SBT_1MS, C_PREL(8), 0);
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
iicbb_one(device_t dev, int timeout)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
|
|
I2C_SET(sc,dev,0,1);
|
|
I2C_SET(sc,dev,1,1);
|
|
I2C_SET(sc,dev,0,1);
|
|
return;
|
|
}
|
|
|
|
static void
|
|
iicbb_zero(device_t dev, int timeout)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
|
|
I2C_SET(sc,dev,0,0);
|
|
I2C_SET(sc,dev,1,0);
|
|
I2C_SET(sc,dev,0,0);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Waiting for ACKNOWLEDGE.
|
|
*
|
|
* When a chip is being addressed or has received data it will issue an
|
|
* ACKNOWLEDGE pulse. Therefore the MASTER must release the DATA line
|
|
* (set it to high level) and then release the CLOCK line.
|
|
* Now it must wait for the SLAVE to pull the DATA line low.
|
|
* Actually on the bus this looks like a START condition so nothing happens
|
|
* because of the fact that the IC's that have not been addressed are doing
|
|
* nothing.
|
|
*
|
|
* When the SLAVE has pulled this line low the MASTER will take the CLOCK
|
|
* line low and then the SLAVE will release the SDA (data) line.
|
|
*/
|
|
static int
|
|
iicbb_ack(device_t dev, int timeout)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
int noack;
|
|
int k = 0;
|
|
|
|
I2C_SET(sc,dev,0,1);
|
|
I2C_SET(sc,dev,1,1);
|
|
|
|
/* SCL must be high now. */
|
|
if (!I2C_GETSCL(dev))
|
|
return (IIC_ETIMEOUT);
|
|
|
|
do {
|
|
noack = I2C_GETSDA(dev);
|
|
if (!noack)
|
|
break;
|
|
DELAY(1);
|
|
k++;
|
|
} while (k < timeout);
|
|
|
|
I2C_SET(sc,dev,0,1);
|
|
I2C_DEBUG(printf("%c ",noack?'-':'+'));
|
|
|
|
return (noack ? IIC_ENOACK : 0);
|
|
}
|
|
|
|
static void
|
|
iicbb_sendbyte(device_t dev, u_char data, int timeout)
|
|
{
|
|
int i;
|
|
|
|
for (i=7; i>=0; i--) {
|
|
if (data&(1<<i)) {
|
|
iicbb_one(dev, timeout);
|
|
} else {
|
|
iicbb_zero(dev, timeout);
|
|
}
|
|
}
|
|
I2C_DEBUG(printf("w%02x",(int)data));
|
|
return;
|
|
}
|
|
|
|
static u_char
|
|
iicbb_readbyte(device_t dev, int last, int timeout)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
int i;
|
|
unsigned char data=0;
|
|
|
|
I2C_SET(sc,dev,0,1);
|
|
for (i=7; i>=0; i--)
|
|
{
|
|
I2C_SET(sc,dev,1,1);
|
|
if (I2C_GETSDA(dev))
|
|
data |= (1<<i);
|
|
I2C_SET(sc,dev,0,1);
|
|
}
|
|
if (last) {
|
|
iicbb_one(dev, timeout);
|
|
} else {
|
|
iicbb_zero(dev, timeout);
|
|
}
|
|
I2C_DEBUG(printf("r%02x%c ",(int)data,last?'-':'+'));
|
|
return (data);
|
|
}
|
|
|
|
static int
|
|
iicbb_callback(device_t dev, int index, caddr_t data)
|
|
{
|
|
return (IICBB_CALLBACK(device_get_parent(dev), index, data));
|
|
}
|
|
|
|
static int
|
|
iicbb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
|
|
{
|
|
iicbb_set_speed(device_get_softc(dev), speed);
|
|
return (IICBB_RESET(device_get_parent(dev), speed, addr, oldaddr));
|
|
}
|
|
|
|
static int
|
|
iicbb_start(device_t dev, u_char slave, int timeout)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
int error;
|
|
|
|
I2C_DEBUG(printf("<"));
|
|
|
|
I2C_SET(sc,dev,1,1);
|
|
|
|
/* SCL must be high now. */
|
|
if (!I2C_GETSCL(dev))
|
|
return (IIC_ETIMEOUT);
|
|
|
|
I2C_SET(sc,dev,1,0);
|
|
I2C_SET(sc,dev,0,0);
|
|
|
|
/* send address */
|
|
iicbb_sendbyte(dev, slave, timeout);
|
|
|
|
/* check for ack */
|
|
error = iicbb_ack(dev, timeout);
|
|
if (error == 0)
|
|
return (0);
|
|
|
|
iicbb_stop(dev);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iicbb_stop(device_t dev)
|
|
{
|
|
struct iicbb_softc *sc = device_get_softc(dev);
|
|
|
|
I2C_SET(sc,dev,0,0);
|
|
I2C_SET(sc,dev,1,0);
|
|
I2C_SET(sc,dev,1,1);
|
|
I2C_DEBUG(printf(">"));
|
|
I2C_DEBUG(printf("\n"));
|
|
|
|
/* SCL must be high now. */
|
|
if (!I2C_GETSCL(dev))
|
|
return (IIC_ETIMEOUT);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iicbb_write(device_t dev, const char *buf, int len, int *sent, int timeout)
|
|
{
|
|
int bytes, error = 0;
|
|
|
|
bytes = 0;
|
|
while (len) {
|
|
/* send byte */
|
|
iicbb_sendbyte(dev,(u_char)*buf++, timeout);
|
|
|
|
/* check for ack */
|
|
error = iicbb_ack(dev, timeout);
|
|
if (error != 0)
|
|
break;
|
|
bytes++;
|
|
len--;
|
|
}
|
|
|
|
*sent = bytes;
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
iicbb_read(device_t dev, char * buf, int len, int *read, int last, int delay)
|
|
{
|
|
int bytes;
|
|
|
|
bytes = 0;
|
|
while (len) {
|
|
/* XXX should insert delay here */
|
|
*buf++ = (char)iicbb_readbyte(dev, (len == 1) ? last : 0, delay);
|
|
|
|
bytes ++;
|
|
len --;
|
|
}
|
|
|
|
*read = bytes;
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
iicbb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
|
|
{
|
|
int error;
|
|
|
|
error = IICBB_PRE_XFER(device_get_parent(dev));
|
|
if (error)
|
|
return (error);
|
|
|
|
error = iicbus_transfer_gen(dev, msgs, nmsgs);
|
|
|
|
IICBB_POST_XFER(device_get_parent(dev));
|
|
return (error);
|
|
}
|
|
|
|
static void
|
|
iicbb_set_speed(struct iicbb_softc *sc, u_char speed)
|
|
{
|
|
u_int busfreq, period;
|
|
|
|
/*
|
|
* NB: the resulting frequency will be a quarter (even less) of the
|
|
* configured bus frequency. This is for historic reasons. The default
|
|
* bus frequency is 100 kHz. And the historic default udelay is 10
|
|
* microseconds. The cycle of sending a bit takes four udelay-s plus
|
|
* SCL is kept low for extra two udelay-s. The actual I/O toggling also
|
|
* has an overhead.
|
|
*/
|
|
busfreq = IICBUS_GET_FREQUENCY(sc->iicbus, speed);
|
|
period = 1000000 / busfreq; /* Hz -> uS */
|
|
sc->udelay = MAX(period, 1);
|
|
}
|
|
|
|
DRIVER_MODULE(iicbus, iicbb, iicbus_driver, iicbus_devclass, 0, 0);
|
|
|
|
MODULE_DEPEND(iicbb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
|
|
MODULE_VERSION(iicbb, IICBB_MODVER);
|