summaryrefslogtreecommitdiff
path: root/drivers/staging/nvec/nvec.c
diff options
context:
space:
mode:
authorJulian Andres Klode <jak@jak-linux.org>2011-09-27 19:00:51 +0200
committerGreg Kroah-Hartman <gregkh@suse.de>2011-09-29 17:41:33 -0700
commit0cab4cb8526d7367c912c9a871d3ad1a9ac1fbf0 (patch)
treedda540c0147ee39b3725f7a7bc438f039a2b3675 /drivers/staging/nvec/nvec.c
parent8517e879e0d11a277275616b9e1c749afe2fceff (diff)
staging: nvec: Rewrite the interrupt handler
Rewrite the interrupt handler to use a state machine similar to that found in the various kernels for the Advent Vega. This also changes the code to use the new functions introduced in the previous commits. This also merges the rewrite sent in August 2011 by Marc Dietrich, and thus also includes code by him. His original patch can be found on the mailing list. Signed-off-by: Julian Andres Klode <jak@jak-linux.org> Acked-by: Marc Dietrich <marvin24@gmx.de> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/staging/nvec/nvec.c')
-rw-r--r--drivers/staging/nvec/nvec.c389
1 files changed, 275 insertions, 114 deletions
diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c
index 8e97e2d7ac3d..1a120c1418ec 100644
--- a/drivers/staging/nvec/nvec.c
+++ b/drivers/staging/nvec/nvec.c
@@ -155,27 +155,81 @@ static void nvec_gpio_set_value(struct nvec_chip *nvec, int value)
void nvec_write_async(struct nvec_chip *nvec, const unsigned char *data,
short size)
{
- struct nvec_msg *msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT);
+ struct nvec_msg *msg;
+ unsigned long flags;
- msg->data = kzalloc(size, GFP_NOWAIT);
+ msg = nvec_msg_alloc(nvec);
msg->data[0] = size;
memcpy(msg->data + 1, data, size);
msg->size = size + 1;
- msg->pos = 0;
- INIT_LIST_HEAD(&msg->node);
+ spin_lock_irqsave(&nvec->tx_lock, flags);
list_add_tail(&msg->node, &nvec->tx_data);
+ spin_unlock_irqrestore(&nvec->tx_lock, flags);
- gpio_set_value(nvec->gpio, 0);
+ queue_work(nvec->wq, &nvec->tx_work);
}
EXPORT_SYMBOL(nvec_write_async);
+struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec,
+ const unsigned char *data, short size)
+{
+ struct nvec_msg *msg;
+
+ mutex_lock(&nvec->sync_write_mutex);
+
+ nvec->sync_write_pending = (data[1] << 8) + data[0];
+ nvec_write_async(nvec, data, size);
+
+ dev_dbg(nvec->dev, "nvec_sync_write: 0x%04x\n",
+ nvec->sync_write_pending);
+ if (!(wait_for_completion_timeout(&nvec->sync_write,
+ msecs_to_jiffies(2000)))) {
+ dev_warn(nvec->dev, "timeout waiting for sync write to complete\n");
+ mutex_unlock(&nvec->sync_write_mutex);
+ return NULL;
+ }
+
+ dev_dbg(nvec->dev, "nvec_sync_write: pong!\n");
+
+ msg = nvec->last_sync_msg;
+
+ mutex_unlock(&nvec->sync_write_mutex);
+
+ return msg;
+}
+EXPORT_SYMBOL(nvec_write_sync);
+
+/* TX worker */
static void nvec_request_master(struct work_struct *work)
{
struct nvec_chip *nvec = container_of(work, struct nvec_chip, tx_work);
+ unsigned long flags;
+ long err;
+ struct nvec_msg *msg;
+
+ spin_lock_irqsave(&nvec->tx_lock, flags);
+ while (!list_empty(&nvec->tx_data)) {
+ msg = list_first_entry(&nvec->tx_data, struct nvec_msg, node);
+ spin_unlock_irqrestore(&nvec->tx_lock, flags);
+ nvec_gpio_set_value(nvec, 0);
+ err = wait_for_completion_interruptible_timeout(
+ &nvec->ec_transfer, msecs_to_jiffies(5000));
+
+ if (err == 0) {
+ dev_warn(nvec->dev, "timeout waiting for ec transfer\n");
+ nvec_gpio_set_value(nvec, 1);
+ msg->pos = 0;
+ }
- if (!list_empty(&nvec->tx_data))
- gpio_set_value(nvec->gpio, 0);
+ spin_lock_irqsave(&nvec->tx_lock, flags);
+
+ if (err > 0) {
+ list_del_init(&msg->node);
+ nvec_msg_free(nvec, msg);
+ }
+ }
+ spin_unlock_irqrestore(&nvec->tx_lock, flags);
}
static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg)
@@ -197,143 +251,243 @@ static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg)
return 0;
}
-static struct nvec_msg *nvec_write_sync(struct nvec_chip *nvec,
- const unsigned char *data, short size)
-{
- down(&nvec->sync_write_mutex);
-
- nvec->sync_write_pending = (data[1] << 8) + data[0];
- nvec_write_async(nvec, data, size);
-
- dev_dbg(nvec->dev, "nvec_sync_write: 0x%04x\n",
- nvec->sync_write_pending);
- wait_for_completion(&nvec->sync_write);
- dev_dbg(nvec->dev, "nvec_sync_write: pong!\n");
-
- up(&nvec->sync_write_mutex);
-
- return nvec->last_sync_msg;
-}
-
/* RX worker */
static void nvec_dispatch(struct work_struct *work)
{
struct nvec_chip *nvec = container_of(work, struct nvec_chip, rx_work);
+ unsigned long flags;
struct nvec_msg *msg;
+ spin_lock_irqsave(&nvec->rx_lock, flags);
while (!list_empty(&nvec->rx_data)) {
msg = list_first_entry(&nvec->rx_data, struct nvec_msg, node);
list_del_init(&msg->node);
+ spin_unlock_irqrestore(&nvec->rx_lock, flags);
if (nvec->sync_write_pending ==
- (msg->data[2] << 8) + msg->data[0]) {
+ (msg->data[2] << 8) + msg->data[0]) {
dev_dbg(nvec->dev, "sync write completed!\n");
nvec->sync_write_pending = 0;
nvec->last_sync_msg = msg;
complete(&nvec->sync_write);
} else {
parse_msg(nvec, msg);
- if ((!msg) || (!msg->data))
- dev_warn(nvec->dev,
- "attempt access zero pointer\n");
- else {
- kfree(msg->data);
- kfree(msg);
- }
+ nvec_msg_free(nvec, msg);
}
+ spin_lock_irqsave(&nvec->rx_lock, flags);
}
+ spin_unlock_irqrestore(&nvec->rx_lock, flags);
}
+static void nvec_tx_completed(struct nvec_chip *nvec)
+{
+ /* We got an END_TRANS, let's skip this, maybe there's an event */
+ if (nvec->tx->pos != nvec->tx->size) {
+ dev_err(nvec->dev, "premature END_TRANS, resending\n");
+ nvec->tx->pos = 0;
+ nvec_gpio_set_value(nvec, 0);
+ } else {
+ nvec->state = 0;
+ }
+}
+
+static void nvec_rx_completed(struct nvec_chip *nvec)
+{
+ if (nvec->rx->pos != nvec_msg_size(nvec->rx))
+ dev_err(nvec->dev, "RX incomplete: Expected %u bytes, got %u\n",
+ (uint) nvec_msg_size(nvec->rx),
+ (uint) nvec->rx->pos);
+
+ spin_lock(&nvec->rx_lock);
+
+ /* add the received data to the work list
+ and move the ring buffer pointer to the next entry */
+ list_add_tail(&nvec->rx->node, &nvec->rx_data);
+
+ spin_unlock(&nvec->rx_lock);
+
+ nvec->state = 0;
+
+ if (!nvec_msg_is_event(nvec->rx))
+ complete(&nvec->ec_transfer);
+
+ queue_work(nvec->wq, &nvec->rx_work);
+}
+
+/**
+ * nvec_invalid_flags - Send an error message about invalid flags and jump
+ * @nvec: The nvec device
+ * @status: The status flags
+ * @reset: Whether we shall jump to state 0.
+ */
+static void nvec_invalid_flags(struct nvec_chip *nvec, unsigned int status,
+ bool reset)
+{
+ dev_err(nvec->dev, "unexpected status flags 0x%02x during state %i\n",
+ status, nvec->state);
+ if (reset)
+ nvec->state = 0;
+}
+
+/**
+ * nvec_tx_set - Set the message to transfer (nvec->tx)
+ */
+static void nvec_tx_set(struct nvec_chip *nvec)
+{
+ spin_lock(&nvec->tx_lock);
+ if (list_empty(&nvec->tx_data)) {
+ dev_err(nvec->dev, "empty tx - sending no-op\n");
+ memcpy(nvec->tx_scratch.data, "\x02\x07\x02", 3);
+ nvec->tx_scratch.size = 3;
+ nvec->tx_scratch.pos = 0;
+ nvec->tx = &nvec->tx_scratch;
+ list_add_tail(&nvec->tx->node, &nvec->tx_data);
+ } else {
+ nvec->tx = list_first_entry(&nvec->tx_data, struct nvec_msg,
+ node);
+ nvec->tx->pos = 0;
+ }
+ spin_unlock(&nvec->tx_lock);
+
+ dev_dbg(nvec->dev, "Sending message of length %u, command 0x%x\n",
+ (uint)nvec->tx->size, nvec->tx->data[1]);
+}
+
+/**
+ * nvec_interrupt - Interrupt handler
+ * @irq: The IRQ
+ * @dev: The nvec device
+ */
static irqreturn_t nvec_interrupt(int irq, void *dev)
{
unsigned long status;
- unsigned long received;
- unsigned char to_send;
- struct nvec_msg *msg;
- struct nvec_chip *nvec = (struct nvec_chip *)dev;
- void __iomem *base = nvec->base;
+ unsigned int received = 0;
+ unsigned char to_send = 0xff;
+ const unsigned long irq_mask = I2C_SL_IRQ | END_TRANS | RCVD | RNW;
+ struct nvec_chip *nvec = dev;
+ unsigned int state = nvec->state;
- status = readl(base + I2C_SL_STATUS);
+ status = readl(nvec->base + I2C_SL_STATUS);
- if (!(status & I2C_SL_IRQ)) {
- dev_warn(nvec->dev, "nvec Spurious IRQ\n");
- goto handled;
+ /* Filter out some errors */
+ if ((status & irq_mask) == 0 && (status & ~irq_mask) != 0) {
+ dev_err(nvec->dev, "unexpected irq mask %lx\n", status);
+ return IRQ_HANDLED;
}
- if (status & END_TRANS && !(status & RCVD)) {
- nvec->state = NVEC_WAIT;
- if (nvec->rx->size > 1) {
- list_add_tail(&nvec->rx->node, &nvec->rx_data);
- schedule_work(&nvec->rx_work);
- } else {
- kfree(nvec->rx->data);
- kfree(nvec->rx);
- }
+ if ((status & I2C_SL_IRQ) == 0) {
+ dev_err(nvec->dev, "Spurious IRQ\n");
return IRQ_HANDLED;
- } else if (status & RNW) {
- if (status & RCVD)
- udelay(3);
+ }
+ /* The EC did not request a read, so it send us something, read it */
+ if ((status & RNW) == 0) {
+ received = readl(nvec->base + I2C_SL_RCVD);
if (status & RCVD)
- nvec->state = NVEC_WRITE;
+ writel(0, nvec->base + I2C_SL_RCVD);
+ }
- if (list_empty(&nvec->tx_data)) {
- dev_err(nvec->dev, "nvec empty tx - sending no-op\n");
- to_send = 0x8a;
- nvec_write_async(nvec, "\x07\x02", 2);
+ if (status == (I2C_SL_IRQ | RCVD))
+ nvec->state = 0;
+
+ switch (nvec->state) {
+ case 0: /* Verify that its a transfer start, the rest later */
+ if (status != (I2C_SL_IRQ | RCVD))
+ nvec_invalid_flags(nvec, status, false);
+ break;
+ case 1: /* command byte */
+ if (status != I2C_SL_IRQ) {
+ nvec_invalid_flags(nvec, status, true);
} else {
- msg =
- list_first_entry(&nvec->tx_data, struct nvec_msg,
- node);
- if (msg->pos < msg->size) {
- to_send = msg->data[msg->pos];
- msg->pos++;
- } else {
- dev_err(nvec->dev, "nvec crap! %d\n",
- msg->size);
- to_send = 0x01;
- }
-
- if (msg->pos >= msg->size) {
- list_del_init(&msg->node);
- kfree(msg->data);
- kfree(msg);
- schedule_work(&nvec->tx_work);
- nvec->state = NVEC_WAIT;
+ nvec->rx = nvec_msg_alloc(nvec);
+ nvec->rx->data[0] = received;
+ nvec->rx->pos = 1;
+ nvec->state = 2;
+ }
+ break;
+ case 2: /* first byte after command */
+ if (status == (I2C_SL_IRQ | RNW | RCVD)) {
+ udelay(33);
+ if (nvec->rx->data[0] != 0x01) {
+ dev_err(nvec->dev,
+ "Read without prior read command\n");
+ nvec->state = 0;
+ break;
}
+ nvec_msg_free(nvec, nvec->rx);
+ nvec->state = 3;
+ nvec_tx_set(nvec);
+ BUG_ON(nvec->tx->size < 1);
+ to_send = nvec->tx->data[0];
+ nvec->tx->pos = 1;
+ } else if (status == (I2C_SL_IRQ)) {
+ BUG_ON(nvec->rx == NULL);
+ nvec->rx->data[1] = received;
+ nvec->rx->pos = 2;
+ nvec->state = 4;
+ } else {
+ nvec_invalid_flags(nvec, status, true);
}
- writel(to_send, base + I2C_SL_RCVD);
-
- gpio_set_value(nvec->gpio, 1);
-
- dev_dbg(nvec->dev, "nvec sent %x\n", to_send);
-
- goto handled;
- } else {
- received = readl(base + I2C_SL_RCVD);
-
- if (status & RCVD) {
- writel(0, base + I2C_SL_RCVD);
- goto handled;
+ break;
+ case 3: /* EC does a block read, we transmit data */
+ if (status & END_TRANS) {
+ nvec_tx_completed(nvec);
+ } else if ((status & RNW) == 0 || (status & RCVD)) {
+ nvec_invalid_flags(nvec, status, true);
+ } else if (nvec->tx && nvec->tx->pos < nvec->tx->size) {
+ to_send = nvec->tx->data[nvec->tx->pos++];
+ } else {
+ dev_err(nvec->dev, "tx buffer underflow on %p (%u > %u)\n",
+ nvec->tx,
+ (uint) (nvec->tx ? nvec->tx->pos : 0),
+ (uint) (nvec->tx ? nvec->tx->size : 0));
+ nvec->state = 0;
}
+ break;
+ case 4: /* EC does some write, we read the data */
+ if ((status & (END_TRANS | RNW)) == END_TRANS)
+ nvec_rx_completed(nvec);
+ else if (status & (RNW | RCVD))
+ nvec_invalid_flags(nvec, status, true);
+ else if (nvec->rx && nvec->rx->pos < NVEC_MSG_SIZE)
+ nvec->rx->data[nvec->rx->pos++] = received;
+ else
+ dev_err(nvec->dev,
+ "RX buffer overflow on %p: "
+ "Trying to write byte %u of %u\n",
+ nvec->rx, nvec->rx->pos, NVEC_MSG_SIZE);
+ break;
+ default:
+ nvec->state = 0;
+ }
- if (nvec->state == NVEC_WAIT) {
- nvec->state = NVEC_READ;
- msg = kzalloc(sizeof(struct nvec_msg), GFP_NOWAIT);
- msg->data = kzalloc(32, GFP_NOWAIT);
- INIT_LIST_HEAD(&msg->node);
- nvec->rx = msg;
- } else
- msg = nvec->rx;
-
- BUG_ON(msg->pos > 32);
-
- msg->data[msg->pos] = received;
- msg->pos++;
- msg->size = msg->pos;
- dev_dbg(nvec->dev, "Got %02lx from Master (pos: %d)!\n",
- received, msg->pos);
+ /* If we are told that a new transfer starts, verify it */
+ if ((status & (RCVD | RNW)) == RCVD) {
+ if (received != nvec->i2c_addr)
+ dev_err(nvec->dev,
+ "received address 0x%02x, expected 0x%02x\n",
+ received, nvec->i2c_addr);
+ nvec->state = 1;
}
-handled:
+
+ /* Send data if requested, but not on end of transmission */
+ if ((status & (RNW | END_TRANS)) == RNW)
+ writel(to_send, nvec->base + I2C_SL_RCVD);
+
+ /* If we have send the first byte */
+ if (status == (I2C_SL_IRQ | RNW | RCVD))
+ nvec_gpio_set_value(nvec, 1);
+
+ dev_dbg(nvec->dev,
+ "Handled: %s 0x%02x, %s 0x%02x in state %u [%s%s%s]\n",
+ (status & RNW) == 0 ? "received" : "R=",
+ received,
+ (status & (RNW | END_TRANS)) ? "sent" : "S=",
+ to_send,
+ state,
+ status & END_TRANS ? " END_TRANS" : "",
+ status & RCVD ? " RCVD" : "",
+ status & RNW ? " RNW" : "");
+
return IRQ_HANDLED;
}
@@ -432,6 +586,7 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev)
nvec->base = base;
nvec->irq = res->start;
nvec->i2c_clk = i2c_clk;
+ nvec->rx = &nvec->msg_pool[0];
/* Set the gpio to low when we've got something to say */
err = gpio_request(nvec->gpio, "nvec gpio");
@@ -441,11 +596,15 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev)
ATOMIC_INIT_NOTIFIER_HEAD(&nvec->notifier_list);
init_completion(&nvec->sync_write);
- sema_init(&nvec->sync_write_mutex, 1);
- INIT_LIST_HEAD(&nvec->tx_data);
+ init_completion(&nvec->ec_transfer);
+ mutex_init(&nvec->sync_write_mutex);
+ spin_lock_init(&nvec->tx_lock);
+ spin_lock_init(&nvec->rx_lock);
INIT_LIST_HEAD(&nvec->rx_data);
+ INIT_LIST_HEAD(&nvec->tx_data);
INIT_WORK(&nvec->rx_work, nvec_dispatch);
INIT_WORK(&nvec->tx_work, nvec_request_master);
+ nvec->wq = alloc_workqueue("nvec", WQ_NON_REENTRANT, 2);
err = request_irq(nvec->irq, nvec_interrupt, 0, "nvec", nvec);
if (err) {
@@ -473,13 +632,14 @@ static int __devinit tegra_nvec_probe(struct platform_device *pdev)
/* Get Firmware Version */
msg = nvec_write_sync(nvec, EC_GET_FIRMWARE_VERSION,
- sizeof(EC_GET_FIRMWARE_VERSION));
+ sizeof(EC_GET_FIRMWARE_VERSION));
- dev_warn(nvec->dev, "ec firmware version %02x.%02x.%02x / %02x\n",
- msg->data[4], msg->data[5], msg->data[6], msg->data[7]);
+ if (msg) {
+ dev_warn(nvec->dev, "ec firmware version %02x.%02x.%02x / %02x\n",
+ msg->data[4], msg->data[5], msg->data[6], msg->data[7]);
- kfree(msg->data);
- kfree(msg);
+ nvec_msg_free(nvec, msg);
+ }
ret = mfd_add_devices(nvec->dev, -1, nvec_devices,
ARRAY_SIZE(nvec_devices), base, 0);
@@ -513,6 +673,7 @@ static int __devexit tegra_nvec_remove(struct platform_device *pdev)
free_irq(nvec->irq, &nvec_interrupt);
iounmap(nvec->base);
gpio_free(nvec->gpio);
+ destroy_workqueue(nvec->wq);
kfree(nvec);
return 0;