Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / block / rsxx / cregs.c
diff --git a/kernel/drivers/block/rsxx/cregs.c b/kernel/drivers/block/rsxx/cregs.c
new file mode 100644 (file)
index 0000000..926dce9
--- /dev/null
@@ -0,0 +1,804 @@
+/*
+* Filename: cregs.c
+*
+*
+* Authors: Joshua Morris <josh.h.morris@us.ibm.com>
+*      Philip Kelleher <pjk1939@linux.vnet.ibm.com>
+*
+* (C) Copyright 2013 IBM Corporation
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License as
+* published by the Free Software Foundation; either version 2 of the
+* License, or (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful, but
+* WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program; if not, write to the Free Software Foundation,
+* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+
+#include <linux/completion.h>
+#include <linux/slab.h>
+
+#include "rsxx_priv.h"
+
+#define CREG_TIMEOUT_MSEC      10000
+
+typedef void (*creg_cmd_cb)(struct rsxx_cardinfo *card,
+                           struct creg_cmd *cmd,
+                           int st);
+
+struct creg_cmd {
+       struct list_head list;
+       creg_cmd_cb cb;
+       void *cb_private;
+       unsigned int op;
+       unsigned int addr;
+       int cnt8;
+       void *buf;
+       unsigned int stream;
+       unsigned int status;
+};
+
+static struct kmem_cache *creg_cmd_pool;
+
+
+/*------------ Private Functions --------------*/
+
+#if defined(__LITTLE_ENDIAN)
+#define LITTLE_ENDIAN 1
+#elif defined(__BIG_ENDIAN)
+#define LITTLE_ENDIAN 0
+#else
+#error Unknown endianess!!! Aborting...
+#endif
+
+static int copy_to_creg_data(struct rsxx_cardinfo *card,
+                             int cnt8,
+                             void *buf,
+                             unsigned int stream)
+{
+       int i = 0;
+       u32 *data = buf;
+
+       if (unlikely(card->eeh_state))
+               return -EIO;
+
+       for (i = 0; cnt8 > 0; i++, cnt8 -= 4) {
+               /*
+                * Firmware implementation makes it necessary to byte swap on
+                * little endian processors.
+                */
+               if (LITTLE_ENDIAN && stream)
+                       iowrite32be(data[i], card->regmap + CREG_DATA(i));
+               else
+                       iowrite32(data[i], card->regmap + CREG_DATA(i));
+       }
+
+       return 0;
+}
+
+
+static int copy_from_creg_data(struct rsxx_cardinfo *card,
+                               int cnt8,
+                               void *buf,
+                               unsigned int stream)
+{
+       int i = 0;
+       u32 *data = buf;
+
+       if (unlikely(card->eeh_state))
+               return -EIO;
+
+       for (i = 0; cnt8 > 0; i++, cnt8 -= 4) {
+               /*
+                * Firmware implementation makes it necessary to byte swap on
+                * little endian processors.
+                */
+               if (LITTLE_ENDIAN && stream)
+                       data[i] = ioread32be(card->regmap + CREG_DATA(i));
+               else
+                       data[i] = ioread32(card->regmap + CREG_DATA(i));
+       }
+
+       return 0;
+}
+
+static void creg_issue_cmd(struct rsxx_cardinfo *card, struct creg_cmd *cmd)
+{
+       int st;
+
+       if (unlikely(card->eeh_state))
+               return;
+
+       iowrite32(cmd->addr, card->regmap + CREG_ADD);
+       iowrite32(cmd->cnt8, card->regmap + CREG_CNT);
+
+       if (cmd->op == CREG_OP_WRITE) {
+               if (cmd->buf) {
+                       st = copy_to_creg_data(card, cmd->cnt8,
+                                              cmd->buf, cmd->stream);
+                       if (st)
+                               return;
+               }
+       }
+
+       if (unlikely(card->eeh_state))
+               return;
+
+       /* Setting the valid bit will kick off the command. */
+       iowrite32(cmd->op, card->regmap + CREG_CMD);
+}
+
+static void creg_kick_queue(struct rsxx_cardinfo *card)
+{
+       if (card->creg_ctrl.active || list_empty(&card->creg_ctrl.queue))
+               return;
+
+       card->creg_ctrl.active = 1;
+       card->creg_ctrl.active_cmd = list_first_entry(&card->creg_ctrl.queue,
+                                                     struct creg_cmd, list);
+       list_del(&card->creg_ctrl.active_cmd->list);
+       card->creg_ctrl.q_depth--;
+
+       /*
+        * We have to set the timer before we push the new command. Otherwise,
+        * we could create a race condition that would occur if the timer
+        * was not canceled, and expired after the new command was pushed,
+        * but before the command was issued to hardware.
+        */
+       mod_timer(&card->creg_ctrl.cmd_timer,
+                               jiffies + msecs_to_jiffies(CREG_TIMEOUT_MSEC));
+
+       creg_issue_cmd(card, card->creg_ctrl.active_cmd);
+}
+
+static int creg_queue_cmd(struct rsxx_cardinfo *card,
+                         unsigned int op,
+                         unsigned int addr,
+                         unsigned int cnt8,
+                         void *buf,
+                         int stream,
+                         creg_cmd_cb callback,
+                         void *cb_private)
+{
+       struct creg_cmd *cmd;
+
+       /* Don't queue stuff up if we're halted. */
+       if (unlikely(card->halt))
+               return -EINVAL;
+
+       if (card->creg_ctrl.reset)
+               return -EAGAIN;
+
+       if (cnt8 > MAX_CREG_DATA8)
+               return -EINVAL;
+
+       cmd = kmem_cache_alloc(creg_cmd_pool, GFP_KERNEL);
+       if (!cmd)
+               return -ENOMEM;
+
+       INIT_LIST_HEAD(&cmd->list);
+
+       cmd->op         = op;
+       cmd->addr       = addr;
+       cmd->cnt8       = cnt8;
+       cmd->buf        = buf;
+       cmd->stream     = stream;
+       cmd->cb         = callback;
+       cmd->cb_private = cb_private;
+       cmd->status     = 0;
+
+       spin_lock_bh(&card->creg_ctrl.lock);
+       list_add_tail(&cmd->list, &card->creg_ctrl.queue);
+       card->creg_ctrl.q_depth++;
+       creg_kick_queue(card);
+       spin_unlock_bh(&card->creg_ctrl.lock);
+
+       return 0;
+}
+
+static void creg_cmd_timed_out(unsigned long data)
+{
+       struct rsxx_cardinfo *card = (struct rsxx_cardinfo *) data;
+       struct creg_cmd *cmd;
+
+       spin_lock(&card->creg_ctrl.lock);
+       cmd = card->creg_ctrl.active_cmd;
+       card->creg_ctrl.active_cmd = NULL;
+       spin_unlock(&card->creg_ctrl.lock);
+
+       if (cmd == NULL) {
+               card->creg_ctrl.creg_stats.creg_timeout++;
+               dev_warn(CARD_TO_DEV(card),
+                       "No active command associated with timeout!\n");
+               return;
+       }
+
+       if (cmd->cb)
+               cmd->cb(card, cmd, -ETIMEDOUT);
+
+       kmem_cache_free(creg_cmd_pool, cmd);
+
+
+       spin_lock(&card->creg_ctrl.lock);
+       card->creg_ctrl.active = 0;
+       creg_kick_queue(card);
+       spin_unlock(&card->creg_ctrl.lock);
+}
+
+
+static void creg_cmd_done(struct work_struct *work)
+{
+       struct rsxx_cardinfo *card;
+       struct creg_cmd *cmd;
+       int st = 0;
+
+       card = container_of(work, struct rsxx_cardinfo,
+                           creg_ctrl.done_work);
+
+       /*
+        * The timer could not be cancelled for some reason,
+        * race to pop the active command.
+        */
+       if (del_timer_sync(&card->creg_ctrl.cmd_timer) == 0)
+               card->creg_ctrl.creg_stats.failed_cancel_timer++;
+
+       spin_lock_bh(&card->creg_ctrl.lock);
+       cmd = card->creg_ctrl.active_cmd;
+       card->creg_ctrl.active_cmd = NULL;
+       spin_unlock_bh(&card->creg_ctrl.lock);
+
+       if (cmd == NULL) {
+               dev_err(CARD_TO_DEV(card),
+                       "Spurious creg interrupt!\n");
+               return;
+       }
+
+       card->creg_ctrl.creg_stats.stat = ioread32(card->regmap + CREG_STAT);
+       cmd->status = card->creg_ctrl.creg_stats.stat;
+       if ((cmd->status & CREG_STAT_STATUS_MASK) == 0) {
+               dev_err(CARD_TO_DEV(card),
+                       "Invalid status on creg command\n");
+               /*
+                * At this point we're probably reading garbage from HW. Don't
+                * do anything else that could mess up the system and let
+                * the sync function return an error.
+                */
+               st = -EIO;
+               goto creg_done;
+       } else if (cmd->status & CREG_STAT_ERROR) {
+               st = -EIO;
+       }
+
+       if ((cmd->op == CREG_OP_READ)) {
+               unsigned int cnt8 = ioread32(card->regmap + CREG_CNT);
+
+               /* Paranoid Sanity Checks */
+               if (!cmd->buf) {
+                       dev_err(CARD_TO_DEV(card),
+                               "Buffer not given for read.\n");
+                       st = -EIO;
+                       goto creg_done;
+               }
+               if (cnt8 != cmd->cnt8) {
+                       dev_err(CARD_TO_DEV(card),
+                               "count mismatch\n");
+                       st = -EIO;
+                       goto creg_done;
+               }
+
+               st = copy_from_creg_data(card, cnt8, cmd->buf, cmd->stream);
+       }
+
+creg_done:
+       if (cmd->cb)
+               cmd->cb(card, cmd, st);
+
+       kmem_cache_free(creg_cmd_pool, cmd);
+
+       spin_lock_bh(&card->creg_ctrl.lock);
+       card->creg_ctrl.active = 0;
+       creg_kick_queue(card);
+       spin_unlock_bh(&card->creg_ctrl.lock);
+}
+
+static void creg_reset(struct rsxx_cardinfo *card)
+{
+       struct creg_cmd *cmd = NULL;
+       struct creg_cmd *tmp;
+       unsigned long flags;
+
+       /*
+        * mutex_trylock is used here because if reset_lock is taken then a
+        * reset is already happening. So, we can just go ahead and return.
+        */
+       if (!mutex_trylock(&card->creg_ctrl.reset_lock))
+               return;
+
+       card->creg_ctrl.reset = 1;
+       spin_lock_irqsave(&card->irq_lock, flags);
+       rsxx_disable_ier_and_isr(card, CR_INTR_CREG | CR_INTR_EVENT);
+       spin_unlock_irqrestore(&card->irq_lock, flags);
+
+       dev_warn(CARD_TO_DEV(card),
+               "Resetting creg interface for recovery\n");
+
+       /* Cancel outstanding commands */
+       spin_lock_bh(&card->creg_ctrl.lock);
+       list_for_each_entry_safe(cmd, tmp, &card->creg_ctrl.queue, list) {
+               list_del(&cmd->list);
+               card->creg_ctrl.q_depth--;
+               if (cmd->cb)
+                       cmd->cb(card, cmd, -ECANCELED);
+               kmem_cache_free(creg_cmd_pool, cmd);
+       }
+
+       cmd = card->creg_ctrl.active_cmd;
+       card->creg_ctrl.active_cmd = NULL;
+       if (cmd) {
+               if (timer_pending(&card->creg_ctrl.cmd_timer))
+                       del_timer_sync(&card->creg_ctrl.cmd_timer);
+
+               if (cmd->cb)
+                       cmd->cb(card, cmd, -ECANCELED);
+               kmem_cache_free(creg_cmd_pool, cmd);
+
+               card->creg_ctrl.active = 0;
+       }
+       spin_unlock_bh(&card->creg_ctrl.lock);
+
+       card->creg_ctrl.reset = 0;
+       spin_lock_irqsave(&card->irq_lock, flags);
+       rsxx_enable_ier_and_isr(card, CR_INTR_CREG | CR_INTR_EVENT);
+       spin_unlock_irqrestore(&card->irq_lock, flags);
+
+       mutex_unlock(&card->creg_ctrl.reset_lock);
+}
+
+/* Used for synchronous accesses */
+struct creg_completion {
+       struct completion       *cmd_done;
+       int                     st;
+       u32                     creg_status;
+};
+
+static void creg_cmd_done_cb(struct rsxx_cardinfo *card,
+                            struct creg_cmd *cmd,
+                            int st)
+{
+       struct creg_completion *cmd_completion;
+
+       cmd_completion = cmd->cb_private;
+       BUG_ON(!cmd_completion);
+
+       cmd_completion->st = st;
+       cmd_completion->creg_status = cmd->status;
+       complete(cmd_completion->cmd_done);
+}
+
+static int __issue_creg_rw(struct rsxx_cardinfo *card,
+                          unsigned int op,
+                          unsigned int addr,
+                          unsigned int cnt8,
+                          void *buf,
+                          int stream,
+                          unsigned int *hw_stat)
+{
+       DECLARE_COMPLETION_ONSTACK(cmd_done);
+       struct creg_completion completion;
+       unsigned long timeout;
+       int st;
+
+       completion.cmd_done = &cmd_done;
+       completion.st = 0;
+       completion.creg_status = 0;
+
+       st = creg_queue_cmd(card, op, addr, cnt8, buf, stream, creg_cmd_done_cb,
+                           &completion);
+       if (st)
+               return st;
+
+       /*
+        * This timeout is necessary for unresponsive hardware. The additional
+        * 20 seconds to used to guarantee that each cregs requests has time to
+        * complete.
+        */
+       timeout = msecs_to_jiffies(CREG_TIMEOUT_MSEC *
+                                  card->creg_ctrl.q_depth + 20000);
+
+       /*
+        * The creg interface is guaranteed to complete. It has a timeout
+        * mechanism that will kick in if hardware does not respond.
+        */
+       st = wait_for_completion_timeout(completion.cmd_done, timeout);
+       if (st == 0) {
+               /*
+                * This is really bad, because the kernel timer did not
+                * expire and notify us of a timeout!
+                */
+               dev_crit(CARD_TO_DEV(card),
+                       "cregs timer failed\n");
+               creg_reset(card);
+               return -EIO;
+       }
+
+       *hw_stat = completion.creg_status;
+
+       if (completion.st) {
+               /*
+               * This read is needed to verify that there has not been any
+               * extreme errors that might have occurred, i.e. EEH. The
+               * function iowrite32 will not detect EEH errors, so it is
+               * necessary that we recover if such an error is the reason
+               * for the timeout. This is a dummy read.
+               */
+               ioread32(card->regmap + SCRATCH);
+
+               dev_warn(CARD_TO_DEV(card),
+                       "creg command failed(%d x%08x)\n",
+                       completion.st, addr);
+               return completion.st;
+       }
+
+       return 0;
+}
+
+static int issue_creg_rw(struct rsxx_cardinfo *card,
+                        u32 addr,
+                        unsigned int size8,
+                        void *data,
+                        int stream,
+                        int read)
+{
+       unsigned int hw_stat;
+       unsigned int xfer;
+       unsigned int op;
+       int st;
+
+       op = read ? CREG_OP_READ : CREG_OP_WRITE;
+
+       do {
+               xfer = min_t(unsigned int, size8, MAX_CREG_DATA8);
+
+               st = __issue_creg_rw(card, op, addr, xfer,
+                                    data, stream, &hw_stat);
+               if (st)
+                       return st;
+
+               data   = (char *)data + xfer;
+               addr  += xfer;
+               size8 -= xfer;
+       } while (size8);
+
+       return 0;
+}
+
+/* ---------------------------- Public API ---------------------------------- */
+int rsxx_creg_write(struct rsxx_cardinfo *card,
+                       u32 addr,
+                       unsigned int size8,
+                       void *data,
+                       int byte_stream)
+{
+       return issue_creg_rw(card, addr, size8, data, byte_stream, 0);
+}
+
+int rsxx_creg_read(struct rsxx_cardinfo *card,
+                      u32 addr,
+                      unsigned int size8,
+                      void *data,
+                      int byte_stream)
+{
+       return issue_creg_rw(card, addr, size8, data, byte_stream, 1);
+}
+
+int rsxx_get_card_state(struct rsxx_cardinfo *card, unsigned int *state)
+{
+       return rsxx_creg_read(card, CREG_ADD_CARD_STATE,
+                                 sizeof(*state), state, 0);
+}
+
+int rsxx_get_card_size8(struct rsxx_cardinfo *card, u64 *size8)
+{
+       unsigned int size;
+       int st;
+
+       st = rsxx_creg_read(card, CREG_ADD_CARD_SIZE,
+                               sizeof(size), &size, 0);
+       if (st)
+               return st;
+
+       *size8 = (u64)size * RSXX_HW_BLK_SIZE;
+       return 0;
+}
+
+int rsxx_get_num_targets(struct rsxx_cardinfo *card,
+                            unsigned int *n_targets)
+{
+       return rsxx_creg_read(card, CREG_ADD_NUM_TARGETS,
+                                 sizeof(*n_targets), n_targets, 0);
+}
+
+int rsxx_get_card_capabilities(struct rsxx_cardinfo *card,
+                                  u32 *capabilities)
+{
+       return rsxx_creg_read(card, CREG_ADD_CAPABILITIES,
+                                 sizeof(*capabilities), capabilities, 0);
+}
+
+int rsxx_issue_card_cmd(struct rsxx_cardinfo *card, u32 cmd)
+{
+       return rsxx_creg_write(card, CREG_ADD_CARD_CMD,
+                                  sizeof(cmd), &cmd, 0);
+}
+
+
+/*----------------- HW Log Functions -------------------*/
+static void hw_log_msg(struct rsxx_cardinfo *card, const char *str, int len)
+{
+       static char level;
+
+       /*
+        * New messages start with "<#>", where # is the log level. Messages
+        * that extend past the log buffer will use the previous level
+        */
+       if ((len > 3) && (str[0] == '<') && (str[2] == '>')) {
+               level = str[1];
+               str += 3; /* Skip past the log level. */
+               len -= 3;
+       }
+
+       switch (level) {
+       case '0':
+               dev_emerg(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '1':
+               dev_alert(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '2':
+               dev_crit(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '3':
+               dev_err(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '4':
+               dev_warn(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '5':
+               dev_notice(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '6':
+               dev_info(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       case '7':
+               dev_dbg(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       default:
+               dev_info(CARD_TO_DEV(card), "HW: %.*s", len, str);
+               break;
+       }
+}
+
+/*
+ * The substrncpy function copies the src string (which includes the
+ * terminating '\0' character), up to the count into the dest pointer.
+ * Returns the number of bytes copied to dest.
+ */
+static int substrncpy(char *dest, const char *src, int count)
+{
+       int max_cnt = count;
+
+       while (count) {
+               count--;
+               *dest = *src;
+               if (*dest == '\0')
+                       break;
+               src++;
+               dest++;
+       }
+       return max_cnt - count;
+}
+
+
+static void read_hw_log_done(struct rsxx_cardinfo *card,
+                            struct creg_cmd *cmd,
+                            int st)
+{
+       char *buf;
+       char *log_str;
+       int cnt;
+       int len;
+       int off;
+
+       buf = cmd->buf;
+       off = 0;
+
+       /* Failed getting the log message */
+       if (st)
+               return;
+
+       while (off < cmd->cnt8) {
+               log_str = &card->log.buf[card->log.buf_len];
+               cnt = min(cmd->cnt8 - off, LOG_BUF_SIZE8 - card->log.buf_len);
+               len = substrncpy(log_str, &buf[off], cnt);
+
+               off += len;
+               card->log.buf_len += len;
+
+               /*
+                * Flush the log if we've hit the end of a message or if we've
+                * run out of buffer space.
+                */
+               if ((log_str[len - 1] == '\0')  ||
+                   (card->log.buf_len == LOG_BUF_SIZE8)) {
+                       if (card->log.buf_len != 1) /* Don't log blank lines. */
+                               hw_log_msg(card, card->log.buf,
+                                          card->log.buf_len);
+                       card->log.buf_len = 0;
+               }
+
+       }
+
+       if (cmd->status & CREG_STAT_LOG_PENDING)
+               rsxx_read_hw_log(card);
+}
+
+int rsxx_read_hw_log(struct rsxx_cardinfo *card)
+{
+       int st;
+
+       st = creg_queue_cmd(card, CREG_OP_READ, CREG_ADD_LOG,
+                           sizeof(card->log.tmp), card->log.tmp,
+                           1, read_hw_log_done, NULL);
+       if (st)
+               dev_err(CARD_TO_DEV(card),
+                       "Failed getting log text\n");
+
+       return st;
+}
+
+/*-------------- IOCTL REG Access ------------------*/
+static int issue_reg_cmd(struct rsxx_cardinfo *card,
+                        struct rsxx_reg_access *cmd,
+                        int read)
+{
+       unsigned int op = read ? CREG_OP_READ : CREG_OP_WRITE;
+
+       return __issue_creg_rw(card, op, cmd->addr, cmd->cnt, cmd->data,
+                              cmd->stream, &cmd->stat);
+}
+
+int rsxx_reg_access(struct rsxx_cardinfo *card,
+                       struct rsxx_reg_access __user *ucmd,
+                       int read)
+{
+       struct rsxx_reg_access cmd;
+       int st;
+
+       st = copy_from_user(&cmd, ucmd, sizeof(cmd));
+       if (st)
+               return -EFAULT;
+
+       if (cmd.cnt > RSXX_MAX_REG_CNT)
+               return -EFAULT;
+
+       st = issue_reg_cmd(card, &cmd, read);
+       if (st)
+               return st;
+
+       st = put_user(cmd.stat, &ucmd->stat);
+       if (st)
+               return -EFAULT;
+
+       if (read) {
+               st = copy_to_user(ucmd->data, cmd.data, cmd.cnt);
+               if (st)
+                       return -EFAULT;
+       }
+
+       return 0;
+}
+
+void rsxx_eeh_save_issued_creg(struct rsxx_cardinfo *card)
+{
+       struct creg_cmd *cmd = NULL;
+
+       cmd = card->creg_ctrl.active_cmd;
+       card->creg_ctrl.active_cmd = NULL;
+
+       if (cmd) {
+               del_timer_sync(&card->creg_ctrl.cmd_timer);
+
+               spin_lock_bh(&card->creg_ctrl.lock);
+               list_add(&cmd->list, &card->creg_ctrl.queue);
+               card->creg_ctrl.q_depth++;
+               card->creg_ctrl.active = 0;
+               spin_unlock_bh(&card->creg_ctrl.lock);
+       }
+}
+
+void rsxx_kick_creg_queue(struct rsxx_cardinfo *card)
+{
+       spin_lock_bh(&card->creg_ctrl.lock);
+       if (!list_empty(&card->creg_ctrl.queue))
+               creg_kick_queue(card);
+       spin_unlock_bh(&card->creg_ctrl.lock);
+}
+
+/*------------ Initialization & Setup --------------*/
+int rsxx_creg_setup(struct rsxx_cardinfo *card)
+{
+       card->creg_ctrl.active_cmd = NULL;
+
+       card->creg_ctrl.creg_wq =
+                       create_singlethread_workqueue(DRIVER_NAME"_creg");
+       if (!card->creg_ctrl.creg_wq)
+               return -ENOMEM;
+
+       INIT_WORK(&card->creg_ctrl.done_work, creg_cmd_done);
+       mutex_init(&card->creg_ctrl.reset_lock);
+       INIT_LIST_HEAD(&card->creg_ctrl.queue);
+       spin_lock_init(&card->creg_ctrl.lock);
+       setup_timer(&card->creg_ctrl.cmd_timer, creg_cmd_timed_out,
+                   (unsigned long) card);
+
+       return 0;
+}
+
+void rsxx_creg_destroy(struct rsxx_cardinfo *card)
+{
+       struct creg_cmd *cmd;
+       struct creg_cmd *tmp;
+       int cnt = 0;
+
+       /* Cancel outstanding commands */
+       spin_lock_bh(&card->creg_ctrl.lock);
+       list_for_each_entry_safe(cmd, tmp, &card->creg_ctrl.queue, list) {
+               list_del(&cmd->list);
+               if (cmd->cb)
+                       cmd->cb(card, cmd, -ECANCELED);
+               kmem_cache_free(creg_cmd_pool, cmd);
+               cnt++;
+       }
+
+       if (cnt)
+               dev_info(CARD_TO_DEV(card),
+                       "Canceled %d queue creg commands\n", cnt);
+
+       cmd = card->creg_ctrl.active_cmd;
+       card->creg_ctrl.active_cmd = NULL;
+       if (cmd) {
+               if (timer_pending(&card->creg_ctrl.cmd_timer))
+                       del_timer_sync(&card->creg_ctrl.cmd_timer);
+
+               if (cmd->cb)
+                       cmd->cb(card, cmd, -ECANCELED);
+               dev_info(CARD_TO_DEV(card),
+                       "Canceled active creg command\n");
+               kmem_cache_free(creg_cmd_pool, cmd);
+       }
+       spin_unlock_bh(&card->creg_ctrl.lock);
+
+       cancel_work_sync(&card->creg_ctrl.done_work);
+}
+
+
+int rsxx_creg_init(void)
+{
+       creg_cmd_pool = KMEM_CACHE(creg_cmd, SLAB_HWCACHE_ALIGN);
+       if (!creg_cmd_pool)
+               return -ENOMEM;
+
+       return 0;
+}
+
+void rsxx_creg_cleanup(void)
+{
+       kmem_cache_destroy(creg_cmd_pool);
+}