--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * HMS Anybus-S Host Driver
+ *
+ * Copyright (C) 2018 Arcx Inc
+ */
+
+/*
+ * Architecture Overview
+ * =====================
+ * This driver (running on the CPU/SoC) and the Anybus-S card communicate
+ * by reading and writing data to/from the Anybus-S Dual-Port RAM (dpram).
+ * This is memory connected to both the SoC and Anybus-S card, which both sides
+ * can access freely and concurrently.
+ *
+ * Synchronization happens by means of two registers located in the dpram:
+ * IND_AB: written exclusively by the Anybus card; and
+ * IND_AP: written exclusively by this driver.
+ *
+ * Communication happens using one of the following mechanisms:
+ * 1. reserve, read/write, release dpram memory areas:
+ *     using an IND_AB/IND_AP protocol, the driver is able to reserve certain
+ *     memory areas. no dpram memory can be read or written except if reserved.
+ *     (with a few limited exceptions)
+ * 2. send and receive data structures via a shared mailbox:
+ *     using an IND_AB/IND_AP protocol, the driver and Anybus card are able to
+ *     exchange commands and responses using a shared mailbox.
+ * 3. receive software interrupts:
+ *     using an IND_AB/IND_AP protocol, the Anybus card is able to notify the
+ *     driver of certain events such as: bus online/offline, data available.
+ *     note that software interrupt event bits are located in a memory area
+ *     which must be reserved before it can be accessed.
+ *
+ * The manual[1] is silent on whether these mechanisms can happen concurrently,
+ * or how they should be synchronized. However, section 13 (Driver Example)
+ * provides the following suggestion for developing a driver:
+ * a) an interrupt handler which updates global variables;
+ * b) a continuously-running task handling area requests (1 above)
+ * c) a continuously-running task handling mailbox requests (2 above)
+ * The example conspicuously leaves out software interrupts (3 above), which
+ * is the thorniest issue to get right (see below).
+ *
+ * The naive, straightforward way to implement this would be:
+ * - create an isr which updates shared variables;
+ * - create a work_struct which handles software interrupts on a queue;
+ * - create a function which does reserve/update/unlock in a loop;
+ * - create a function which does mailbox send/receive in a loop;
+ * - call the above functions from the driver's read/write/ioctl;
+ * - synchronize using mutexes/spinlocks:
+ *     + only one area request at a time
+ *     + only one mailbox request at a time
+ *     + protect AB_IND, AB_IND against data hazards (e.g. read-after-write)
+ *
+ * Unfortunately, the presence of the software interrupt causes subtle yet
+ * considerable synchronization issues; especially problematic is the
+ * requirement to reserve/release the area which contains the status bits.
+ *
+ * The driver architecture presented here sidesteps these synchronization issues
+ * by accessing the dpram from a single kernel thread only. User-space throws
+ * "tasks" (i.e. 1, 2 above) into a task queue, waits for their completion,
+ * and the kernel thread runs them to completion.
+ *
+ * Each task has a task_function, which is called/run by the queue thread.
+ * That function communicates with the Anybus card, and returns either
+ * 0 (OK), a negative error code (error), or -EINPROGRESS (waiting).
+ * On OK or error, the queue thread completes and dequeues the task,
+ * which also releases the user space thread which may still be waiting for it.
+ * On -EINPROGRESS (waiting), the queue thread will leave the task on the queue,
+ * and revisit (call again) whenever an interrupt event comes in.
+ *
+ * Each task has a state machine, which is run by calling its task_function.
+ * It ensures that the task will go through its various stages over time,
+ * returning -EINPROGRESS if it wants to wait for an event to happen.
+ *
+ * Note that according to the manual's driver example, the following operations
+ * may run independent of each other:
+ * - area reserve/read/write/release   (point 1 above)
+ * - mailbox operations                        (point 2 above)
+ * - switching power on/off
+ *
+ * To allow them to run independently, each operation class gets its own queue.
+ *
+ * Userspace processes A, B, C, D post tasks to the appropriate queue,
+ * and wait for task completion:
+ *
+ *     process A       B       C       D
+ *             |       |       |       |
+ *             v       v       v       v
+ *     |<----- ========================================
+ *     |               |          |            |
+ *     |               v          v            v-------<-------+
+ *     |       +--------------------------------------+        |
+ *     |       | power q     | mbox q    | area q     |        |
+ *     |       |------------|------------|------------|        |
+ *     |       | task       | task       | task       |        |
+ *     |       | task       | task       | task       |        |
+ *     |       | task wait  | task wait  | task wait  |        |
+ *     |       +--------------------------------------+        |
+ *     |               ^          ^            ^               |
+ *     |               |          |            |               ^
+ *     |       +--------------------------------------+        |
+ *     |       |            queue thread              |        |
+ *     |       |--------------------------------------|        |
+ *     |       | single-threaded:                     |        |
+ *     |       | loop:                                |        |
+ *     v       |   for each queue:                    |        |
+ *     |       |     run task state machine           |        |
+ *     |       |     if task waiting:                 |        |
+ *     |       |       leave on queue                 |        |
+ *     |       |     if task done:                    |        |
+ *     |       |       complete task, remove from q   |        |
+ *     |       |   if software irq event bits set:    |        |
+ *     |       |     notify userspace                 |        |
+ *     |       |     post clear event bits task------>|>-------+
+ *     |       |   wait for IND_AB changed event OR   |
+ *     |       |            task added event     OR   |
+ *     |       |            timeout                   |
+ *     |       | end loop                             |
+ *     |       +--------------------------------------+
+ *     |       +               wake up                +
+ *     |       +--------------------------------------+
+ *     |               ^                       ^
+ *     |               |                       |
+ *     +-------->-------                       |
+ *                                             |
+ *             +--------------------------------------+
+ *             |       interrupt service routine      |
+ *             |--------------------------------------|
+ *             | wake up queue thread on IND_AB change|
+ *             +--------------------------------------+
+ *
+ * Note that the Anybus interrupt is dual-purpose:
+ * - after a reset, triggered when the card becomes ready;
+ * - during normal operation, triggered when AB_IND changes.
+ * This is why the interrupt service routine doesn't just wake up the
+ * queue thread, but also completes the card_boot completion.
+ *
+ * [1] https://www.anybus.com/docs/librariesprovider7/default-document-library/
+ *     manuals-design-guides/hms-hmsi-27-275.pdf
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/atomic.h>
+#include <linux/kthread.h>
+#include <linux/kfifo.h>
+#include <linux/spinlock.h>
+#include <linux/uaccess.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/random.h>
+#include <linux/kref.h>
+#include <linux/of_address.h>
+
+/* move to <linux/anybuss-*.h> when taking this out of staging */
+#include "anybuss-client.h"
+#include "anybuss-controller.h"
+
+#define DPRAM_SIZE             0x800
+#define MAX_MBOX_MSG_SZ                0x0FF
+#define TIMEOUT                        (HZ * 2)
+#define MAX_DATA_AREA_SZ       0x200
+#define MAX_FBCTRL_AREA_SZ     0x1BE
+
+#define REG_BOOTLOADER_V       0x7C0
+#define REG_API_V              0x7C2
+#define REG_FIELDBUS_V         0x7C4
+#define REG_SERIAL_NO          0x7C6
+#define REG_FIELDBUS_TYPE      0x7CC
+#define REG_MODULE_SW_V                0x7CE
+#define REG_IND_AB             0x7FF
+#define REG_IND_AP             0x7FE
+#define REG_EVENT_CAUSE                0x7ED
+#define MBOX_IN_AREA           0x400
+#define MBOX_OUT_AREA          0x520
+#define DATA_IN_AREA           0x000
+#define DATA_OUT_AREA          0x200
+#define FBCTRL_AREA            0x640
+
+#define EVENT_CAUSE_DC          0x01
+#define EVENT_CAUSE_FBOF        0x02
+#define EVENT_CAUSE_FBON        0x04
+
+#define IND_AB_UPDATED         0x08
+#define IND_AX_MIN             0x80
+#define IND_AX_MOUT            0x40
+#define IND_AX_IN              0x04
+#define IND_AX_OUT             0x02
+#define IND_AX_FBCTRL          0x01
+#define IND_AP_LOCK            0x08
+#define IND_AP_ACTION          0x10
+#define IND_AX_EVNT            0x20
+#define IND_AP_ABITS           (IND_AX_IN | IND_AX_OUT | \
+                                       IND_AX_FBCTRL | \
+                                       IND_AP_ACTION | IND_AP_LOCK)
+
+#define INFO_TYPE_FB           0x0002
+#define INFO_TYPE_APP          0x0001
+#define INFO_COMMAND           0x4000
+
+#define OP_MODE_FBFC           0x0002
+#define OP_MODE_FBS            0x0004
+#define OP_MODE_CD             0x0200
+
+#define CMD_START_INIT         0x0001
+#define CMD_ANYBUS_INIT                0x0002
+#define CMD_END_INIT           0x0003
+
+/*
+ * ---------------------------------------------------------------
+ * Anybus mailbox messages - definitions
+ * ---------------------------------------------------------------
+ * note that we're depending on the layout of these structures being
+ * exactly as advertised.
+ */
+
+struct anybus_mbox_hdr {
+       __be16 id;
+       __be16 info;
+       __be16 cmd_num;
+       __be16 data_size;
+       __be16 frame_count;
+       __be16 frame_num;
+       __be16 offset_high;
+       __be16 offset_low;
+       __be16 extended[8];
+};
+
+struct msg_anybus_init {
+       __be16 input_io_len;
+       __be16 input_dpram_len;
+       __be16 input_total_len;
+       __be16 output_io_len;
+       __be16 output_dpram_len;
+       __be16 output_total_len;
+       __be16 op_mode;
+       __be16 notif_config;
+       __be16 wd_val;
+};
+
+/* ------------- ref counted tasks ------------- */
+
+struct ab_task;
+typedef int (*ab_task_fn_t)(struct anybuss_host *cd,
+                                       struct ab_task *t);
+typedef void (*ab_done_fn_t)(struct anybuss_host *cd);
+
+struct area_priv {
+       bool is_write;
+       u16 flags;
+       u16 addr;
+       size_t count;
+       u8 buf[MAX_DATA_AREA_SZ];
+};
+
+struct mbox_priv {
+       struct anybus_mbox_hdr hdr;
+       size_t msg_out_sz;
+       size_t msg_in_sz;
+       u8 msg[MAX_MBOX_MSG_SZ];
+};
+
+struct ab_task {
+       struct kmem_cache       *cache;
+       struct kref             refcount;
+       ab_task_fn_t            task_fn;
+       ab_done_fn_t            done_fn;
+       int                     result;
+       struct completion       done;
+       unsigned long           start_jiffies;
+       union {
+               struct area_priv area_pd;
+               struct mbox_priv mbox_pd;
+       };
+};
+
+static struct ab_task *ab_task_create_get(struct kmem_cache *cache,
+                                         ab_task_fn_t task_fn)
+{
+       struct ab_task *t;
+
+       t = kmem_cache_alloc(cache, GFP_KERNEL);
+       if (!t)
+               return NULL;
+       t->cache = cache;
+       kref_init(&t->refcount);
+       t->task_fn = task_fn;
+       t->done_fn = NULL;
+       t->result = 0;
+       init_completion(&t->done);
+       return t;
+}
+
+static void __ab_task_destroy(struct kref *refcount)
+{
+       struct ab_task *t = container_of(refcount, struct ab_task, refcount);
+       struct kmem_cache *cache = t->cache;
+
+       kmem_cache_free(cache, t);
+}
+
+static void ab_task_put(struct ab_task *t)
+{
+       kref_put(&t->refcount, __ab_task_destroy);
+}
+
+static struct ab_task *__ab_task_get(struct ab_task *t)
+{
+       kref_get(&t->refcount);
+       return t;
+}
+
+static void __ab_task_finish(struct ab_task *t, struct anybuss_host *cd)
+{
+       if (t->done_fn)
+               t->done_fn(cd);
+       complete(&t->done);
+}
+
+static void
+ab_task_dequeue_finish_put(struct kfifo *q, struct anybuss_host *cd)
+{
+       int ret;
+       struct ab_task *t;
+
+       ret = kfifo_out(q, &t, sizeof(t));
+       WARN_ON(!ret);
+       __ab_task_finish(t, cd);
+       ab_task_put(t);
+}
+
+static int
+ab_task_enqueue(struct ab_task *t, struct kfifo *q, spinlock_t *slock,
+               wait_queue_head_t *wq)
+{
+       int ret;
+
+       t->start_jiffies = jiffies;
+       __ab_task_get(t);
+       ret = kfifo_in_spinlocked(q, &t, sizeof(t), slock);
+       if (!ret) {
+               ab_task_put(t);
+               return -ENOMEM;
+       }
+       wake_up(wq);
+       return 0;
+}
+
+static int
+ab_task_enqueue_wait(struct ab_task *t, struct kfifo *q, spinlock_t *slock,
+                    wait_queue_head_t *wq)
+{
+       int ret;
+
+       ret = ab_task_enqueue(t, q, slock, wq);
+       if (ret)
+               return ret;
+       ret = wait_for_completion_interruptible(&t->done);
+       if (ret)
+               return ret;
+       return t->result;
+}
+
+/* ------------------------ anybus hardware ------------------------ */
+
+struct anybuss_host {
+       struct device *dev;
+       struct anybuss_client *client;
+       void (*reset)(struct device *dev, bool assert);
+       struct regmap *regmap;
+       int irq;
+       int host_idx;
+       struct task_struct *qthread;
+       wait_queue_head_t wq;
+       struct completion card_boot;
+       atomic_t ind_ab;
+       spinlock_t qlock; /* protects IN side of powerq, mboxq, areaq */
+       struct kmem_cache *qcache;
+       struct kfifo qs[3];
+       struct kfifo *powerq;
+       struct kfifo *mboxq;
+       struct kfifo *areaq;
+       bool power_on;
+       bool softint_pending;
+};
+
+static void reset_assert(struct anybuss_host *cd)
+{
+       cd->reset(cd->dev, true);
+}
+
+static void reset_deassert(struct anybuss_host *cd)
+{
+       cd->reset(cd->dev, false);
+}
+
+static int test_dpram(struct regmap *regmap)
+{
+       int i;
+       unsigned int val;
+
+       for (i = 0; i < DPRAM_SIZE; i++)
+               regmap_write(regmap, i, (u8)i);
+       for (i = 0; i < DPRAM_SIZE; i++) {
+               regmap_read(regmap, i, &val);
+               if ((u8)val != (u8)i)
+                       return -EIO;
+       }
+       return 0;
+}
+
+static int read_ind_ab(struct regmap *regmap)
+{
+       unsigned long timeout = jiffies + HZ / 2;
+       unsigned int a, b, i = 0;
+
+       while (time_before_eq(jiffies, timeout)) {
+               regmap_read(regmap, REG_IND_AB, &a);
+               regmap_read(regmap, REG_IND_AB, &b);
+               if (likely(a == b))
+                       return (int)a;
+               if (i < 10) {
+                       cpu_relax();
+                       i++;
+               } else {
+                       usleep_range(500, 1000);
+               }
+       }
+       WARN(1, "IND_AB register not stable");
+       return -ETIMEDOUT;
+}
+
+static int write_ind_ap(struct regmap *regmap, unsigned int ind_ap)
+{
+       unsigned long timeout = jiffies + HZ / 2;
+       unsigned int v, i = 0;
+
+       while (time_before_eq(jiffies, timeout)) {
+               regmap_write(regmap, REG_IND_AP, ind_ap);
+               regmap_read(regmap, REG_IND_AP, &v);
+               if (likely(ind_ap == v))
+                       return 0;
+               if (i < 10) {
+                       cpu_relax();
+                       i++;
+               } else {
+                       usleep_range(500, 1000);
+               }
+       }
+       WARN(1, "IND_AP register not stable");
+       return -ETIMEDOUT;
+}
+
+static irqreturn_t irq_handler(int irq, void *data)
+{
+       struct anybuss_host *cd = data;
+       int ind_ab;
+
+       /*
+        * irq handler needs exclusive access to the IND_AB register,
+        * because the act of reading the register acks the interrupt.
+        *
+        * store the register value in cd->ind_ab (an atomic_t), so that the
+        * queue thread is able to read it without causing an interrupt ack
+        * side-effect (and without spuriously acking an interrupt).
+        */
+       ind_ab = read_ind_ab(cd->regmap);
+       if (ind_ab < 0)
+               return IRQ_NONE;
+       atomic_set(&cd->ind_ab, ind_ab);
+       complete(&cd->card_boot);
+       wake_up(&cd->wq);
+       return IRQ_HANDLED;
+}
+
+/* ------------------------ power on/off tasks --------------------- */
+
+static int task_fn_power_off(struct anybuss_host *cd,
+                            struct ab_task *t)
+{
+       struct anybuss_client *client = cd->client;
+
+       if (!cd->power_on)
+               return 0;
+       disable_irq(cd->irq);
+       reset_assert(cd);
+       atomic_set(&cd->ind_ab, IND_AB_UPDATED);
+       if (client->on_online_changed)
+               client->on_online_changed(client, false);
+       cd->power_on = false;
+       return 0;
+}
+
+static int task_fn_power_on_2(struct anybuss_host *cd,
+                             struct ab_task *t)
+{
+       if (completion_done(&cd->card_boot)) {
+               cd->power_on = true;
+               return 0;
+       }
+       if (time_after(jiffies, t->start_jiffies + TIMEOUT)) {
+               disable_irq(cd->irq);
+               reset_assert(cd);
+               dev_err(cd->dev, "power on timed out");
+               return -ETIMEDOUT;
+       }
+       return -EINPROGRESS;
+}
+
+static int task_fn_power_on(struct anybuss_host *cd,
+                           struct ab_task *t)
+{
+       unsigned int dummy;
+
+       if (cd->power_on)
+               return 0;
+       /*
+        * anybus docs: prevent false 'init done' interrupt by
+        * doing a dummy read of IND_AB register while in reset.
+        */
+       regmap_read(cd->regmap, REG_IND_AB, &dummy);
+       reinit_completion(&cd->card_boot);
+       enable_irq(cd->irq);
+       reset_deassert(cd);
+       t->task_fn = task_fn_power_on_2;
+       return -EINPROGRESS;
+}
+
+int anybuss_set_power(struct anybuss_client *client, bool power_on)
+{
+       struct anybuss_host *cd = client->host;
+       struct ab_task *t;
+       int err;
+
+       t = ab_task_create_get(cd->qcache, power_on ?
+                               task_fn_power_on : task_fn_power_off);
+       if (!t)
+               return -ENOMEM;
+       err = ab_task_enqueue_wait(t, cd->powerq, &cd->qlock, &cd->wq);
+       ab_task_put(t);
+       return err;
+}
+EXPORT_SYMBOL_GPL(anybuss_set_power);
+
+/* ---------------------------- area tasks ------------------------ */
+
+static int task_fn_area_3(struct anybuss_host *cd, struct ab_task *t)
+{
+       struct area_priv *pd = &t->area_pd;
+
+       if (!cd->power_on)
+               return -EIO;
+       if (atomic_read(&cd->ind_ab) & pd->flags) {
+               /* area not released yet */
+               if (time_after(jiffies, t->start_jiffies + TIMEOUT))
+                       return -ETIMEDOUT;
+               return -EINPROGRESS;
+       }
+       return 0;
+}
+
+static int task_fn_area_2(struct anybuss_host *cd, struct ab_task *t)
+{
+       struct area_priv *pd = &t->area_pd;
+       unsigned int ind_ap;
+       int ret;
+
+       if (!cd->power_on)
+               return -EIO;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       if (!(atomic_read(&cd->ind_ab) & pd->flags)) {
+               /* we don't own the area yet */
+               if (time_after(jiffies, t->start_jiffies + TIMEOUT)) {
+                       dev_warn(cd->dev, "timeout waiting for area");
+                       dump_stack();
+                       return -ETIMEDOUT;
+               }
+               return -EINPROGRESS;
+       }
+       /* we own the area, do what we're here to do */
+       if (pd->is_write)
+               regmap_bulk_write(cd->regmap, pd->addr, pd->buf,
+                                 pd->count);
+       else
+               regmap_bulk_read(cd->regmap, pd->addr, pd->buf,
+                                pd->count);
+       /* ask to release the area, must use unlocked release */
+       ind_ap &= ~IND_AP_ABITS;
+       ind_ap |= pd->flags;
+       ret = write_ind_ap(cd->regmap, ind_ap);
+       if (ret)
+               return ret;
+       t->task_fn = task_fn_area_3;
+       return -EINPROGRESS;
+}
+
+static int task_fn_area(struct anybuss_host *cd, struct ab_task *t)
+{
+       struct area_priv *pd = &t->area_pd;
+       unsigned int ind_ap;
+       int ret;
+
+       if (!cd->power_on)
+               return -EIO;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       /* ask to take the area */
+       ind_ap &= ~IND_AP_ABITS;
+       ind_ap |= pd->flags | IND_AP_ACTION | IND_AP_LOCK;
+       ret = write_ind_ap(cd->regmap, ind_ap);
+       if (ret)
+               return ret;
+       t->task_fn = task_fn_area_2;
+       return -EINPROGRESS;
+}
+
+static struct ab_task *
+create_area_reader(struct kmem_cache *qcache, u16 flags, u16 addr,
+                  size_t count)
+{
+       struct ab_task *t;
+       struct area_priv *ap;
+
+       t = ab_task_create_get(qcache, task_fn_area);
+       if (!t)
+               return NULL;
+       ap = &t->area_pd;
+       ap->flags = flags;
+       ap->addr = addr;
+       ap->is_write = false;
+       ap->count = count;
+       return t;
+}
+
+static struct ab_task *
+create_area_writer(struct kmem_cache *qcache, u16 flags, u16 addr,
+                  const void *buf, size_t count)
+{
+       struct ab_task *t;
+       struct area_priv *ap;
+
+       t = ab_task_create_get(qcache, task_fn_area);
+       if (!t)
+               return NULL;
+       ap = &t->area_pd;
+       ap->flags = flags;
+       ap->addr = addr;
+       ap->is_write = true;
+       ap->count = count;
+       memcpy(ap->buf, buf, count);
+       return t;
+}
+
+static struct ab_task *
+create_area_user_writer(struct kmem_cache *qcache, u16 flags, u16 addr,
+                       const void __user *buf, size_t count)
+{
+       struct ab_task *t;
+       struct area_priv *ap;
+
+       t = ab_task_create_get(qcache, task_fn_area);
+       if (!t)
+               return ERR_PTR(-ENOMEM);
+       ap = &t->area_pd;
+       ap->flags = flags;
+       ap->addr = addr;
+       ap->is_write = true;
+       ap->count = count;
+       if (copy_from_user(ap->buf, buf, count)) {
+               ab_task_put(t);
+               return ERR_PTR(-EFAULT);
+       }
+       return t;
+}
+
+static bool area_range_ok(u16 addr, size_t count, u16 area_start,
+                         size_t area_sz)
+{
+       u16 area_end_ex = area_start + area_sz;
+       u16 addr_end_ex;
+
+       if (addr < area_start)
+               return false;
+       if (addr >= area_end_ex)
+               return false;
+       addr_end_ex = addr + count;
+       if (addr_end_ex > area_end_ex)
+               return false;
+       return true;
+}
+
+/* -------------------------- mailbox tasks ----------------------- */
+
+static int task_fn_mbox_2(struct anybuss_host *cd, struct ab_task *t)
+{
+       struct mbox_priv *pd = &t->mbox_pd;
+       unsigned int ind_ap;
+
+       if (!cd->power_on)
+               return -EIO;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       if (((atomic_read(&cd->ind_ab) ^ ind_ap) & IND_AX_MOUT) == 0) {
+               /* output message not here */
+               if (time_after(jiffies, t->start_jiffies + TIMEOUT))
+                       return -ETIMEDOUT;
+               return -EINPROGRESS;
+       }
+       /* grab the returned header and msg */
+       regmap_bulk_read(cd->regmap, MBOX_OUT_AREA, &pd->hdr,
+                        sizeof(pd->hdr));
+       regmap_bulk_read(cd->regmap, MBOX_OUT_AREA + sizeof(pd->hdr),
+                        pd->msg, pd->msg_in_sz);
+       /* tell anybus we've consumed the message */
+       ind_ap ^= IND_AX_MOUT;
+       return write_ind_ap(cd->regmap, ind_ap);
+}
+
+static int task_fn_mbox(struct anybuss_host *cd, struct ab_task *t)
+{
+       struct mbox_priv *pd = &t->mbox_pd;
+       unsigned int ind_ap;
+       int ret;
+
+       if (!cd->power_on)
+               return -EIO;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       if ((atomic_read(&cd->ind_ab) ^ ind_ap) & IND_AX_MIN) {
+               /* mbox input area busy */
+               if (time_after(jiffies, t->start_jiffies + TIMEOUT))
+                       return -ETIMEDOUT;
+               return -EINPROGRESS;
+       }
+       /* write the header and msg to input area */
+       regmap_bulk_write(cd->regmap, MBOX_IN_AREA, &pd->hdr,
+                         sizeof(pd->hdr));
+       regmap_bulk_write(cd->regmap, MBOX_IN_AREA + sizeof(pd->hdr),
+                         pd->msg, pd->msg_out_sz);
+       /* tell anybus we gave it a message */
+       ind_ap ^= IND_AX_MIN;
+       ret = write_ind_ap(cd->regmap, ind_ap);
+       if (ret)
+               return ret;
+       t->start_jiffies = jiffies;
+       t->task_fn = task_fn_mbox_2;
+       return -EINPROGRESS;
+}
+
+static void log_invalid_other(struct device *dev,
+                             struct anybus_mbox_hdr *hdr)
+{
+       size_t ext_offs = ARRAY_SIZE(hdr->extended) - 1;
+       u16 code = be16_to_cpu(hdr->extended[ext_offs]);
+
+       dev_err(dev, "   Invalid other: [0x%02X]", code);
+}
+
+static const char * const EMSGS[] = {
+       "Invalid Message ID",
+       "Invalid Message Type",
+       "Invalid Command",
+       "Invalid Data Size",
+       "Message Header Malformed (offset 008h)",
+       "Message Header Malformed (offset 00Ah)",
+       "Message Header Malformed (offset 00Ch - 00Dh)",
+       "Invalid Address",
+       "Invalid Response",
+       "Flash Config Error",
+};
+
+static int mbox_cmd_err(struct device *dev, struct mbox_priv *mpriv)
+{
+       int i;
+       u8 ecode;
+       struct anybus_mbox_hdr *hdr = &mpriv->hdr;
+       u16 info = be16_to_cpu(hdr->info);
+       u8 *phdr = (u8 *)hdr;
+       u8 *pmsg = mpriv->msg;
+
+       if (!(info & 0x8000))
+               return 0;
+       ecode = (info >> 8) & 0x0F;
+       dev_err(dev, "mailbox command failed:");
+       if (ecode == 0x0F)
+               log_invalid_other(dev, hdr);
+       else if (ecode < ARRAY_SIZE(EMSGS))
+               dev_err(dev, "   Error code: %s (0x%02X)",
+                       EMSGS[ecode], ecode);
+       else
+               dev_err(dev, "   Error code: 0x%02X\n", ecode);
+       dev_err(dev, "Failed command:");
+       dev_err(dev, "Message Header:");
+       for (i = 0; i < sizeof(mpriv->hdr); i += 2)
+               dev_err(dev, "%02X%02X", phdr[i], phdr[i + 1]);
+       dev_err(dev, "Message Data:");
+       for (i = 0; i < mpriv->msg_in_sz; i += 2)
+               dev_err(dev, "%02X%02X", pmsg[i], pmsg[i + 1]);
+       dev_err(dev, "Stack dump:");
+       dump_stack();
+       return -EIO;
+}
+
+static int _anybus_mbox_cmd(struct anybuss_host *cd,
+                           u16 cmd_num, bool is_fb_cmd,
+                               const void *msg_out, size_t msg_out_sz,
+                               void *msg_in, size_t msg_in_sz,
+                               const void *ext, size_t ext_sz)
+{
+       struct ab_task *t;
+       struct mbox_priv *pd;
+       struct anybus_mbox_hdr *h;
+       size_t msg_sz = max(msg_in_sz, msg_out_sz);
+       u16 info;
+       int err;
+
+       if (msg_sz > MAX_MBOX_MSG_SZ)
+               return -EINVAL;
+       if (ext && ext_sz > sizeof(h->extended))
+               return -EINVAL;
+       t = ab_task_create_get(cd->qcache, task_fn_mbox);
+       if (!t)
+               return -ENOMEM;
+       pd = &t->mbox_pd;
+       h = &pd->hdr;
+       info = is_fb_cmd ? INFO_TYPE_FB : INFO_TYPE_APP;
+       /*
+        * prevent uninitialized memory in the header from being sent
+        * across the anybus
+        */
+       memset(h, 0, sizeof(*h));
+       h->info = cpu_to_be16(info | INFO_COMMAND);
+       h->cmd_num = cpu_to_be16(cmd_num);
+       h->data_size = cpu_to_be16(msg_out_sz);
+       h->frame_count = cpu_to_be16(1);
+       h->frame_num = cpu_to_be16(1);
+       h->offset_high = cpu_to_be16(0);
+       h->offset_low = cpu_to_be16(0);
+       if (ext)
+               memcpy(h->extended, ext, ext_sz);
+       memcpy(pd->msg, msg_out, msg_out_sz);
+       pd->msg_out_sz = msg_out_sz;
+       pd->msg_in_sz = msg_in_sz;
+       err = ab_task_enqueue_wait(t, cd->powerq, &cd->qlock, &cd->wq);
+       if (err)
+               goto out;
+       /*
+        * mailbox mechanism worked ok, but maybe the mbox response
+        * contains an error ?
+        */
+       err = mbox_cmd_err(cd->dev, pd);
+       if (err)
+               goto out;
+       memcpy(msg_in, pd->msg, msg_in_sz);
+out:
+       ab_task_put(t);
+       return err;
+}
+
+/* ------------------------ anybus queues ------------------------ */
+
+static void process_q(struct anybuss_host *cd, struct kfifo *q)
+{
+       struct ab_task *t;
+       int ret;
+
+       ret = kfifo_out_peek(q, &t, sizeof(t));
+       if (!ret)
+               return;
+       t->result = t->task_fn(cd, t);
+       if (t->result != -EINPROGRESS)
+               ab_task_dequeue_finish_put(q, cd);
+}
+
+static bool qs_have_work(struct kfifo *qs, size_t num)
+{
+       size_t i;
+       struct ab_task *t;
+       int ret;
+
+       for (i = 0; i < num; i++, qs++) {
+               ret = kfifo_out_peek(qs, &t, sizeof(t));
+               if (ret && (t->result != -EINPROGRESS))
+                       return true;
+       }
+       return false;
+}
+
+static void process_qs(struct anybuss_host *cd)
+{
+       size_t i;
+       struct kfifo *qs = cd->qs;
+       size_t nqs = ARRAY_SIZE(cd->qs);
+
+       for (i = 0; i < nqs; i++, qs++)
+               process_q(cd, qs);
+}
+
+static void softint_ack(struct anybuss_host *cd)
+{
+       unsigned int ind_ap;
+
+       cd->softint_pending = false;
+       if (!cd->power_on)
+               return;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       ind_ap &= ~IND_AX_EVNT;
+       ind_ap |= atomic_read(&cd->ind_ab) & IND_AX_EVNT;
+       write_ind_ap(cd->regmap, ind_ap);
+}
+
+static void process_softint(struct anybuss_host *cd)
+{
+       struct anybuss_client *client = cd->client;
+       static const u8 zero;
+       int ret;
+       unsigned int ind_ap, ev;
+       struct ab_task *t;
+
+       if (!cd->power_on)
+               return;
+       if (cd->softint_pending)
+               return;
+       regmap_read(cd->regmap, REG_IND_AP, &ind_ap);
+       if (!((atomic_read(&cd->ind_ab) ^ ind_ap) & IND_AX_EVNT))
+               return;
+       /* process software interrupt */
+       regmap_read(cd->regmap, REG_EVENT_CAUSE, &ev);
+       if (ev & EVENT_CAUSE_FBON) {
+               if (client->on_online_changed)
+                       client->on_online_changed(client, true);
+               dev_dbg(cd->dev, "Fieldbus ON");
+       }
+       if (ev & EVENT_CAUSE_FBOF) {
+               if (client->on_online_changed)
+                       client->on_online_changed(client, false);
+               dev_dbg(cd->dev, "Fieldbus OFF");
+       }
+       if (ev & EVENT_CAUSE_DC) {
+               if (client->on_area_updated)
+                       client->on_area_updated(client);
+               dev_dbg(cd->dev, "Fieldbus data changed");
+       }
+       /*
+        * reset the event cause bits.
+        * this must be done while owning the fbctrl area, so we'll
+        * enqueue a task to do that.
+        */
+       t = create_area_writer(cd->qcache, IND_AX_FBCTRL,
+                              REG_EVENT_CAUSE, &zero, sizeof(zero));
+       if (!t) {
+               ret = -ENOMEM;
+               goto out;
+       }
+       t->done_fn = softint_ack;
+       ret = ab_task_enqueue(t, cd->powerq, &cd->qlock, &cd->wq);
+       ab_task_put(t);
+       cd->softint_pending = true;
+out:
+       WARN_ON(ret);
+       if (ret)
+               softint_ack(cd);
+}
+
+static int qthread_fn(void *data)
+{
+       struct anybuss_host *cd = data;
+       struct kfifo *qs = cd->qs;
+       size_t nqs = ARRAY_SIZE(cd->qs);
+       unsigned int ind_ab;
+
+       /*
+        * this kernel thread has exclusive access to the anybus's memory.
+        * only exception: the IND_AB register, which is accessed exclusively
+        * by the interrupt service routine (ISR). This thread must not touch
+        * the IND_AB register, but it does require access to its value.
+        *
+        * the interrupt service routine stores the register's value in
+        * cd->ind_ab (an atomic_t), where we may safely access it, with the
+        * understanding that it can be modified by the ISR at any time.
+        */
+
+       while (!kthread_should_stop()) {
+               /*
+                * make a local copy of IND_AB, so we can go around the loop
+                * again in case it changed while processing queues and softint.
+                */
+               ind_ab = atomic_read(&cd->ind_ab);
+               process_qs(cd);
+               process_softint(cd);
+               wait_event_timeout(cd->wq,
+                                  (atomic_read(&cd->ind_ab) != ind_ab) ||
+                               qs_have_work(qs, nqs) ||
+                               kthread_should_stop(),
+                       HZ);
+               /*
+                * time out so even 'stuck' tasks will run eventually,
+                * and can time out.
+                */
+       }
+
+       return 0;
+}
+
+/* ------------------------ anybus exports ------------------------ */
+
+int anybuss_start_init(struct anybuss_client *client,
+                      const struct anybuss_memcfg *cfg)
+{
+       int ret;
+       u16 op_mode;
+       struct anybuss_host *cd = client->host;
+       struct msg_anybus_init msg = {
+               .input_io_len = cpu_to_be16(cfg->input_io),
+               .input_dpram_len = cpu_to_be16(cfg->input_dpram),
+               .input_total_len = cpu_to_be16(cfg->input_total),
+               .output_io_len = cpu_to_be16(cfg->output_io),
+               .output_dpram_len = cpu_to_be16(cfg->output_dpram),
+               .output_total_len = cpu_to_be16(cfg->output_total),
+               .notif_config = cpu_to_be16(0x000F),
+               .wd_val = cpu_to_be16(0),
+       };
+
+       switch (cfg->offl_mode) {
+       case AB_OFFL_MODE_CLEAR:
+               op_mode = 0;
+               break;
+       case AB_OFFL_MODE_FREEZE:
+               op_mode = OP_MODE_FBFC;
+               break;
+       case AB_OFFL_MODE_SET:
+               op_mode = OP_MODE_FBS;
+               break;
+       default:
+               return -EINVAL;
+       }
+       msg.op_mode = cpu_to_be16(op_mode | OP_MODE_CD);
+       ret = _anybus_mbox_cmd(cd, CMD_START_INIT, false, NULL, 0,
+                              NULL, 0, NULL, 0);
+       if (ret)
+               return ret;
+       return _anybus_mbox_cmd(cd, CMD_ANYBUS_INIT, false,
+                       &msg, sizeof(msg), NULL, 0, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(anybuss_start_init);
+
+int anybuss_finish_init(struct anybuss_client *client)
+{
+       struct anybuss_host *cd = client->host;
+
+       return _anybus_mbox_cmd(cd, CMD_END_INIT, false, NULL, 0,
+                                       NULL, 0, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(anybuss_finish_init);
+
+int anybuss_read_fbctrl(struct anybuss_client *client, u16 addr,
+                       void *buf, size_t count)
+{
+       struct anybuss_host *cd = client->host;
+       struct ab_task *t;
+       int ret;
+
+       if (count == 0)
+               return 0;
+       if (!area_range_ok(addr, count, FBCTRL_AREA,
+                          MAX_FBCTRL_AREA_SZ))
+               return -EFAULT;
+       t = create_area_reader(cd->qcache, IND_AX_FBCTRL, addr, count);
+       if (!t)
+               return -ENOMEM;
+       ret = ab_task_enqueue_wait(t, cd->powerq, &cd->qlock, &cd->wq);
+       if (ret)
+               goto out;
+       memcpy(buf, t->area_pd.buf, count);
+out:
+       ab_task_put(t);
+       return ret;
+}
+EXPORT_SYMBOL_GPL(anybuss_read_fbctrl);
+
+int anybuss_write_input(struct anybuss_client *client,
+                       const char __user *buf, size_t size,
+                               loff_t *offset)
+{
+       ssize_t len = min_t(loff_t, MAX_DATA_AREA_SZ - *offset, size);
+       struct anybuss_host *cd = client->host;
+       struct ab_task *t;
+       int ret;
+
+       if (len <= 0)
+               return 0;
+       t = create_area_user_writer(cd->qcache, IND_AX_IN,
+                                   DATA_IN_AREA + *offset, buf, len);
+       if (IS_ERR(t))
+               return PTR_ERR(t);
+       ret = ab_task_enqueue_wait(t, cd->powerq, &cd->qlock, &cd->wq);
+       ab_task_put(t);
+       if (ret)
+               return ret;
+       /* success */
+       *offset += len;
+       return len;
+}
+EXPORT_SYMBOL_GPL(anybuss_write_input);
+
+int anybuss_read_output(struct anybuss_client *client,
+                       char __user *buf, size_t size,
+                               loff_t *offset)
+{
+       ssize_t len = min_t(loff_t, MAX_DATA_AREA_SZ - *offset, size);
+       struct anybuss_host *cd = client->host;
+       struct ab_task *t;
+       int ret;
+
+       if (len <= 0)
+               return 0;
+       t = create_area_reader(cd->qcache, IND_AX_OUT,
+                              DATA_OUT_AREA + *offset, len);
+       if (!t)
+               return -ENOMEM;
+       ret = ab_task_enqueue_wait(t, cd->powerq, &cd->qlock, &cd->wq);
+       if (ret)
+               goto out;
+       if (copy_to_user(buf, t->area_pd.buf, len))
+               ret = -EFAULT;
+out:
+       ab_task_put(t);
+       if (ret)
+               return ret;
+       /* success */
+       *offset += len;
+       return len;
+}
+EXPORT_SYMBOL_GPL(anybuss_read_output);
+
+int anybuss_send_msg(struct anybuss_client *client, u16 cmd_num,
+                    const void *buf, size_t count)
+{
+       struct anybuss_host *cd = client->host;
+
+       return _anybus_mbox_cmd(cd, cmd_num, true, buf, count, NULL, 0,
+                                       NULL, 0);
+}
+EXPORT_SYMBOL_GPL(anybuss_send_msg);
+
+int anybuss_send_ext(struct anybuss_client *client, u16 cmd_num,
+                    const void *buf, size_t count)
+{
+       struct anybuss_host *cd = client->host;
+
+       return _anybus_mbox_cmd(cd, cmd_num, true, NULL, 0, NULL, 0,
+                                       buf, count);
+}
+EXPORT_SYMBOL_GPL(anybuss_send_ext);
+
+int anybuss_recv_msg(struct anybuss_client *client, u16 cmd_num,
+                    void *buf, size_t count)
+{
+       struct anybuss_host *cd = client->host;
+
+       return _anybus_mbox_cmd(cd, cmd_num, true, NULL, 0, buf, count,
+                                       NULL, 0);
+}
+EXPORT_SYMBOL_GPL(anybuss_recv_msg);
+
+/* ------------------------ bus functions ------------------------ */
+
+static int anybus_bus_match(struct device *dev,
+                           struct device_driver *drv)
+{
+       struct anybuss_client_driver *adrv =
+               to_anybuss_client_driver(drv);
+       struct anybuss_client *adev =
+               to_anybuss_client(dev);
+
+       return adrv->fieldbus_type == adev->fieldbus_type;
+}
+
+static int anybus_bus_probe(struct device *dev)
+{
+       struct anybuss_client_driver *adrv =
+               to_anybuss_client_driver(dev->driver);
+       struct anybuss_client *adev =
+               to_anybuss_client(dev);
+
+       if (!adrv->probe)
+               return -ENODEV;
+       return adrv->probe(adev);
+}
+
+static int anybus_bus_remove(struct device *dev)
+{
+       struct anybuss_client_driver *adrv =
+               to_anybuss_client_driver(dev->driver);
+
+       if (adrv->remove)
+               return adrv->remove(to_anybuss_client(dev));
+       return 0;
+}
+
+static struct bus_type anybus_bus = {
+       .name           = "anybuss",
+       .match          = anybus_bus_match,
+       .probe          = anybus_bus_probe,
+       .remove         = anybus_bus_remove,
+};
+
+int anybuss_client_driver_register(struct anybuss_client_driver *drv)
+{
+       drv->driver.bus = &anybus_bus;
+       return driver_register(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(anybuss_client_driver_register);
+
+void anybuss_client_driver_unregister(struct anybuss_client_driver *drv)
+{
+       return driver_unregister(&drv->driver);
+}
+EXPORT_SYMBOL_GPL(anybuss_client_driver_unregister);
+
+static void client_device_release(struct device *dev)
+{
+       kfree(to_anybuss_client(dev));
+}
+
+static int taskq_alloc(struct device *dev, struct kfifo *q)
+{
+       void *buf;
+       size_t size = 64 * sizeof(struct ab_task *);
+
+       buf = devm_kzalloc(dev, size, GFP_KERNEL);
+       if (!buf)
+               return -EIO;
+       return kfifo_init(q, buf, size);
+}
+
+static int anybus_of_get_host_idx(struct device_node *np)
+{
+       const __be32 *host_idx;
+
+       host_idx = of_get_address(np, 0, NULL, NULL);
+       if (!host_idx)
+               return -ENOENT;
+       return __be32_to_cpu(*host_idx);
+}
+
+static struct device_node *
+anybus_of_find_child_device(struct device *dev, int host_idx)
+{
+       struct device_node *node;
+
+       if (!dev || !dev->of_node)
+               return NULL;
+       for_each_child_of_node(dev->of_node, node) {
+               if (anybus_of_get_host_idx(node) == host_idx)
+                       return node;
+       }
+       return NULL;
+}
+
+struct anybuss_host * __must_check
+anybuss_host_common_probe(struct device *dev,
+                         const struct anybuss_ops *ops)
+{
+       int ret, i;
+       u8 val[4];
+       u16 fieldbus_type;
+       struct anybuss_host *cd;
+
+       cd = devm_kzalloc(dev, sizeof(*cd), GFP_KERNEL);
+       if (!cd)
+               return ERR_PTR(-ENOMEM);
+       cd->dev = dev;
+       cd->host_idx = ops->host_idx;
+       init_completion(&cd->card_boot);
+       init_waitqueue_head(&cd->wq);
+       for (i = 0; i < ARRAY_SIZE(cd->qs); i++) {
+               ret = taskq_alloc(dev, &cd->qs[i]);
+               if (ret)
+                       return ERR_PTR(ret);
+       }
+       if (WARN_ON(ARRAY_SIZE(cd->qs) < 3))
+               return ERR_PTR(-EINVAL);
+       cd->powerq = &cd->qs[0];
+       cd->mboxq = &cd->qs[1];
+       cd->areaq = &cd->qs[2];
+       cd->reset = ops->reset;
+       if (!cd->reset)
+               return ERR_PTR(-EINVAL);
+       cd->regmap = ops->regmap;
+       if (!cd->regmap)
+               return ERR_PTR(-EINVAL);
+       spin_lock_init(&cd->qlock);
+       cd->qcache = kmem_cache_create(dev_name(dev),
+                                      sizeof(struct ab_task), 0, 0, NULL);
+       if (!cd->qcache)
+               return ERR_PTR(-ENOMEM);
+       cd->irq = ops->irq;
+       if (cd->irq <= 0) {
+               ret = -EINVAL;
+               goto err_qcache;
+       }
+       /*
+        * use a dpram test to check if a card is present, this is only
+        * possible while in reset.
+        */
+       reset_assert(cd);
+       if (test_dpram(cd->regmap)) {
+               dev_err(dev, "no Anybus-S card in slot");
+               ret = -ENODEV;
+               goto err_qcache;
+       }
+       ret = devm_request_threaded_irq(dev, cd->irq, NULL, irq_handler,
+                                       IRQF_ONESHOT, dev_name(dev), cd);
+       if (ret) {
+               dev_err(dev, "could not request irq");
+               goto err_qcache;
+       }
+       /*
+        * startup sequence:
+        *   perform dummy IND_AB read to prevent false 'init done' irq
+        *     (already done by test_dpram() above)
+        *   release reset
+        *   wait for first interrupt
+        *   interrupt came in: ready to go !
+        */
+       reset_deassert(cd);
+       ret = wait_for_completion_timeout(&cd->card_boot, TIMEOUT);
+       if (ret == 0)
+               ret = -ETIMEDOUT;
+       if (ret < 0)
+               goto err_reset;
+       /*
+        * according to the anybus docs, we're allowed to read these
+        * without handshaking / reserving the area
+        */
+       dev_info(dev, "Anybus-S card detected");
+       regmap_bulk_read(cd->regmap, REG_BOOTLOADER_V, val, 2);
+       dev_info(dev, "Bootloader version: %02X%02X",
+                val[0], val[1]);
+       regmap_bulk_read(cd->regmap, REG_API_V, val, 2);
+       dev_info(dev, "API version: %02X%02X", val[0], val[1]);
+       regmap_bulk_read(cd->regmap, REG_FIELDBUS_V, val, 2);
+       dev_info(dev, "Fieldbus version: %02X%02X", val[0], val[1]);
+       regmap_bulk_read(cd->regmap, REG_SERIAL_NO, val, 4);
+       dev_info(dev, "Serial number: %02X%02X%02X%02X",
+                val[0], val[1], val[2], val[3]);
+       add_device_randomness(&val, 4);
+       regmap_bulk_read(cd->regmap, REG_FIELDBUS_TYPE, &fieldbus_type,
+                        sizeof(fieldbus_type));
+       fieldbus_type = be16_to_cpu(fieldbus_type);
+       dev_info(dev, "Fieldbus type: %04X", fieldbus_type);
+       regmap_bulk_read(cd->regmap, REG_MODULE_SW_V, val, 2);
+       dev_info(dev, "Module SW version: %02X%02X",
+                val[0], val[1]);
+       /* put card back reset until a client driver releases it */
+       disable_irq(cd->irq);
+       reset_assert(cd);
+       atomic_set(&cd->ind_ab, IND_AB_UPDATED);
+       /* fire up the queue thread */
+       cd->qthread = kthread_run(qthread_fn, cd, dev_name(dev));
+       if (IS_ERR(cd->qthread)) {
+               dev_err(dev, "could not create kthread");
+               ret = PTR_ERR(cd->qthread);
+               goto err_reset;
+       }
+       /*
+        * now advertise that we've detected a client device (card).
+        * the bus infrastructure will match it to a client driver.
+        */
+       cd->client = kzalloc(sizeof(*cd->client), GFP_KERNEL);
+       if (!cd->client) {
+               ret = -ENOMEM;
+               goto err_kthread;
+       }
+       cd->client->fieldbus_type = fieldbus_type;
+       cd->client->host = cd;
+       cd->client->dev.bus = &anybus_bus;
+       cd->client->dev.parent = dev;
+       cd->client->dev.release = client_device_release;
+       cd->client->dev.of_node =
+               anybus_of_find_child_device(dev, cd->host_idx);
+       dev_set_name(&cd->client->dev, "anybuss.card%d", cd->host_idx);
+       ret = device_register(&cd->client->dev);
+       if (ret)
+               goto err_device;
+       return cd;
+err_device:
+       device_unregister(&cd->client->dev);
+err_kthread:
+       kthread_stop(cd->qthread);
+err_reset:
+       reset_assert(cd);
+err_qcache:
+       kmem_cache_destroy(cd->qcache);
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL_GPL(anybuss_host_common_probe);
+
+void anybuss_host_common_remove(struct anybuss_host *host)
+{
+       struct anybuss_host *cd = host;
+
+       device_unregister(&cd->client->dev);
+       kthread_stop(cd->qthread);
+       reset_assert(cd);
+       kmem_cache_destroy(cd->qcache);
+}
+EXPORT_SYMBOL_GPL(anybuss_host_common_remove);
+
+static void host_release(struct device *dev, void *res)
+{
+       struct anybuss_host **dr = res;
+
+       anybuss_host_common_remove(*dr);
+}
+
+struct anybuss_host * __must_check
+devm_anybuss_host_common_probe(struct device *dev,
+                              const struct anybuss_ops *ops)
+{
+       struct anybuss_host **dr;
+       struct anybuss_host *host;
+
+       dr = devres_alloc(host_release, sizeof(struct anybuss_host *),
+                         GFP_KERNEL);
+       if (!dr)
+               return ERR_PTR(-ENOMEM);
+
+       host = anybuss_host_common_probe(dev, ops);
+       if (IS_ERR(host)) {
+               devres_free(dr);
+               return host;
+       }
+       *dr = host;
+       devres_add(dev, dr);
+       return host;
+}
+EXPORT_SYMBOL_GPL(devm_anybuss_host_common_probe);
+
+static int __init anybus_init(void)
+{
+       int ret;
+
+       ret = bus_register(&anybus_bus);
+       if (ret)
+               pr_err("could not register Anybus-S bus: %d\n", ret);
+       return ret;
+}
+module_init(anybus_init);
+
+static void __exit anybus_exit(void)
+{
+       bus_unregister(&anybus_bus);
+}
+module_exit(anybus_exit);
+
+MODULE_DESCRIPTION("HMS Anybus-S Host Driver");
+MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
+MODULE_LICENSE("GPL v2");