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. Signed-off-by: Julian Andres Klode <jak@xxxxxxxxxxxxx> --- drivers/staging/nvec/nvec.c | 391 +++++++++++++++++++++++++++------------ drivers/staging/nvec/nvec.h | 55 +++--- drivers/staging/nvec/nvec_kbd.c | 2 +- 3 files changed, 305 insertions(+), 143 deletions(-) diff --git a/drivers/staging/nvec/nvec.c b/drivers/staging/nvec/nvec.c index 005d389..0df3270 100644 --- a/drivers/staging/nvec/nvec.c +++ b/drivers/staging/nvec/nvec.c @@ -153,27 +153,71 @@ 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) +{ + 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"); + + mutex_unlock(&nvec->sync_write_mutex); + + return nvec->last_sync_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; + struct nvec_msg *msg; - if (!list_empty(&nvec->tx_data)) - gpio_set_value(nvec->gpio, 0); + 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); + if (!(wait_for_completion_interruptible_timeout( + &nvec->ec_transfer, msecs_to_jiffies(5000)))) { + dev_warn(nvec->dev, "timeout waiting for ec transfer\n"); + nvec_gpio_set_value(nvec, 1); + msg->pos = 0; + } else { + list_del_init(&msg->node); + nvec_msg_free(nvec, msg); + } + spin_lock_irqsave(&nvec->tx_lock, flags); + } + spin_unlock_irqrestore(&nvec->tx_lock, flags); } static int parse_msg(struct nvec_chip *nvec, struct nvec_msg *msg) @@ -195,143 +239,253 @@ 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 irqreturn_t nvec_interrupt(int irq, void *dev) +static void nvec_tx_completed(struct nvec_chip *nvec) { - 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; + /* 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; + } +} - status = readl(base + I2C_SL_STATUS); +static void nvec_rx_completed(struct nvec_chip *nvec) +{ + unsigned long flags; - if (!(status & I2C_SL_IRQ)) { - dev_warn(nvec->dev, "nvec Spurious IRQ\n"); - goto 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); - } - return IRQ_HANDLED; - } else if (status & RNW) { - if (status & RCVD) - udelay(3); + 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); - if (status & RCVD) - nvec->state = NVEC_WRITE; + spin_lock_irqsave(&nvec->rx_lock, flags); - 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); - } 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; - } + /* 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); - if (msg->pos >= msg->size) { - list_del_init(&msg->node); - kfree(msg->data); - kfree(msg); - schedule_work(&nvec->tx_work); - nvec->state = NVEC_WAIT; - } - } - writel(to_send, base + I2C_SL_RCVD); + spin_unlock_irqrestore(&nvec->rx_lock, flags); - gpio_set_value(nvec->gpio, 1); + nvec->state = 0; - dev_dbg(nvec->dev, "nvec sent %x\n", to_send); + if (!nvec_msg_is_event(nvec->rx)) + complete(&nvec->ec_transfer); - goto handled; + 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) +{ + unsigned long flags; + + spin_lock_irqsave(&nvec->tx_lock, flags); + 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 { - received = readl(base + I2C_SL_RCVD); + nvec->tx = list_first_entry(&nvec->tx_data, struct nvec_msg, + node); + nvec->tx->pos = 0; + } + spin_unlock_irqrestore(&nvec->tx_lock, flags); + + 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 flags; + unsigned long status; + 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(nvec->base + I2C_SL_STATUS); + + /* 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 & I2C_SL_IRQ) == 0) { + dev_err(nvec->dev, "Spurious IRQ\n"); + return IRQ_HANDLED; + } + + /* The EC did not request a read, so it send us something, read it */ + if ((status & RNW) == 0) { if (status & RCVD) { - writel(0, base + I2C_SL_RCVD); - goto handled; + local_irq_save(flags); + received = readl(nvec->base + I2C_SL_RCVD); + writel(0, nvec->base + I2C_SL_RCVD); + local_irq_restore(flags); + } else { + received = readl(nvec->base + I2C_SL_RCVD); + } + } + + 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 { + 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); + } + 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; } @@ -430,6 +584,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"); @@ -439,11 +594,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) { @@ -471,13 +630,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); @@ -511,6 +671,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; diff --git a/drivers/staging/nvec/nvec.h b/drivers/staging/nvec/nvec.h index 5440802..0a98b92 100644 --- a/drivers/staging/nvec/nvec.h +++ b/drivers/staging/nvec/nvec.h @@ -23,39 +23,36 @@ /* NVEC_POOL_SIZE - Size of the pool in &struct nvec_msg */ #define NVEC_POOL_SIZE 64 -typedef enum { +/* + * NVEC_MSG_SIZE - Maximum size of the data field of &struct nvec_msg. + * + * A message must store up to a SMBus block operation which consists of + * one command byte, one count byte, and up to 32 payload bytes = 34 + * byte. + */ +#define NVEC_MSG_SIZE 34 + +enum { NVEC_2BYTES, NVEC_3BYTES, - NVEC_VAR_SIZE -} nvec_size; - -typedef enum { - NOT_REALLY, - YES, - NOT_AT_ALL, -} how_care; + NVEC_VAR_SIZE, +}; -typedef enum { +enum { NVEC_SYS = 1, NVEC_BAT, NVEC_KBD = 5, NVEC_PS2, NVEC_CNTL, NVEC_KB_EVT = 0x80, - NVEC_PS2_EVT -} nvec_event; - -typedef enum { - NVEC_WAIT, - NVEC_READ, - NVEC_WRITE -} nvec_state; + NVEC_PS2_EVT, +}; struct nvec_msg { - unsigned char *data; + struct list_head node; + unsigned char data[NVEC_MSG_SIZE]; unsigned short size; unsigned short pos; - struct list_head node; atomic_t used; }; @@ -77,19 +74,27 @@ struct nvec_chip { int i2c_addr; void __iomem *base; struct clk *i2c_clk; - nvec_state state; struct atomic_notifier_head notifier_list; struct list_head rx_data, tx_data; struct notifier_block nvec_status_notifier; struct work_struct rx_work, tx_work; - struct nvec_msg *rx, *tx; + struct workqueue_struct *wq; struct nvec_msg msg_pool[NVEC_POOL_SIZE]; + struct nvec_msg *rx; + + struct nvec_msg *tx; + struct nvec_msg tx_scratch; + struct completion ec_transfer; + + spinlock_t tx_lock, rx_lock; /* sync write stuff */ - struct semaphore sync_write_mutex; + struct mutex sync_write_mutex; struct completion sync_write; u16 sync_write_pending; struct nvec_msg *last_sync_msg; + + int state; }; extern void nvec_write_async(struct nvec_chip *nvec, const unsigned char *data, @@ -103,10 +108,6 @@ extern int nvec_unregister_notifier(struct device *dev, struct notifier_block *nb, unsigned int events); -const char *nvec_send_msg(unsigned char *src, unsigned char *dst_size, - how_care care_resp, - void (*rt_handler) (unsigned char *data)); - #define I2C_CNFG 0x00 #define I2C_CNFG_PACKET_MODE_EN (1<<10) #define I2C_CNFG_NEW_MASTER_SFM (1<<11) diff --git a/drivers/staging/nvec/nvec_kbd.c b/drivers/staging/nvec/nvec_kbd.c index eaaafac..167eac0 100644 --- a/drivers/staging/nvec/nvec_kbd.c +++ b/drivers/staging/nvec/nvec_kbd.c @@ -41,7 +41,7 @@ static int nvec_keys_notifier(struct notifier_block *nb, unsigned char *msg = (unsigned char *)data; if (event_type == NVEC_KB_EVT) { - nvec_size _size = (msg[0] & (3 << 5)) >> 5; + int _size = (msg[0] & (3 << 5)) >> 5; /* power on/off button */ if (_size == NVEC_VAR_SIZE) -- 1.7.5.4 _______________________________________________ devel mailing list devel@xxxxxxxxxxxxxxxxxxxxxx http://driverdev.linuxdriverproject.org/mailman/listinfo/devel