2006-11-26 11:13:41 -07:00
|
|
|
/*
|
|
|
|
* Copyright (C) 1998 Itai Nahshon, Michael Schimek
|
|
|
|
*
|
|
|
|
* The original code was derived from and inspired by
|
|
|
|
* the I2C driver from the Linux kernel.
|
|
|
|
* (c) 1998 Gerd Knorr <kraxel@cs.tu-berlin.de>
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_XORG_CONFIG_H
|
|
|
|
#include <xorg-config.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "misc.h"
|
|
|
|
#include "xf86.h"
|
|
|
|
#include "xf86_OSproc.h"
|
|
|
|
|
|
|
|
#include <X11/X.h>
|
|
|
|
#include <X11/Xos.h>
|
|
|
|
#include <X11/Xproto.h>
|
|
|
|
#include "scrnintstr.h"
|
|
|
|
#include "regionstr.h"
|
|
|
|
#include "windowstr.h"
|
|
|
|
#include "pixmapstr.h"
|
|
|
|
#include "validate.h"
|
|
|
|
#include "resource.h"
|
|
|
|
#include "gcstruct.h"
|
|
|
|
#include "dixstruct.h"
|
|
|
|
|
|
|
|
#include "xf86i2c.h"
|
|
|
|
|
|
|
|
#define I2C_TIMEOUT(x) /*(x)*/ /* Report timeouts */
|
|
|
|
#define I2C_TRACE(x) /*(x)*/ /* Report progress */
|
|
|
|
|
|
|
|
/* This is the default I2CUDelay function if not supplied by the driver.
|
|
|
|
* High level I2C interfaces implementing the bus protocol in hardware
|
|
|
|
* should supply this function too.
|
|
|
|
*
|
|
|
|
* Delay execution at least usec microseconds.
|
|
|
|
* All values 0 to 1e6 inclusive must be expected.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
I2CUDelay(I2CBusPtr b, int usec)
|
|
|
|
{
|
|
|
|
struct timeval begin, cur;
|
|
|
|
long d_secs, d_usecs;
|
|
|
|
long diff;
|
|
|
|
|
|
|
|
if (usec > 0) {
|
|
|
|
X_GETTIMEOFDAY(&begin);
|
|
|
|
do {
|
|
|
|
/* It would be nice to use {xf86}usleep,
|
|
|
|
* but usleep (1) takes >10000 usec !
|
|
|
|
*/
|
|
|
|
X_GETTIMEOFDAY(&cur);
|
|
|
|
d_secs = (cur.tv_sec - begin.tv_sec);
|
|
|
|
d_usecs = (cur.tv_usec - begin.tv_usec);
|
|
|
|
diff = d_secs*1000000 + d_usecs;
|
|
|
|
} while (diff>=0 && diff< (usec + 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Most drivers will register just with GetBits/PutBits functions.
|
|
|
|
* The following functions implement a software I2C protocol
|
|
|
|
* by using the promitive functions given by the driver.
|
|
|
|
* ================================================================
|
|
|
|
*
|
|
|
|
* It is assumed that there is just one master on the I2C bus, therefore
|
|
|
|
* there is no explicit test for conflits.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#define RISEFALLTIME 2 /* usec, actually 300 to 1000 ns according to the i2c specs */
|
|
|
|
|
|
|
|
/* Some devices will hold SCL low to slow down the bus or until
|
|
|
|
* ready for transmission.
|
|
|
|
*
|
|
|
|
* This condition will be noticed when the master tries to raise
|
|
|
|
* the SCL line. You can set the timeout to zero if the slave device
|
|
|
|
* does not support this clock synchronization.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CRaiseSCL(I2CBusPtr b, int sda, int timeout)
|
|
|
|
{
|
|
|
|
int i, scl;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 1, sda);
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
|
|
|
|
for (i = timeout; i > 0; i -= b->RiseFallTime) {
|
|
|
|
b->I2CGetBits(b, &scl, &sda);
|
|
|
|
if (scl) break;
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i <= 0) {
|
|
|
|
I2C_TIMEOUT(ErrorF("[I2CRaiseSCL(<%s>, %d, %d) timeout]", b->BusName, sda, timeout));
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Send a start signal on the I2C bus. The start signal notifies
|
|
|
|
* devices that a new transaction is initiated by the bus master.
|
|
|
|
*
|
|
|
|
* The start signal is always followed by a slave address.
|
|
|
|
* Slave addresses are 8+ bits. The first 7 bits identify the
|
|
|
|
* device and the last bit signals if this is a read (1) or
|
|
|
|
* write (0) operation.
|
|
|
|
*
|
|
|
|
* There may be more than one start signal on one transaction.
|
|
|
|
* This happens for example on some devices that allow reading
|
|
|
|
* of registers. First send a start bit followed by the device
|
|
|
|
* address (with the last bit 0) and the register number. Then send
|
|
|
|
* a new start bit with the device address (with the last bit 1)
|
|
|
|
* and then read the value from the device.
|
|
|
|
*
|
|
|
|
* Note this is function does not implement a multiple master
|
|
|
|
* arbitration procedure.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CStart(I2CBusPtr b, int timeout)
|
|
|
|
{
|
|
|
|
if (!I2CRaiseSCL(b, 1, timeout))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 1, 0);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
b->I2CPutBits(b, 0, 0);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
I2C_TRACE(ErrorF("\ni2c: <"));
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is the default I2CStop function if not supplied by the driver.
|
|
|
|
*
|
|
|
|
* Signal devices on the I2C bus that a transaction on the
|
|
|
|
* bus has finished. There may be more than one start signal
|
|
|
|
* on a transaction but only one stop signal.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static void
|
|
|
|
I2CStop(I2CDevPtr d)
|
|
|
|
{
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, 0);
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 1, 0);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
b->I2CPutBits(b, 1, 1);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
I2C_TRACE(ErrorF(">\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write/Read a single bit to/from a device.
|
|
|
|
* Return FALSE if a timeout occurs.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CWriteBit(I2CBusPtr b, int sda, int timeout)
|
|
|
|
{
|
|
|
|
Bool r;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, sda);
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
|
|
|
|
r = I2CRaiseSCL(b, sda, timeout);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, sda);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CReadBit(I2CBusPtr b, int *psda, int timeout)
|
|
|
|
{
|
|
|
|
Bool r;
|
|
|
|
int scl;
|
|
|
|
|
|
|
|
r = I2CRaiseSCL(b, 1, timeout);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
b->I2CGetBits(b, &scl, psda);
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, 1);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is the default I2CPutByte function if not supplied by the driver.
|
|
|
|
*
|
|
|
|
* A single byte is sent to the device.
|
|
|
|
* The function returns FALSE if a timeout occurs, you should send
|
|
|
|
* a stop condition afterwards to reset the bus.
|
|
|
|
*
|
|
|
|
* A timeout occurs,
|
|
|
|
* if the slave pulls SCL to slow down the bus more than ByteTimeout usecs,
|
|
|
|
* or slows down the bus for more than BitTimeout usecs for each bit,
|
|
|
|
* or does not send an ACK bit (0) to acknowledge the transmission within
|
|
|
|
* AcknTimeout usecs, but a NACK (1) bit.
|
|
|
|
*
|
|
|
|
* AcknTimeout must be at least b->HoldTime, the other timeouts can be
|
|
|
|
* zero according to the comment on I2CRaiseSCL.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CPutByte(I2CDevPtr d, I2CByte data)
|
|
|
|
{
|
|
|
|
Bool r;
|
|
|
|
int i, scl, sda;
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
|
|
|
|
if (!I2CWriteBit(b, (data >> 7) & 1, d->ByteTimeout))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
for (i = 6; i >= 0; i--)
|
|
|
|
if (!I2CWriteBit(b, (data >> i) & 1, d->BitTimeout))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, 1);
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
|
|
|
|
r = I2CRaiseSCL(b, 1, b->HoldTime);
|
|
|
|
|
|
|
|
if (r) {
|
|
|
|
for (i = d->AcknTimeout; i > 0; i -= b->HoldTime) {
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
b->I2CGetBits(b, &scl, &sda);
|
|
|
|
if (sda == 0) break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i <= 0) {
|
|
|
|
I2C_TIMEOUT(ErrorF("[I2CPutByte(<%s>, 0x%02x, %d, %d, %d) timeout]",
|
|
|
|
b->BusName, data, d->BitTimeout,
|
|
|
|
d->ByteTimeout, d->AcknTimeout));
|
|
|
|
r = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
I2C_TRACE(ErrorF("W%02x%c ", (int) data, sda ? '-' : '+'));
|
|
|
|
}
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, 1);
|
|
|
|
b->I2CUDelay(b, b->HoldTime);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is the default I2CGetByte function if not supplied by the driver.
|
|
|
|
*
|
|
|
|
* A single byte is read from the device.
|
|
|
|
* The function returns FALSE if a timeout occurs, you should send
|
|
|
|
* a stop condition afterwards to reset the bus.
|
|
|
|
*
|
|
|
|
* A timeout occurs,
|
|
|
|
* if the slave pulls SCL to slow down the bus more than ByteTimeout usecs,
|
|
|
|
* or slows down the bus for more than b->BitTimeout usecs for each bit.
|
|
|
|
*
|
|
|
|
* ByteTimeout must be at least b->HoldTime, the other timeouts can be
|
|
|
|
* zero according to the comment on I2CRaiseSCL.
|
|
|
|
*
|
|
|
|
* For the <last> byte in a sequence the acknowledge bit NACK (1),
|
|
|
|
* otherwise ACK (0) will be sent.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CGetByte(I2CDevPtr d, I2CByte *data, Bool last)
|
|
|
|
{
|
|
|
|
int i, sda;
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
|
|
|
|
b->I2CPutBits(b, 0, 1);
|
|
|
|
b->I2CUDelay(b, b->RiseFallTime);
|
|
|
|
|
|
|
|
if (!I2CReadBit(b, &sda, d->ByteTimeout))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
*data = (sda > 0) << 7;
|
|
|
|
|
|
|
|
for (i = 6; i >= 0; i--)
|
|
|
|
if (!I2CReadBit(b, &sda, d->BitTimeout))
|
|
|
|
return FALSE;
|
|
|
|
else
|
|
|
|
*data |= (sda > 0) << i;
|
|
|
|
|
|
|
|
if (!I2CWriteBit(b, last ? 1 : 0, d->BitTimeout))
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
I2C_TRACE(ErrorF("R%02x%c ", (int) *data, last ? '+' : '-'));
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* This is the default I2CAddress function if not supplied by the driver.
|
|
|
|
*
|
|
|
|
* It creates the start condition, followed by the d->SlaveAddr.
|
|
|
|
* Higher level functions must call this routine rather than
|
|
|
|
* I2CStart/PutByte because a hardware I2C master may not be able
|
|
|
|
* to send a slave address without a start condition.
|
|
|
|
*
|
|
|
|
* The same timeouts apply as with I2CPutByte and additional a
|
|
|
|
* StartTimeout, similar to the ByteTimeout but for the start
|
|
|
|
* condition.
|
|
|
|
*
|
|
|
|
* In case of a timeout, the bus is left in a clean idle condition.
|
|
|
|
* I. e. you *must not* send a Stop. If this function succeeds, you *must*.
|
|
|
|
*
|
|
|
|
* The slave address format is 16 bit, with the legacy _8_bit_ slave address
|
|
|
|
* in the least significant byte. This is, the slave address must include the
|
|
|
|
* R/_W flag as least significant bit.
|
|
|
|
*
|
|
|
|
* The most significant byte of the address will be sent _after_ the LSB,
|
|
|
|
* but only if the LSB indicates:
|
|
|
|
* a) an 11 bit address, this is LSB = 1111 0xxx.
|
|
|
|
* b) a 'general call address', this is LSB = 0000 000x - see the I2C specs
|
|
|
|
* for more.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CAddress(I2CDevPtr d, I2CSlaveAddr addr)
|
|
|
|
{
|
|
|
|
if (I2CStart(d->pI2CBus, d->StartTimeout)) {
|
|
|
|
if (I2CPutByte(d, addr & 0xFF)) {
|
|
|
|
if ((addr & 0xF8) != 0xF0 &&
|
|
|
|
(addr & 0xFE) != 0x00)
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
if (I2CPutByte(d, (addr >> 8) & 0xFF))
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
I2CStop(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* These are the hardware independent I2C helper functions.
|
|
|
|
* ========================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Function for probing. Just send the slave address
|
|
|
|
* and return true if the device responds. The slave address
|
|
|
|
* must have the lsb set to reflect a read (1) or write (0) access.
|
|
|
|
* Don't expect a read- or write-only device will respond otherwise.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CProbeAddress(I2CBusPtr b, I2CSlaveAddr addr)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
I2CDevRec d;
|
|
|
|
|
|
|
|
d.DevName = "Probing";
|
|
|
|
d.BitTimeout = b->BitTimeout;
|
|
|
|
d.ByteTimeout = b->ByteTimeout;
|
|
|
|
d.AcknTimeout = b->AcknTimeout;
|
|
|
|
d.StartTimeout = b->StartTimeout;
|
|
|
|
d.SlaveAddr = addr;
|
|
|
|
d.pI2CBus = b;
|
|
|
|
d.NextDev = NULL;
|
|
|
|
|
|
|
|
r = b->I2CAddress(&d, addr);
|
|
|
|
|
|
|
|
if (r) b->I2CStop(&d);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* All functions below are related to devices and take the
|
|
|
|
* slave address and timeout values from an I2CDevRec. They
|
|
|
|
* return FALSE in case of an error (presumably a timeout).
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* General purpose read and write function.
|
|
|
|
*
|
|
|
|
* 1st, if nWrite > 0
|
|
|
|
* Send a start condition
|
|
|
|
* Send the slave address (1 or 2 bytes) with write flag
|
|
|
|
* Write n bytes from WriteBuffer
|
|
|
|
* 2nd, if nRead > 0
|
|
|
|
* Send a start condition [again]
|
|
|
|
* Send the slave address (1 or 2 bytes) with read flag
|
|
|
|
* Read n bytes to ReadBuffer
|
|
|
|
* 3rd, if a Start condition has been successfully sent,
|
|
|
|
* Send a Stop condition.
|
|
|
|
*
|
|
|
|
* The functions exits immediately when an error occures,
|
|
|
|
* not proceeding any data left. However, step 3 will
|
|
|
|
* be executed anyway to leave the bus in clean idle state.
|
|
|
|
*/
|
|
|
|
|
|
|
|
static Bool
|
|
|
|
I2CWriteRead(I2CDevPtr d,
|
|
|
|
I2CByte *WriteBuffer, int nWrite,
|
|
|
|
I2CByte *ReadBuffer, int nRead)
|
|
|
|
{
|
|
|
|
Bool r = TRUE;
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
int s = 0;
|
|
|
|
|
|
|
|
if (r && nWrite > 0) {
|
|
|
|
r = b->I2CAddress(d, d->SlaveAddr & ~1);
|
|
|
|
if (r) {
|
|
|
|
for (; nWrite > 0; WriteBuffer++, nWrite--)
|
|
|
|
if (!(r = b->I2CPutByte(d, *WriteBuffer)))
|
|
|
|
break;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (r && nRead > 0) {
|
|
|
|
r = b->I2CAddress(d, d->SlaveAddr | 1);
|
|
|
|
if (r) {
|
|
|
|
for (; nRead > 0; ReadBuffer++, nRead--)
|
|
|
|
if (!(r = b->I2CGetByte(d, ReadBuffer, nRead == 1)))
|
|
|
|
break;
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s) b->I2CStop(d);
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* wrapper - for compatibility and convinience */
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CWriteRead(I2CDevPtr d,
|
|
|
|
I2CByte *WriteBuffer, int nWrite,
|
|
|
|
I2CByte *ReadBuffer, int nRead)
|
|
|
|
{
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
return b->I2CWriteRead(d,WriteBuffer,nWrite,ReadBuffer,nRead);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read a byte, the only readable register of a device.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CReadStatus(I2CDevPtr d, I2CByte *pbyte)
|
|
|
|
{
|
|
|
|
return xf86I2CWriteRead(d, NULL, 0, pbyte, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read a byte from one of the registers determined by its sub-address.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CReadByte(I2CDevPtr d, I2CByte subaddr, I2CByte *pbyte)
|
|
|
|
{
|
|
|
|
return xf86I2CWriteRead(d, &subaddr, 1, pbyte, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read bytes from subsequent registers determined by the
|
|
|
|
* sub-address of the first register.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CReadBytes(I2CDevPtr d, I2CByte subaddr, I2CByte *pbyte, int n)
|
|
|
|
{
|
|
|
|
return xf86I2CWriteRead(d, &subaddr, 1, pbyte, n);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Read a word (high byte, then low byte) from one of the registers
|
|
|
|
* determined by its sub-address.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CReadWord(I2CDevPtr d, I2CByte subaddr, unsigned short *pword)
|
|
|
|
{
|
|
|
|
I2CByte rb[2];
|
|
|
|
|
|
|
|
if (!xf86I2CWriteRead(d, &subaddr, 1, rb, 2)) return FALSE;
|
|
|
|
|
|
|
|
*pword = (rb[0] << 8) | rb[1];
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write a byte to one of the registers determined by its sub-address.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CWriteByte(I2CDevPtr d, I2CByte subaddr, I2CByte byte)
|
|
|
|
{
|
|
|
|
I2CByte wb[2];
|
|
|
|
|
|
|
|
wb[0] = subaddr;
|
|
|
|
wb[1] = byte;
|
|
|
|
|
|
|
|
return xf86I2CWriteRead(d, wb, 2, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write bytes to subsequent registers determined by the
|
|
|
|
* sub-address of the first register.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CWriteBytes(I2CDevPtr d, I2CByte subaddr,
|
|
|
|
I2CByte *WriteBuffer, int nWrite)
|
|
|
|
{
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
Bool r = TRUE;
|
|
|
|
|
|
|
|
if (nWrite > 0) {
|
|
|
|
r = b->I2CAddress(d, d->SlaveAddr & ~1);
|
|
|
|
if (r){
|
|
|
|
if ((r = b->I2CPutByte(d, subaddr)))
|
|
|
|
for (; nWrite > 0; WriteBuffer++, nWrite--)
|
|
|
|
if (!(r = b->I2CPutByte(d, *WriteBuffer)))
|
|
|
|
break;
|
|
|
|
|
|
|
|
b->I2CStop(d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write a word (high byte, then low byte) to one of the registers
|
|
|
|
* determined by its sub-address.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CWriteWord(I2CDevPtr d, I2CByte subaddr, unsigned short word)
|
|
|
|
{
|
|
|
|
I2CByte wb[3];
|
|
|
|
|
|
|
|
wb[0] = subaddr;
|
|
|
|
wb[1] = word >> 8;
|
|
|
|
wb[2] = word & 0xFF;
|
|
|
|
|
|
|
|
return xf86I2CWriteRead(d, wb, 3, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write a vector of bytes to not adjacent registers. This vector is,
|
|
|
|
* 1st byte sub-address, 2nd byte value, 3rd byte sub-address asf.
|
|
|
|
* This function is intended to initialize devices. Note this function
|
|
|
|
* exits immediately when an error occurs, some registers may
|
|
|
|
* remain uninitialized.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CWriteVec(I2CDevPtr d, I2CByte *vec, int nValues)
|
|
|
|
{
|
|
|
|
I2CBusPtr b = d->pI2CBus;
|
|
|
|
Bool r = TRUE;
|
|
|
|
int s = 0;
|
|
|
|
|
|
|
|
if (nValues > 0) {
|
|
|
|
for (; nValues > 0; nValues--, vec += 2) {
|
|
|
|
if (!(r = b->I2CAddress(d, d->SlaveAddr & ~1)))
|
|
|
|
break;
|
|
|
|
|
|
|
|
s++;
|
|
|
|
|
|
|
|
if (!(r = b->I2CPutByte(d, vec[0])))
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (!(r = b->I2CPutByte(d, vec[1])))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s > 0) b->I2CStop(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Administrative functions.
|
|
|
|
* =========================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Allocates an I2CDevRec for you and initializes with propper defaults
|
|
|
|
* you may modify before calling xf86I2CDevInit. Your I2CDevRec must
|
|
|
|
* contain at least a SlaveAddr, and a pI2CBus pointer to the bus this
|
|
|
|
* device shall be linked to.
|
|
|
|
*
|
|
|
|
* See function I2CAddress for the slave address format. Always set
|
|
|
|
* the least significant bit, indicating a read or write access, to zero.
|
|
|
|
*/
|
|
|
|
|
|
|
|
I2CDevPtr
|
|
|
|
xf86CreateI2CDevRec(void)
|
|
|
|
{
|
|
|
|
return xcalloc(1, sizeof(I2CDevRec));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unlink an I2C device. If you got the I2CDevRec from xf86CreateI2CDevRec
|
|
|
|
* you should set <unalloc> to free it.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
xf86DestroyI2CDevRec(I2CDevPtr d, Bool unalloc)
|
|
|
|
{
|
|
|
|
if (d) {
|
|
|
|
I2CDevPtr *p;
|
|
|
|
|
|
|
|
/* Remove this from the list of active I2C devices. */
|
|
|
|
|
|
|
|
for (p = &d->pI2CBus->FirstDev; *p != NULL; p = &(*p)->NextDev)
|
|
|
|
if (*p == d) {
|
|
|
|
*p = (*p)->NextDev;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
xf86DrvMsg(d->pI2CBus->scrnIndex, X_INFO,
|
|
|
|
"I2C device \"%s:%s\" removed.\n",
|
|
|
|
d->pI2CBus->BusName, d->DevName);
|
|
|
|
|
|
|
|
if (unalloc) xfree(d);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* I2C transmissions are related to an I2CDevRec you must link to a
|
|
|
|
* previously registered bus (see xf86I2CBusInit) before attempting
|
|
|
|
* to read and write data. You may call xf86I2CProbeAddress first to
|
|
|
|
* see if the device in question is present on this bus.
|
|
|
|
*
|
|
|
|
* xf86I2CDevInit will not allocate an I2CBusRec for you, instead you
|
|
|
|
* may enter a pointer to a statically allocated I2CDevRec or the (modified)
|
|
|
|
* result of xf86CreateI2CDevRec.
|
|
|
|
*
|
|
|
|
* If you don't specify timeouts for the device (n <= 0), it will inherit
|
|
|
|
* the bus-wide defaults. The function returns TRUE on success.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CDevInit(I2CDevPtr d)
|
|
|
|
{
|
|
|
|
I2CBusPtr b;
|
|
|
|
|
|
|
|
if (d == NULL ||
|
|
|
|
(b = d->pI2CBus) == NULL ||
|
|
|
|
(d->SlaveAddr & 1) ||
|
|
|
|
xf86I2CFindDev(b, d->SlaveAddr) != NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
if (d->BitTimeout <= 0) d->BitTimeout = b->BitTimeout;
|
|
|
|
if (d->ByteTimeout <= 0) d->ByteTimeout = b->ByteTimeout;
|
|
|
|
if (d->AcknTimeout <= 0) d->AcknTimeout = b->AcknTimeout;
|
|
|
|
if (d->StartTimeout <= 0) d->StartTimeout = b->StartTimeout;
|
|
|
|
|
|
|
|
d->NextDev = b->FirstDev;
|
|
|
|
b->FirstDev = d;
|
|
|
|
|
|
|
|
xf86DrvMsg(b->scrnIndex, X_INFO,
|
|
|
|
"I2C device \"%s:%s\" registered at address 0x%02X.\n",
|
|
|
|
b->BusName, d->DevName, d->SlaveAddr);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
I2CDevPtr
|
|
|
|
xf86I2CFindDev(I2CBusPtr b, I2CSlaveAddr addr)
|
|
|
|
{
|
|
|
|
I2CDevPtr d;
|
|
|
|
|
|
|
|
if (b) {
|
|
|
|
for (d = b->FirstDev; d != NULL; d = d->NextDev)
|
|
|
|
if (d->SlaveAddr == addr)
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static I2CBusPtr I2CBusList;
|
|
|
|
|
|
|
|
/* Allocates an I2CBusRec for you and initializes with propper defaults
|
|
|
|
* you may modify before calling xf86I2CBusInit. Your I2CBusRec must
|
|
|
|
* contain at least a BusName, a scrnIndex (or -1), and a complete set
|
|
|
|
* of either high or low level I2C function pointers. You may pass
|
|
|
|
* bus-wide timeouts, otherwise inplausible values will be replaced
|
|
|
|
* with safe defaults.
|
|
|
|
*/
|
|
|
|
|
|
|
|
I2CBusPtr
|
|
|
|
xf86CreateI2CBusRec(void)
|
|
|
|
{
|
|
|
|
I2CBusPtr b;
|
|
|
|
|
|
|
|
b = (I2CBusPtr) xcalloc(1, sizeof(I2CBusRec));
|
|
|
|
|
|
|
|
if (b != NULL) {
|
|
|
|
b->scrnIndex = -1;
|
|
|
|
b->HoldTime = 5; /* 100 kHz bus */
|
|
|
|
b->BitTimeout = 5;
|
|
|
|
b->ByteTimeout = 5;
|
|
|
|
b->AcknTimeout = 5;
|
|
|
|
b->StartTimeout = 5;
|
|
|
|
b->RiseFallTime = RISEFALLTIME;
|
|
|
|
}
|
|
|
|
|
|
|
|
return b;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unregister an I2C bus. If you got the I2CBusRec from xf86CreateI2CBusRec
|
|
|
|
* you should set <unalloc> to free it. If you set <devs_too>, the function
|
|
|
|
* xf86DestroyI2CDevRec will be called for all devices linked to the bus
|
|
|
|
* first, passing down the <unalloc> option.
|
|
|
|
*/
|
|
|
|
|
|
|
|
void
|
|
|
|
xf86DestroyI2CBusRec(I2CBusPtr b, Bool unalloc, Bool devs_too)
|
|
|
|
{
|
|
|
|
if (b) {
|
|
|
|
I2CBusPtr *p;
|
|
|
|
|
|
|
|
/* Remove this from the list of active I2C buses */
|
|
|
|
|
|
|
|
for (p = &I2CBusList; *p != NULL; p = &(*p)->NextBus)
|
|
|
|
if (*p == b) {
|
|
|
|
*p = (*p)->NextBus;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b->FirstDev != NULL) {
|
|
|
|
if (devs_too) {
|
|
|
|
I2CDevPtr d;
|
|
|
|
|
|
|
|
while ((d = b->FirstDev) != NULL) {
|
|
|
|
b->FirstDev = d->NextDev;
|
|
|
|
xf86DestroyI2CDevRec(d, unalloc);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (unalloc) {
|
|
|
|
xf86Msg(X_ERROR, "i2c bug: Attempt to remove I2C bus \"%s\", "
|
|
|
|
"but device list is not empty.\n",
|
|
|
|
b->BusName);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
xf86DrvMsg(b->scrnIndex, X_INFO, "I2C bus \"%s\" removed.\n",
|
|
|
|
b->BusName);
|
|
|
|
|
|
|
|
if (unalloc) xfree(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* I2C masters have to register themselves using this function.
|
|
|
|
* It will not allocate an I2CBusRec for you, instead you may enter
|
|
|
|
* a pointer to a statically allocated I2CBusRec or the (modified)
|
|
|
|
* result of xf86CreateI2CBusRec. Returns TRUE on success.
|
|
|
|
*
|
|
|
|
* At this point there won't be any traffic on the I2C bus.
|
|
|
|
*/
|
|
|
|
|
|
|
|
Bool
|
|
|
|
xf86I2CBusInit(I2CBusPtr b)
|
|
|
|
{
|
|
|
|
/* I2C buses must be identified by a unique scrnIndex
|
|
|
|
* and name. If scrnIndex is unspecified (a negative value),
|
|
|
|
* then the name must be unique throughout the server.
|
|
|
|
*/
|
|
|
|
|
|
|
|
if (b->BusName == NULL ||
|
|
|
|
xf86I2CFindBus(b->scrnIndex, b->BusName) != NULL)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* If the high level functions are not
|
|
|
|
* supplied, use the generic functions.
|
|
|
|
* In this case we need the low-level
|
|
|
|
* function.
|
|
|
|
*/
|
|
|
|
if (b->I2CWriteRead == NULL)
|
|
|
|
{
|
|
|
|
b->I2CWriteRead=I2CWriteRead;
|
|
|
|
|
|
|
|
if (b->I2CPutBits == NULL ||
|
|
|
|
b->I2CGetBits == NULL)
|
|
|
|
{
|
|
|
|
if (b->I2CPutByte == NULL ||
|
|
|
|
b->I2CGetByte == NULL ||
|
|
|
|
b->I2CAddress == NULL ||
|
|
|
|
b->I2CStart == NULL ||
|
|
|
|
b->I2CStop == NULL)
|
|
|
|
return FALSE;
|
|
|
|
} else {
|
|
|
|
b->I2CPutByte = I2CPutByte;
|
|
|
|
b->I2CGetByte = I2CGetByte;
|
|
|
|
b->I2CAddress = I2CAddress;
|
|
|
|
b->I2CStop = I2CStop;
|
|
|
|
b->I2CStart = I2CStart;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (b->I2CUDelay == NULL)
|
|
|
|
b->I2CUDelay = I2CUDelay;
|
|
|
|
|
|
|
|
if (b->HoldTime < 2) b->HoldTime = 5;
|
|
|
|
if (b->BitTimeout <= 0) b->BitTimeout = b->HoldTime;
|
|
|
|
if (b->ByteTimeout <= 0) b->ByteTimeout = b->HoldTime;
|
|
|
|
if (b->AcknTimeout <= 0) b->AcknTimeout = b->HoldTime;
|
|
|
|
if (b->StartTimeout <= 0) b->StartTimeout = b->HoldTime;
|
|
|
|
|
|
|
|
/* Put new bus on list. */
|
|
|
|
|
|
|
|
b->NextBus = I2CBusList;
|
|
|
|
I2CBusList = b;
|
|
|
|
|
|
|
|
xf86DrvMsg(b->scrnIndex, X_INFO, "I2C bus \"%s\" initialized.\n",
|
|
|
|
b->BusName);
|
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
I2CBusPtr
|
|
|
|
xf86I2CFindBus(int scrnIndex, char *name)
|
|
|
|
{
|
|
|
|
I2CBusPtr p;
|
|
|
|
|
|
|
|
if (name != NULL)
|
|
|
|
for (p = I2CBusList; p != NULL; p = p->NextBus)
|
|
|
|
if (scrnIndex < 0 || p->scrnIndex == scrnIndex)
|
|
|
|
if (!strcmp(p->BusName, name))
|
|
|
|
return p;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return an array of I2CBusPtr's related to a screen. The caller is
|
|
|
|
* responsible for freeing the array.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
xf86I2CGetScreenBuses(int scrnIndex, I2CBusPtr **pppI2CBus)
|
|
|
|
{
|
|
|
|
I2CBusPtr pI2CBus;
|
|
|
|
int n = 0;
|
|
|
|
|
|
|
|
if (pppI2CBus)
|
|
|
|
*pppI2CBus = NULL;
|
|
|
|
|
|
|
|
for (pI2CBus = I2CBusList; pI2CBus; pI2CBus = pI2CBus->NextBus) {
|
|
|
|
if ((pI2CBus->scrnIndex >= 0) && (pI2CBus->scrnIndex != scrnIndex))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
n++;
|
|
|
|
|
|
|
|
if (!pppI2CBus)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*pppI2CBus = xnfrealloc(*pppI2CBus, n * sizeof(I2CBusPtr));
|
2008-11-02 08:26:08 -07:00
|
|
|
(*pppI2CBus)[n - 1] = pI2CBus;
|
2006-11-26 11:13:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return n;
|
|
|
|
}
|