Contributors: 8
Author Tokens Token Proportion Commits Commit Proportion
Sven Van Asbroeck 6404 99.35% 4 30.77%
Tian Tao 19 0.29% 1 7.69%
Uwe Kleine-König 13 0.20% 3 23.08%
Nicholas Mc Guire 5 0.08% 1 7.69%
Greg Kroah-Hartman 2 0.03% 1 7.69%
Christophe Jaillet 1 0.02% 1 7.69%
Ricardo B. Marliere 1 0.02% 1 7.69%
Ajith P V 1 0.02% 1 7.69%
Total 6446 13


// 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 FIELDBUS_DEV_OFFL_MODE_CLEAR:
		op_mode = 0;
		break;
	case FIELDBUS_DEV_OFFL_MODE_FREEZE:
		op_mode = OP_MODE_FBFC;
		break;
	case FIELDBUS_DEV_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,
			    const struct device_driver *drv)
{
	const struct anybuss_client_driver *adrv =
		to_anybuss_client_driver(drv);
	struct anybuss_client *adev =
		to_anybuss_client(dev);

	return adrv->anybus_id == be16_to_cpu(adev->anybus_id);
}

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);

	return adrv->probe(adev);
}

static void anybus_bus_remove(struct device *dev)
{
	struct anybuss_client_driver *adrv =
		to_anybuss_client_driver(dev->driver);

	if (adrv->remove)
		adrv->remove(to_anybuss_client(dev));
}

static const 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)
{
	if (!drv->probe)
		return -ENODEV;

	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];
	__be16 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:
	 *   a) perform dummy IND_AB read to prevent false 'init done' irq
	 *     (already done by test_dpram() above)
	 *   b) release reset
	 *   c) wait for first interrupt
	 *   d) interrupt came in: ready to go !
	 */
	reset_deassert(cd);
	if (!wait_for_completion_timeout(&cd->card_boot, TIMEOUT)) {
		ret = -ETIMEDOUT;
		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));
	dev_info(dev, "Fieldbus type: %04X", be16_to_cpu(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->anybus_id = 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:
	put_device(&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(void *res)
{
	anybuss_host_common_remove(res);
}

struct anybuss_host * __must_check
devm_anybuss_host_common_probe(struct device *dev,
			       const struct anybuss_ops *ops)
{
	struct anybuss_host *host;
	int ret;

	host = anybuss_host_common_probe(dev, ops);
	if (IS_ERR(host))
		return host;

	ret = devm_add_action_or_reset(dev, host_release, host);
	if (ret)
		return ERR_PTR(ret);

	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");