Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / net / wireless / cw1200 / bh.c
diff --git a/kernel/drivers/net/wireless/cw1200/bh.c b/kernel/drivers/net/wireless/cw1200/bh.c
new file mode 100644 (file)
index 0000000..92d299a
--- /dev/null
@@ -0,0 +1,619 @@
+/*
+ * Device handling thread implementation for mac80211 ST-Ericsson CW1200 drivers
+ *
+ * Copyright (c) 2010, ST-Ericsson
+ * Author: Dmitry Tarnyagin <dmitry.tarnyagin@lockless.no>
+ *
+ * Based on:
+ * ST-Ericsson UMAC CW1200 driver, which is
+ * Copyright (c) 2010, ST-Ericsson
+ * Author: Ajitpal Singh <ajitpal.singh@stericsson.com>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <net/mac80211.h>
+#include <linux/kthread.h>
+#include <linux/timer.h>
+
+#include "cw1200.h"
+#include "bh.h"
+#include "hwio.h"
+#include "wsm.h"
+#include "hwbus.h"
+#include "debug.h"
+#include "fwio.h"
+
+static int cw1200_bh(void *arg);
+
+#define DOWNLOAD_BLOCK_SIZE_WR (0x1000 - 4)
+/* an SPI message cannot be bigger than (2"12-1)*2 bytes
+ * "*2" to cvt to bytes
+ */
+#define MAX_SZ_RD_WR_BUFFERS   (DOWNLOAD_BLOCK_SIZE_WR*2)
+#define PIGGYBACK_CTRL_REG     (2)
+#define EFFECTIVE_BUF_SIZE     (MAX_SZ_RD_WR_BUFFERS - PIGGYBACK_CTRL_REG)
+
+/* Suspend state privates */
+enum cw1200_bh_pm_state {
+       CW1200_BH_RESUMED = 0,
+       CW1200_BH_SUSPEND,
+       CW1200_BH_SUSPENDED,
+       CW1200_BH_RESUME,
+};
+
+typedef int (*cw1200_wsm_handler)(struct cw1200_common *priv,
+       u8 *data, size_t size);
+
+static void cw1200_bh_work(struct work_struct *work)
+{
+       struct cw1200_common *priv =
+       container_of(work, struct cw1200_common, bh_work);
+       cw1200_bh(priv);
+}
+
+int cw1200_register_bh(struct cw1200_common *priv)
+{
+       int err = 0;
+       /* Realtime workqueue */
+       priv->bh_workqueue = alloc_workqueue("cw1200_bh",
+                               WQ_MEM_RECLAIM | WQ_HIGHPRI
+                               | WQ_CPU_INTENSIVE, 1);
+
+       if (!priv->bh_workqueue)
+               return -ENOMEM;
+
+       INIT_WORK(&priv->bh_work, cw1200_bh_work);
+
+       pr_debug("[BH] register.\n");
+
+       atomic_set(&priv->bh_rx, 0);
+       atomic_set(&priv->bh_tx, 0);
+       atomic_set(&priv->bh_term, 0);
+       atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
+       priv->bh_error = 0;
+       priv->hw_bufs_used = 0;
+       priv->buf_id_tx = 0;
+       priv->buf_id_rx = 0;
+       init_waitqueue_head(&priv->bh_wq);
+       init_waitqueue_head(&priv->bh_evt_wq);
+
+       err = !queue_work(priv->bh_workqueue, &priv->bh_work);
+       WARN_ON(err);
+       return err;
+}
+
+void cw1200_unregister_bh(struct cw1200_common *priv)
+{
+       atomic_add(1, &priv->bh_term);
+       wake_up(&priv->bh_wq);
+
+       flush_workqueue(priv->bh_workqueue);
+
+       destroy_workqueue(priv->bh_workqueue);
+       priv->bh_workqueue = NULL;
+
+       pr_debug("[BH] unregistered.\n");
+}
+
+void cw1200_irq_handler(struct cw1200_common *priv)
+{
+       pr_debug("[BH] irq.\n");
+
+       /* Disable Interrupts! */
+       /* NOTE:  hwbus_ops->lock already held */
+       __cw1200_irq_enable(priv, 0);
+
+       if (/* WARN_ON */(priv->bh_error))
+               return;
+
+       if (atomic_add_return(1, &priv->bh_rx) == 1)
+               wake_up(&priv->bh_wq);
+}
+EXPORT_SYMBOL_GPL(cw1200_irq_handler);
+
+void cw1200_bh_wakeup(struct cw1200_common *priv)
+{
+       pr_debug("[BH] wakeup.\n");
+       if (priv->bh_error) {
+               pr_err("[BH] wakeup failed (BH error)\n");
+               return;
+       }
+
+       if (atomic_add_return(1, &priv->bh_tx) == 1)
+               wake_up(&priv->bh_wq);
+}
+
+int cw1200_bh_suspend(struct cw1200_common *priv)
+{
+       pr_debug("[BH] suspend.\n");
+       if (priv->bh_error) {
+               wiphy_warn(priv->hw->wiphy, "BH error -- can't suspend\n");
+               return -EINVAL;
+       }
+
+       atomic_set(&priv->bh_suspend, CW1200_BH_SUSPEND);
+       wake_up(&priv->bh_wq);
+       return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
+               (CW1200_BH_SUSPENDED == atomic_read(&priv->bh_suspend)),
+                1 * HZ) ? 0 : -ETIMEDOUT;
+}
+
+int cw1200_bh_resume(struct cw1200_common *priv)
+{
+       pr_debug("[BH] resume.\n");
+       if (priv->bh_error) {
+               wiphy_warn(priv->hw->wiphy, "BH error -- can't resume\n");
+               return -EINVAL;
+       }
+
+       atomic_set(&priv->bh_suspend, CW1200_BH_RESUME);
+       wake_up(&priv->bh_wq);
+       return wait_event_timeout(priv->bh_evt_wq, priv->bh_error ||
+               (CW1200_BH_RESUMED == atomic_read(&priv->bh_suspend)),
+               1 * HZ) ? 0 : -ETIMEDOUT;
+}
+
+static inline void wsm_alloc_tx_buffer(struct cw1200_common *priv)
+{
+       ++priv->hw_bufs_used;
+}
+
+int wsm_release_tx_buffer(struct cw1200_common *priv, int count)
+{
+       int ret = 0;
+       int hw_bufs_used = priv->hw_bufs_used;
+
+       priv->hw_bufs_used -= count;
+       if (WARN_ON(priv->hw_bufs_used < 0))
+               ret = -1;
+       else if (hw_bufs_used >= priv->wsm_caps.input_buffers)
+               ret = 1;
+       if (!priv->hw_bufs_used)
+               wake_up(&priv->bh_evt_wq);
+       return ret;
+}
+
+static int cw1200_bh_read_ctrl_reg(struct cw1200_common *priv,
+                                         u16 *ctrl_reg)
+{
+       int ret;
+
+       ret = cw1200_reg_read_16(priv,
+                       ST90TDS_CONTROL_REG_ID, ctrl_reg);
+       if (ret) {
+               ret = cw1200_reg_read_16(priv,
+                               ST90TDS_CONTROL_REG_ID, ctrl_reg);
+               if (ret)
+                       pr_err("[BH] Failed to read control register.\n");
+       }
+
+       return ret;
+}
+
+static int cw1200_device_wakeup(struct cw1200_common *priv)
+{
+       u16 ctrl_reg;
+       int ret;
+
+       pr_debug("[BH] Device wakeup.\n");
+
+       /* First, set the dpll register */
+       ret = cw1200_reg_write_32(priv, ST90TDS_TSET_GEN_R_W_REG_ID,
+                                 cw1200_dpll_from_clk(priv->hw_refclk));
+       if (WARN_ON(ret))
+               return ret;
+
+       /* To force the device to be always-on, the host sets WLAN_UP to 1 */
+       ret = cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID,
+                       ST90TDS_CONT_WUP_BIT);
+       if (WARN_ON(ret))
+               return ret;
+
+       ret = cw1200_bh_read_ctrl_reg(priv, &ctrl_reg);
+       if (WARN_ON(ret))
+               return ret;
+
+       /* If the device returns WLAN_RDY as 1, the device is active and will
+        * remain active.
+        */
+       if (ctrl_reg & ST90TDS_CONT_RDY_BIT) {
+               pr_debug("[BH] Device awake.\n");
+               return 1;
+       }
+
+       return 0;
+}
+
+/* Must be called from BH thraed. */
+void cw1200_enable_powersave(struct cw1200_common *priv,
+                            bool enable)
+{
+       pr_debug("[BH] Powerave is %s.\n",
+                enable ? "enabled" : "disabled");
+       priv->powersave_enabled = enable;
+}
+
+static int cw1200_bh_rx_helper(struct cw1200_common *priv,
+                              uint16_t *ctrl_reg,
+                              int *tx)
+{
+       size_t read_len = 0;
+       struct sk_buff *skb_rx = NULL;
+       struct wsm_hdr *wsm;
+       size_t wsm_len;
+       u16 wsm_id;
+       u8 wsm_seq;
+       int rx_resync = 1;
+
+       size_t alloc_len;
+       u8 *data;
+
+       read_len = (*ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) * 2;
+       if (!read_len)
+               return 0; /* No more work */
+
+       if (WARN_ON((read_len < sizeof(struct wsm_hdr)) ||
+                   (read_len > EFFECTIVE_BUF_SIZE))) {
+               pr_debug("Invalid read len: %zu (%04x)",
+                        read_len, *ctrl_reg);
+               goto err;
+       }
+
+       /* Add SIZE of PIGGYBACK reg (CONTROL Reg)
+        * to the NEXT Message length + 2 Bytes for SKB
+        */
+       read_len = read_len + 2;
+
+       alloc_len = priv->hwbus_ops->align_size(
+               priv->hwbus_priv, read_len);
+
+       /* Check if not exceeding CW1200 capabilities */
+       if (WARN_ON_ONCE(alloc_len > EFFECTIVE_BUF_SIZE)) {
+               pr_debug("Read aligned len: %zu\n",
+                        alloc_len);
+       }
+
+       skb_rx = dev_alloc_skb(alloc_len);
+       if (WARN_ON(!skb_rx))
+               goto err;
+
+       skb_trim(skb_rx, 0);
+       skb_put(skb_rx, read_len);
+       data = skb_rx->data;
+       if (WARN_ON(!data))
+               goto err;
+
+       if (WARN_ON(cw1200_data_read(priv, data, alloc_len))) {
+               pr_err("rx blew up, len %zu\n", alloc_len);
+               goto err;
+       }
+
+       /* Piggyback */
+       *ctrl_reg = __le16_to_cpu(
+               ((__le16 *)data)[alloc_len / 2 - 1]);
+
+       wsm = (struct wsm_hdr *)data;
+       wsm_len = __le16_to_cpu(wsm->len);
+       if (WARN_ON(wsm_len > read_len))
+               goto err;
+
+       if (priv->wsm_enable_wsm_dumps)
+               print_hex_dump_bytes("<-- ",
+                                    DUMP_PREFIX_NONE,
+                                    data, wsm_len);
+
+       wsm_id  = __le16_to_cpu(wsm->id) & 0xFFF;
+       wsm_seq = (__le16_to_cpu(wsm->id) >> 13) & 7;
+
+       skb_trim(skb_rx, wsm_len);
+
+       if (wsm_id == 0x0800) {
+               wsm_handle_exception(priv,
+                                    &data[sizeof(*wsm)],
+                                    wsm_len - sizeof(*wsm));
+               goto err;
+       } else if (!rx_resync) {
+               if (WARN_ON(wsm_seq != priv->wsm_rx_seq))
+                       goto err;
+       }
+       priv->wsm_rx_seq = (wsm_seq + 1) & 7;
+       rx_resync = 0;
+
+       if (wsm_id & 0x0400) {
+               int rc = wsm_release_tx_buffer(priv, 1);
+               if (WARN_ON(rc < 0))
+                       return rc;
+               else if (rc > 0)
+                       *tx = 1;
+       }
+
+       /* cw1200_wsm_rx takes care on SKB livetime */
+       if (WARN_ON(wsm_handle_rx(priv, wsm_id, wsm, &skb_rx)))
+               goto err;
+
+       if (skb_rx) {
+               dev_kfree_skb(skb_rx);
+               skb_rx = NULL;
+       }
+
+       return 0;
+
+err:
+       if (skb_rx) {
+               dev_kfree_skb(skb_rx);
+               skb_rx = NULL;
+       }
+       return -1;
+}
+
+static int cw1200_bh_tx_helper(struct cw1200_common *priv,
+                              int *pending_tx,
+                              int *tx_burst)
+{
+       size_t tx_len;
+       u8 *data;
+       int ret;
+       struct wsm_hdr *wsm;
+
+       if (priv->device_can_sleep) {
+               ret = cw1200_device_wakeup(priv);
+               if (WARN_ON(ret < 0)) { /* Error in wakeup */
+                       *pending_tx = 1;
+                       return 0;
+               } else if (ret) { /* Woke up */
+                       priv->device_can_sleep = false;
+               } else { /* Did not awake */
+                       *pending_tx = 1;
+                       return 0;
+               }
+       }
+
+       wsm_alloc_tx_buffer(priv);
+       ret = wsm_get_tx(priv, &data, &tx_len, tx_burst);
+       if (ret <= 0) {
+               wsm_release_tx_buffer(priv, 1);
+               if (WARN_ON(ret < 0))
+                       return ret; /* Error */
+               return 0; /* No work */
+       }
+
+       wsm = (struct wsm_hdr *)data;
+       BUG_ON(tx_len < sizeof(*wsm));
+       BUG_ON(__le16_to_cpu(wsm->len) != tx_len);
+
+       atomic_add(1, &priv->bh_tx);
+
+       tx_len = priv->hwbus_ops->align_size(
+               priv->hwbus_priv, tx_len);
+
+       /* Check if not exceeding CW1200 capabilities */
+       if (WARN_ON_ONCE(tx_len > EFFECTIVE_BUF_SIZE))
+               pr_debug("Write aligned len: %zu\n", tx_len);
+
+       wsm->id &= __cpu_to_le16(0xffff ^ WSM_TX_SEQ(WSM_TX_SEQ_MAX));
+       wsm->id |= __cpu_to_le16(WSM_TX_SEQ(priv->wsm_tx_seq));
+
+       if (WARN_ON(cw1200_data_write(priv, data, tx_len))) {
+               pr_err("tx blew up, len %zu\n", tx_len);
+               wsm_release_tx_buffer(priv, 1);
+               return -1; /* Error */
+       }
+
+       if (priv->wsm_enable_wsm_dumps)
+               print_hex_dump_bytes("--> ",
+                                    DUMP_PREFIX_NONE,
+                                    data,
+                                    __le16_to_cpu(wsm->len));
+
+       wsm_txed(priv, data);
+       priv->wsm_tx_seq = (priv->wsm_tx_seq + 1) & WSM_TX_SEQ_MAX;
+
+       if (*tx_burst > 1) {
+               cw1200_debug_tx_burst(priv);
+               return 1; /* Work remains */
+       }
+
+       return 0;
+}
+
+static int cw1200_bh(void *arg)
+{
+       struct cw1200_common *priv = arg;
+       int rx, tx, term, suspend;
+       u16 ctrl_reg = 0;
+       int tx_allowed;
+       int pending_tx = 0;
+       int tx_burst;
+       long status;
+       u32 dummy;
+       int ret;
+
+       for (;;) {
+               if (!priv->hw_bufs_used &&
+                   priv->powersave_enabled &&
+                   !priv->device_can_sleep &&
+                   !atomic_read(&priv->recent_scan)) {
+                       status = 1 * HZ;
+                       pr_debug("[BH] Device wakedown. No data.\n");
+                       cw1200_reg_write_16(priv, ST90TDS_CONTROL_REG_ID, 0);
+                       priv->device_can_sleep = true;
+               } else if (priv->hw_bufs_used) {
+                       /* Interrupt loss detection */
+                       status = 1 * HZ;
+               } else {
+                       status = MAX_SCHEDULE_TIMEOUT;
+               }
+
+               /* Dummy Read for SDIO retry mechanism*/
+               if ((priv->hw_type != -1) &&
+                   (atomic_read(&priv->bh_rx) == 0) &&
+                   (atomic_read(&priv->bh_tx) == 0))
+                       cw1200_reg_read(priv, ST90TDS_CONFIG_REG_ID,
+                                       &dummy, sizeof(dummy));
+
+               pr_debug("[BH] waiting ...\n");
+               status = wait_event_interruptible_timeout(priv->bh_wq, ({
+                               rx = atomic_xchg(&priv->bh_rx, 0);
+                               tx = atomic_xchg(&priv->bh_tx, 0);
+                               term = atomic_xchg(&priv->bh_term, 0);
+                               suspend = pending_tx ?
+                                       0 : atomic_read(&priv->bh_suspend);
+                               (rx || tx || term || suspend || priv->bh_error);
+                       }), status);
+
+               pr_debug("[BH] - rx: %d, tx: %d, term: %d, bh_err: %d, suspend: %d, status: %ld\n",
+                        rx, tx, term, suspend, priv->bh_error, status);
+
+               /* Did an error occur? */
+               if ((status < 0 && status != -ERESTARTSYS) ||
+                   term || priv->bh_error) {
+                       break;
+               }
+               if (!status) {  /* wait_event timed out */
+                       unsigned long timestamp = jiffies;
+                       long timeout;
+                       int pending = 0;
+                       int i;
+
+                       /* Check to see if we have any outstanding frames */
+                       if (priv->hw_bufs_used && (!rx || !tx)) {
+                               wiphy_warn(priv->hw->wiphy,
+                                          "Missed interrupt? (%d frames outstanding)\n",
+                                          priv->hw_bufs_used);
+                               rx = 1;
+
+                               /* Get a timestamp of "oldest" frame */
+                               for (i = 0; i < 4; ++i)
+                                       pending += cw1200_queue_get_xmit_timestamp(
+                                               &priv->tx_queue[i],
+                                               &timestamp,
+                                               priv->pending_frame_id);
+
+                               /* Check if frame transmission is timed out.
+                                * Add an extra second with respect to possible
+                                * interrupt loss.
+                                */
+                               timeout = timestamp +
+                                       WSM_CMD_LAST_CHANCE_TIMEOUT +
+                                       1 * HZ  -
+                                       jiffies;
+
+                               /* And terminate BH thread if the frame is "stuck" */
+                               if (pending && timeout < 0) {
+                                       wiphy_warn(priv->hw->wiphy,
+                                                  "Timeout waiting for TX confirm (%d/%d pending, %ld vs %lu).\n",
+                                                  priv->hw_bufs_used, pending,
+                                                  timestamp, jiffies);
+                                       break;
+                               }
+                       } else if (!priv->device_can_sleep &&
+                                  !atomic_read(&priv->recent_scan)) {
+                               pr_debug("[BH] Device wakedown. Timeout.\n");
+                               cw1200_reg_write_16(priv,
+                                                   ST90TDS_CONTROL_REG_ID, 0);
+                               priv->device_can_sleep = true;
+                       }
+                       goto done;
+               } else if (suspend) {
+                       pr_debug("[BH] Device suspend.\n");
+                       if (priv->powersave_enabled) {
+                               pr_debug("[BH] Device wakedown. Suspend.\n");
+                               cw1200_reg_write_16(priv,
+                                                   ST90TDS_CONTROL_REG_ID, 0);
+                               priv->device_can_sleep = true;
+                       }
+
+                       atomic_set(&priv->bh_suspend, CW1200_BH_SUSPENDED);
+                       wake_up(&priv->bh_evt_wq);
+                       status = wait_event_interruptible(priv->bh_wq,
+                                                         CW1200_BH_RESUME == atomic_read(&priv->bh_suspend));
+                       if (status < 0) {
+                               wiphy_err(priv->hw->wiphy,
+                                         "Failed to wait for resume: %ld.\n",
+                                         status);
+                               break;
+                       }
+                       pr_debug("[BH] Device resume.\n");
+                       atomic_set(&priv->bh_suspend, CW1200_BH_RESUMED);
+                       wake_up(&priv->bh_evt_wq);
+                       atomic_add(1, &priv->bh_rx);
+                       goto done;
+               }
+
+       rx:
+               tx += pending_tx;
+               pending_tx = 0;
+
+               if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
+                       break;
+
+               /* Don't bother trying to rx unless we have data to read */
+               if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
+                       ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
+                       if (ret < 0)
+                               break;
+                       /* Double up here if there's more data.. */
+                       if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK) {
+                               ret = cw1200_bh_rx_helper(priv, &ctrl_reg, &tx);
+                               if (ret < 0)
+                                       break;
+                       }
+               }
+
+       tx:
+               if (tx) {
+                       tx = 0;
+
+                       BUG_ON(priv->hw_bufs_used > priv->wsm_caps.input_buffers);
+                       tx_burst = priv->wsm_caps.input_buffers - priv->hw_bufs_used;
+                       tx_allowed = tx_burst > 0;
+
+                       if (!tx_allowed) {
+                               /* Buffers full.  Ensure we process tx
+                                * after we handle rx..
+                                */
+                               pending_tx = tx;
+                               goto done_rx;
+                       }
+                       ret = cw1200_bh_tx_helper(priv, &pending_tx, &tx_burst);
+                       if (ret < 0)
+                               break;
+                       if (ret > 0) /* More to transmit */
+                               tx = ret;
+
+                       /* Re-read ctrl reg */
+                       if (cw1200_bh_read_ctrl_reg(priv, &ctrl_reg))
+                               break;
+               }
+
+       done_rx:
+               if (priv->bh_error)
+                       break;
+               if (ctrl_reg & ST90TDS_CONT_NEXT_LEN_MASK)
+                       goto rx;
+               if (tx)
+                       goto tx;
+
+       done:
+               /* Re-enable device interrupts */
+               priv->hwbus_ops->lock(priv->hwbus_priv);
+               __cw1200_irq_enable(priv, 1);
+               priv->hwbus_ops->unlock(priv->hwbus_priv);
+       }
+
+       /* Explicitly disable device interrupts */
+       priv->hwbus_ops->lock(priv->hwbus_priv);
+       __cw1200_irq_enable(priv, 0);
+       priv->hwbus_ops->unlock(priv->hwbus_priv);
+
+       if (!term) {
+               pr_err("[BH] Fatal error, exiting.\n");
+               priv->bh_error = 1;
+               /* TODO: schedule_work(recovery) */
+       }
+       return 0;
+}