summaryrefslogtreecommitdiff
path: root/drivers/net/sfc/i2c-direct.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/sfc/i2c-direct.c')
-rw-r--r--drivers/net/sfc/i2c-direct.c381
1 files changed, 381 insertions, 0 deletions
diff --git a/drivers/net/sfc/i2c-direct.c b/drivers/net/sfc/i2c-direct.c
new file mode 100644
index 000000000000..b6c62d0ed9c2
--- /dev/null
+++ b/drivers/net/sfc/i2c-direct.c
@@ -0,0 +1,381 @@
+/****************************************************************************
+ * Driver for Solarflare Solarstorm network controllers and boards
+ * Copyright 2005 Fen Systems Ltd.
+ * Copyright 2006-2008 Solarflare Communications Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation, incorporated herein by reference.
+ */
+
+#include <linux/delay.h>
+#include "net_driver.h"
+#include "i2c-direct.h"
+
+/*
+ * I2C data (SDA) and clock (SCL) line read/writes with appropriate
+ * delays.
+ */
+
+static inline void setsda(struct efx_i2c_interface *i2c, int state)
+{
+ udelay(i2c->op->udelay);
+ i2c->sda = state;
+ i2c->op->setsda(i2c);
+ udelay(i2c->op->udelay);
+}
+
+static inline void setscl(struct efx_i2c_interface *i2c, int state)
+{
+ udelay(i2c->op->udelay);
+ i2c->scl = state;
+ i2c->op->setscl(i2c);
+ udelay(i2c->op->udelay);
+}
+
+static inline int getsda(struct efx_i2c_interface *i2c)
+{
+ int sda;
+
+ udelay(i2c->op->udelay);
+ sda = i2c->op->getsda(i2c);
+ udelay(i2c->op->udelay);
+ return sda;
+}
+
+static inline int getscl(struct efx_i2c_interface *i2c)
+{
+ int scl;
+
+ udelay(i2c->op->udelay);
+ scl = i2c->op->getscl(i2c);
+ udelay(i2c->op->udelay);
+ return scl;
+}
+
+/*
+ * I2C low-level protocol operations
+ *
+ */
+
+static inline void i2c_release(struct efx_i2c_interface *i2c)
+{
+ EFX_WARN_ON_PARANOID(!i2c->scl);
+ EFX_WARN_ON_PARANOID(!i2c->sda);
+ /* Devices may time out if operations do not end */
+ setscl(i2c, 1);
+ setsda(i2c, 1);
+ EFX_BUG_ON_PARANOID(getsda(i2c) != 1);
+ EFX_BUG_ON_PARANOID(getscl(i2c) != 1);
+}
+
+static inline void i2c_start(struct efx_i2c_interface *i2c)
+{
+ /* We may be restarting immediately after a {send,recv}_bit,
+ * so SCL will not necessarily already be high.
+ */
+ EFX_WARN_ON_PARANOID(!i2c->sda);
+ setscl(i2c, 1);
+ setsda(i2c, 0);
+ setscl(i2c, 0);
+ setsda(i2c, 1);
+}
+
+static inline void i2c_send_bit(struct efx_i2c_interface *i2c, int bit)
+{
+ EFX_WARN_ON_PARANOID(i2c->scl != 0);
+ setsda(i2c, bit);
+ setscl(i2c, 1);
+ setscl(i2c, 0);
+ setsda(i2c, 1);
+}
+
+static inline int i2c_recv_bit(struct efx_i2c_interface *i2c)
+{
+ int bit;
+
+ EFX_WARN_ON_PARANOID(i2c->scl != 0);
+ EFX_WARN_ON_PARANOID(!i2c->sda);
+ setscl(i2c, 1);
+ bit = getsda(i2c);
+ setscl(i2c, 0);
+ return bit;
+}
+
+static inline void i2c_stop(struct efx_i2c_interface *i2c)
+{
+ EFX_WARN_ON_PARANOID(i2c->scl != 0);
+ setsda(i2c, 0);
+ setscl(i2c, 1);
+ setsda(i2c, 1);
+}
+
+/*
+ * I2C mid-level protocol operations
+ *
+ */
+
+/* Sends a byte via the I2C bus and checks for an acknowledgement from
+ * the slave device.
+ */
+static int i2c_send_byte(struct efx_i2c_interface *i2c, u8 byte)
+{
+ int i;
+
+ /* Send byte */
+ for (i = 0; i < 8; i++) {
+ i2c_send_bit(i2c, !!(byte & 0x80));
+ byte <<= 1;
+ }
+
+ /* Check for acknowledgement from slave */
+ return (i2c_recv_bit(i2c) == 0 ? 0 : -EIO);
+}
+
+/* Receives a byte via the I2C bus and sends ACK/NACK to the slave device. */
+static u8 i2c_recv_byte(struct efx_i2c_interface *i2c, int ack)
+{
+ u8 value = 0;
+ int i;
+
+ /* Receive byte */
+ for (i = 0; i < 8; i++)
+ value = (value << 1) | i2c_recv_bit(i2c);
+
+ /* Send ACK/NACK */
+ i2c_send_bit(i2c, (ack ? 0 : 1));
+
+ return value;
+}
+
+/* Calculate command byte for a read operation */
+static inline u8 i2c_read_cmd(u8 device_id)
+{
+ return ((device_id << 1) | 1);
+}
+
+/* Calculate command byte for a write operation */
+static inline u8 i2c_write_cmd(u8 device_id)
+{
+ return ((device_id << 1) | 0);
+}
+
+int efx_i2c_check_presence(struct efx_i2c_interface *i2c, u8 device_id)
+{
+ int rc;
+
+ /* If someone is driving the bus low we just give up. */
+ if (getsda(i2c) == 0 || getscl(i2c) == 0) {
+ EFX_ERR(i2c->efx, "%s someone is holding the I2C bus low."
+ " Giving up.\n", __func__);
+ return -EFAULT;
+ }
+
+ /* Pretend to initiate a device write */
+ i2c_start(i2c);
+ rc = i2c_send_byte(i2c, i2c_write_cmd(device_id));
+ if (rc)
+ goto out;
+
+ out:
+ i2c_stop(i2c);
+ i2c_release(i2c);
+
+ return rc;
+}
+
+/* This performs a fast read of one or more consecutive bytes from an
+ * I2C device. Not all devices support consecutive reads of more than
+ * one byte; for these devices use efx_i2c_read() instead.
+ */
+int efx_i2c_fast_read(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset, u8 *data, unsigned int len)
+{
+ int i;
+ int rc;
+
+ EFX_WARN_ON_PARANOID(getsda(i2c) != 1);
+ EFX_WARN_ON_PARANOID(getscl(i2c) != 1);
+ EFX_WARN_ON_PARANOID(data == NULL);
+ EFX_WARN_ON_PARANOID(len < 1);
+
+ /* Select device and starting offset */
+ i2c_start(i2c);
+ rc = i2c_send_byte(i2c, i2c_write_cmd(device_id));
+ if (rc)
+ goto out;
+ rc = i2c_send_byte(i2c, offset);
+ if (rc)
+ goto out;
+
+ /* Read data from device */
+ i2c_start(i2c);
+ rc = i2c_send_byte(i2c, i2c_read_cmd(device_id));
+ if (rc)
+ goto out;
+ for (i = 0; i < (len - 1); i++)
+ /* Read and acknowledge all but the last byte */
+ data[i] = i2c_recv_byte(i2c, 1);
+ /* Read last byte with no acknowledgement */
+ data[i] = i2c_recv_byte(i2c, 0);
+
+ out:
+ i2c_stop(i2c);
+ i2c_release(i2c);
+
+ return rc;
+}
+
+/* This performs a fast write of one or more consecutive bytes to an
+ * I2C device. Not all devices support consecutive writes of more
+ * than one byte; for these devices use efx_i2c_write() instead.
+ */
+int efx_i2c_fast_write(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset,
+ const u8 *data, unsigned int len)
+{
+ int i;
+ int rc;
+
+ EFX_WARN_ON_PARANOID(getsda(i2c) != 1);
+ EFX_WARN_ON_PARANOID(getscl(i2c) != 1);
+ EFX_WARN_ON_PARANOID(len < 1);
+
+ /* Select device and starting offset */
+ i2c_start(i2c);
+ rc = i2c_send_byte(i2c, i2c_write_cmd(device_id));
+ if (rc)
+ goto out;
+ rc = i2c_send_byte(i2c, offset);
+ if (rc)
+ goto out;
+
+ /* Write data to device */
+ for (i = 0; i < len; i++) {
+ rc = i2c_send_byte(i2c, data[i]);
+ if (rc)
+ goto out;
+ }
+
+ out:
+ i2c_stop(i2c);
+ i2c_release(i2c);
+
+ return rc;
+}
+
+/* I2C byte-by-byte read */
+int efx_i2c_read(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset, u8 *data, unsigned int len)
+{
+ int rc;
+
+ /* i2c_fast_read with length 1 is a single byte read */
+ for (; len > 0; offset++, data++, len--) {
+ rc = efx_i2c_fast_read(i2c, device_id, offset, data, 1);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+/* I2C byte-by-byte write */
+int efx_i2c_write(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset, const u8 *data, unsigned int len)
+{
+ int rc;
+
+ /* i2c_fast_write with length 1 is a single byte write */
+ for (; len > 0; offset++, data++, len--) {
+ rc = efx_i2c_fast_write(i2c, device_id, offset, data, 1);
+ if (rc)
+ return rc;
+ mdelay(i2c->op->mdelay);
+ }
+
+ return 0;
+}
+
+
+/* This is just a slightly neater wrapper round efx_i2c_fast_write
+ * in the case where the target doesn't take an offset
+ */
+int efx_i2c_send_bytes(struct efx_i2c_interface *i2c,
+ u8 device_id, const u8 *data, unsigned int len)
+{
+ return efx_i2c_fast_write(i2c, device_id, data[0], data + 1, len - 1);
+}
+
+/* I2C receiving of bytes - does not send an offset byte */
+int efx_i2c_recv_bytes(struct efx_i2c_interface *i2c, u8 device_id,
+ u8 *bytes, unsigned int len)
+{
+ int i;
+ int rc;
+
+ EFX_WARN_ON_PARANOID(getsda(i2c) != 1);
+ EFX_WARN_ON_PARANOID(getscl(i2c) != 1);
+ EFX_WARN_ON_PARANOID(len < 1);
+
+ /* Select device */
+ i2c_start(i2c);
+
+ /* Read data from device */
+ rc = i2c_send_byte(i2c, i2c_read_cmd(device_id));
+ if (rc)
+ goto out;
+
+ for (i = 0; i < (len - 1); i++)
+ /* Read and acknowledge all but the last byte */
+ bytes[i] = i2c_recv_byte(i2c, 1);
+ /* Read last byte with no acknowledgement */
+ bytes[i] = i2c_recv_byte(i2c, 0);
+
+ out:
+ i2c_stop(i2c);
+ i2c_release(i2c);
+
+ return rc;
+}
+
+/* SMBus and some I2C devices will time out if the I2C clock is
+ * held low for too long. This is most likely to happen in virtualised
+ * systems (when the entire domain is descheduled) but could in
+ * principle happen due to preemption on any busy system (and given the
+ * potential length of an I2C operation turning preemption off is not
+ * a sensible option). The following functions deal with the failure by
+ * retrying up to a fixed number of times.
+ */
+
+#define I2C_MAX_RETRIES (10)
+
+/* The timeout problem will result in -EIO. If the wrapped function
+ * returns any other error, pass this up and do not retry. */
+#define RETRY_WRAPPER(_f) \
+ int retries = I2C_MAX_RETRIES; \
+ int rc; \
+ while (retries) { \
+ rc = _f; \
+ if (rc != -EIO) \
+ return rc; \
+ retries--; \
+ } \
+ return rc; \
+
+int efx_i2c_check_presence_retry(struct efx_i2c_interface *i2c, u8 device_id)
+{
+ RETRY_WRAPPER(efx_i2c_check_presence(i2c, device_id))
+}
+
+int efx_i2c_read_retry(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset, u8 *data, unsigned int len)
+{
+ RETRY_WRAPPER(efx_i2c_read(i2c, device_id, offset, data, len))
+}
+
+int efx_i2c_write_retry(struct efx_i2c_interface *i2c,
+ u8 device_id, u8 offset, const u8 *data, unsigned int len)
+{
+ RETRY_WRAPPER(efx_i2c_write(i2c, device_id, offset, data, len))
+}