diff options
Diffstat (limited to 'drivers/staging/nvec/nvec.c')
-rw-r--r-- | drivers/staging/nvec/nvec.c | 389 |
1 files changed, 275 insertions, 114 deletions
diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c index 8e97e2d..1a120c1 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; |