[PATCH 3/3] slimbus: Add messaging APIs to slimbus framework

From: Sagar Dharia
Date: Sun Jun 14 2015 - 01:50:31 EST


Slimbus devices use value-element, and information elements to
control device parameters (e.g. value element is used to represent
gain for codec, information element is used to represent interrupt
status for codec when codec interrupt fires).
Messaging APIs are used to set/get these value and information
elements. Slimbus specification uses 8-bit "transaction IDs" for
messages where a read-value is anticipated. Framework uses a table
of pointers to store those TIDs and responds back to the caller in
O(1).
Caller can opt to do synchronous, or asynchronous reads/writes. For
asynchronous operations, the callback can be called from atomic
context.

Signed-off-by: Sagar Dharia <sdharia@xxxxxxxxxxxxxx>
Tested-by: Naveen Kaje <nkaje@xxxxxxxxxxxxxx>
---
drivers/slimbus/slimbus.c | 273 ++++++++++++++++++++++++++++++++++++++++++++++
include/linux/slimbus.h | 124 +++++++++++++++++++++
2 files changed, 397 insertions(+)

diff --git a/drivers/slimbus/slimbus.c b/drivers/slimbus/slimbus.c
index f51b503..44614e1 100644
--- a/drivers/slimbus/slimbus.c
+++ b/drivers/slimbus/slimbus.c
@@ -26,6 +26,14 @@ static DEFINE_IDR(ctrl_idr);
static struct device_type slim_dev_type;
static struct device_type slim_ctrl_type;

+#define DEFINE_SLIM_LDEST_TXN(name, mc, rl, la, msg) \
+ struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_LOGICALADDR, 0,\
+ 0, la, msg, }
+
+#define DEFINE_SLIM_BCAST_TXN(name, mc, rl, la, msg) \
+ struct slim_msg_txn name = { rl, 0, mc, SLIM_MSG_DEST_BROADCAST, 0,\
+ 0, la, msg, }
+
static int slim_compare_eaddr(struct slim_eaddr *a, struct slim_eaddr *b)
{
if (a->manf_id == b->manf_id && a->prod_code == b->prod_code &&
@@ -762,6 +770,271 @@ int slim_get_logical_addr(struct slim_device *sb, struct slim_eaddr *e_addr,
}
EXPORT_SYMBOL(slim_get_logical_addr);

+/*
+ * slim_msg_response: Deliver Message response received from a device to the
+ * framework.
+ * @ctrl: Controller handle
+ * @reply: Reply received from the device
+ * @len: Length of the reply
+ * @tid: Transaction ID received with which framework can associate reply.
+ * Called by controller to inform framework about the response received.
+ * This helps in making the API asynchronous, and controller-driver doesn't need
+ * to manage 1 more table other than the one managed by framework mapping TID
+ * with buffers
+ */
+void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid, u8 len)
+{
+ struct slim_val_inf *msg;
+
+ spin_lock(&ctrl->txn_lock);
+ msg = ctrl->txnt[tid];
+ if (msg == NULL || msg->rbuf == NULL) {
+ spin_unlock(&ctrl->txn_lock);
+ dev_err(&ctrl->dev, "Got response to invalid TID:%d, len:%d",
+ tid, len);
+ return;
+ }
+ memcpy(msg->rbuf, reply, len);
+ ctrl->txnt[tid] = NULL;
+ if (msg->comp_cb)
+ msg->comp_cb(msg->ctx);
+ spin_unlock(&ctrl->txn_lock);
+}
+EXPORT_SYMBOL(slim_msg_response);
+
+static int slim_processtxn(struct slim_controller *ctrl,
+ struct slim_msg_txn *txn)
+{
+ int i = 0, ret = 0;
+ unsigned long flags;
+
+ if (txn->mt == SLIM_MSG_MT_CORE &&
+ (txn->mc == SLIM_MSG_MC_REQUEST_INFORMATION ||
+ txn->mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION ||
+ txn->mc == SLIM_MSG_MC_REQUEST_VALUE ||
+ txn->mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION)) {
+ spin_lock_irqsave(&ctrl->txn_lock, flags);
+ for (i = 0; i < ctrl->last_tid; i++) {
+ if (ctrl->txnt[i] == NULL)
+ break;
+ }
+ if (i >= ctrl->last_tid) {
+ if (ctrl->last_tid == 255) {
+ spin_unlock_irqrestore(&ctrl->txn_lock, flags);
+ return -ENOMEM;
+ }
+ ctrl->last_tid++;
+ }
+ ctrl->txnt[i] = txn->msg;
+ txn->tid = i;
+ spin_unlock_irqrestore(&ctrl->txn_lock, flags);
+ }
+
+ ret = ctrl->xfer_msg(ctrl, txn);
+ return ret;
+}
+
+static int slim_val_inf_sanity(struct slim_val_inf *msg, int oper)
+{
+ if (!msg || msg->num_bytes > 16 ||
+ (msg->start_offset + msg->num_bytes) > 0xC00)
+ return -EINVAL;
+ switch (oper) {
+ case SLIM_MSG_MC_REQUEST_VALUE:
+ case SLIM_MSG_MC_REQUEST_INFORMATION:
+ if (msg->rbuf == NULL)
+ return -EINVAL;
+ return 0;
+ case SLIM_MSG_MC_CHANGE_VALUE:
+ case SLIM_MSG_MC_CLEAR_INFORMATION:
+ if (msg->wbuf == NULL)
+ return -EINVAL;
+ return 0;
+ case SLIM_MSG_MC_REQUEST_CHANGE_VALUE:
+ case SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION:
+ if (msg->rbuf == NULL || msg->wbuf == NULL)
+ return -EINVAL;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static u16 slim_slicecodefromsize(u16 req)
+{
+ static const u8 codetosize[8] = {1, 2, 3, 4, 6, 8, 12, 16};
+
+ if (req >= ARRAY_SIZE(codetosize))
+ return 0;
+ else
+ return codetosize[req];
+}
+
+static u16 slim_slicesize(int code)
+{
+ static const u8 sizetocode[16] = {
+ 0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7
+ };
+
+ clamp(code, 1, (int)ARRAY_SIZE(sizetocode));
+ return sizetocode[code - 1];
+}
+
+static void slim_sync_default_cb(void *ctx)
+{
+ struct completion *comp = ctx;
+
+ complete(comp);
+}
+
+static int slim_xfer_msg(struct slim_controller *ctrl,
+ struct slim_device *sbdev, struct slim_val_inf *msg,
+ u8 mc)
+{
+ DEFINE_SLIM_LDEST_TXN(txn_stack, mc, 6, sbdev->laddr, msg);
+ struct slim_msg_txn *txn = &txn_stack;
+ int ret;
+ unsigned long flags;
+ u16 sl, cur;
+ bool tid_txn, async = false;
+ DECLARE_COMPLETION_ONSTACK(complete);
+
+ ret = slim_val_inf_sanity(msg, mc);
+ if (ret) {
+ pr_err("Sanity check failed for msg:offset:0x%x, mc:%d",
+ msg->start_offset, mc);
+ return ret;
+ }
+
+ tid_txn = slim_tid_txn(mc);
+
+ sl = slim_slicesize(msg->num_bytes);
+ dev_err(&ctrl->dev, "SB xfer msg:os:%x, len:%d, MC:%x, sl:%x\n",
+ msg->start_offset, msg->num_bytes, mc, sl);
+
+ cur = slim_slicecodefromsize(sl);
+ txn->ec = ((sl | (1 << 3)) | ((msg->start_offset & 0xFFF) << 4));
+
+ if (!msg->comp_cb && tid_txn) {
+ msg->comp_cb = slim_sync_default_cb;
+ msg->ctx = &complete;
+ } else
+ async = true;
+
+ if (mc == SLIM_MSG_MC_REQUEST_CHANGE_VALUE ||
+ mc == SLIM_MSG_MC_CHANGE_VALUE ||
+ mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION ||
+ mc == SLIM_MSG_MC_CLEAR_INFORMATION)
+ txn->rl += msg->num_bytes;
+ if (tid_txn)
+ txn->rl++;
+
+ ret = slim_processtxn(ctrl, txn);
+
+ /* sync read */
+ if (!ret && tid_txn && !async) {
+ ret = wait_for_completion_timeout(&complete, HZ);
+ if (!ret)
+ ret = -ETIMEDOUT;
+ else
+ ret = 0;
+ }
+
+ if (ret && tid_txn) {
+ spin_lock_irqsave(&ctrl->txn_lock, flags);
+ /* Invalidate the transaction */
+ ctrl->txnt[txn->tid] = NULL;
+ spin_unlock_irqrestore(&ctrl->txn_lock, flags);
+ }
+ if (ret)
+ pr_err("slimbus transfer error:%d:offset:0x%x, MC:%d", ret,
+ msg->start_offset, mc);
+ if (!async && tid_txn) {
+ msg->comp_cb = NULL;
+ msg->ctx = NULL;
+ }
+ return ret;
+}
+EXPORT_SYMBOL(slim_xfer_msg);
+
+/* Message APIs Unicast message APIs used by slimbus slave drivers */
+
+/*
+ * Message API access routines.
+ * @sb: client handle requesting elemental message reads, writes.
+ * @msg: Input structure for start-offset, number of bytes to read.
+ * context: can sleep
+ * Returns:
+ * -EINVAL: Invalid parameters
+ * -ETIMEDOUT: If controller could not complete the request. This may happen if
+ * the bus lines are not clocked, controller is not powered-on, slave with
+ * given address is not enumerated/responding.
+ */
+int slim_request_val_element(struct slim_device *sb,
+ struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_VALUE);
+}
+EXPORT_SYMBOL(slim_request_val_element);
+
+int slim_request_inf_element(struct slim_device *sb,
+ struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_INFORMATION);
+}
+EXPORT_SYMBOL(slim_request_inf_element);
+
+int slim_change_val_element(struct slim_device *sb, struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CHANGE_VALUE);
+}
+EXPORT_SYMBOL(slim_change_val_element);
+
+int slim_clear_inf_element(struct slim_device *sb, struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_CLEAR_INFORMATION);
+}
+EXPORT_SYMBOL(slim_clear_inf_element);
+
+int slim_request_change_val_element(struct slim_device *sb,
+ struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg, SLIM_MSG_MC_REQUEST_CHANGE_VALUE);
+}
+EXPORT_SYMBOL(slim_request_change_val_element);
+
+int slim_request_clear_inf_element(struct slim_device *sb,
+ struct slim_val_inf *msg)
+{
+ struct slim_controller *ctrl = sb->ctrl;
+
+ if (!ctrl)
+ return -EINVAL;
+ return slim_xfer_msg(ctrl, sb, msg,
+ SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION);
+}
+EXPORT_SYMBOL(slim_request_clear_inf_element);
+
#if IS_ENABLED(CONFIG_OF)
/* OF helpers for SLIMbus */
int of_register_slim_devices(struct slim_controller *ctrl)
diff --git a/include/linux/slimbus.h b/include/linux/slimbus.h
index 61b7c74..1d98c58 100644
--- a/include/linux/slimbus.h
+++ b/include/linux/slimbus.h
@@ -34,6 +34,7 @@ extern struct bus_type slimbus_type;
#define SLIM_FRM_SLOTS_PER_SUPERFRAME 16
#define SLIM_GDE_SLOTS_PER_SUPERFRAME 2

+#define SLIM_MAX_TXNS 256
struct slim_controller;
struct slim_device;

@@ -97,12 +98,67 @@ struct slim_addrt {
#define SLIM_MSG_MC_ASSIGN_LOGICAL_ADDRESS 0x2
#define SLIM_MSG_MC_REPORT_ABSENT 0xF

+/* Information Element management messages */
+#define SLIM_MSG_MC_REQUEST_INFORMATION 0x20
+#define SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION 0x21
+#define SLIM_MSG_MC_REPLY_INFORMATION 0x24
+#define SLIM_MSG_MC_CLEAR_INFORMATION 0x28
+#define SLIM_MSG_MC_REPORT_INFORMATION 0x29
+
+/* Value Element management messages */
+#define SLIM_MSG_MC_REQUEST_VALUE 0x60
+#define SLIM_MSG_MC_REQUEST_CHANGE_VALUE 0x61
+#define SLIM_MSG_MC_REPLY_VALUE 0x64
+#define SLIM_MSG_MC_CHANGE_VALUE 0x68
+
/* Destination type Values */
#define SLIM_MSG_DEST_LOGICALADDR 0
#define SLIM_MSG_DEST_ENUMADDR 1
#define SLIM_MSG_DEST_BROADCAST 3

/*
+ * struct slim_val_inf: Slimbus value or information element
+ * @start_offset: Specifies starting offset in information/value element map
+ * @num_bytes: upto 16. This ensures that the message will fit the slicesize
+ * per slimbus spec
+ * @comp_cb: Callback if this read/write is asynchronous
+ * @ctx: Argument for comp_cb
+ */
+struct slim_val_inf {
+ u16 start_offset;
+ u8 num_bytes;
+ u8 *rbuf;
+ const u8 *wbuf;
+ void (*comp_cb)(void *ctx);
+ void *ctx;
+};
+
+/*
+ * struct slim_msg_txn: Message to be sent by the controller.
+ * This structure has packet header, payload and buffer to be filled (if any)
+ * @rl: Header field. remaining length.
+ * @mt: Header field. Message type.
+ * @mc: Header field. LSB is message code for type mt.
+ * @dt: Header field. Destination type.
+ * @ec: Element code. Used for elemental access APIs.
+ * @len: Length of payload. (excludes ec)
+ * @tid: Transaction ID. Used for messages expecting response.
+ * (relevant for message-codes involving read operation)
+ * @la: Logical address of the device this message is going to.
+ * (Not used when destination type is broadcast.)
+ * @msg: Elemental access message to be read/written
+ */
+struct slim_msg_txn {
+ u8 rl;
+ u8 mt;
+ u8 mc;
+ u8 dt;
+ u16 ec;
+ u8 tid;
+ u8 la;
+ struct slim_val_inf *msg;
+};
+/*
* struct slim_controller: Controls every instance of SLIMbus
* (similar to 'master' on SPI)
* 'Manager device' is responsible for device management, bandwidth
@@ -137,6 +193,9 @@ struct slim_addrt {
* @num_dev: Number of active slimbus slaves on this bus
* @devs: List of devices on this controller
* @wq: Workqueue per controller used to notify devices when they report present
+ * @txnt: Table of transactions having transaction ID
+ * @txn_lock: Lock to protect table of transactions
+ * @last_tid: size of the table txnt (can't grow beyond 256 since TID is 8-bits)
* @dev_released: completion used to signal when sysfs has released this
* controller so that it can be deleted during shutdown
* @xfer_msg: Transfer a message on this controller (this can be a broadcast
@@ -163,7 +222,12 @@ struct slim_controller {
u8 num_dev;
struct list_head devs;
struct workqueue_struct *wq;
+ struct slim_val_inf *txnt[SLIM_MAX_TXNS];
+ u8 last_tid;
+ spinlock_t txn_lock;
struct completion dev_released;
+ int (*xfer_msg)(struct slim_controller *ctrl,
+ struct slim_msg_txn *txn);
int (*set_laddr)(struct slim_controller *ctrl,
struct slim_eaddr *ea, u8 laddr);
int (*get_laddr)(struct slim_controller *ctrl,
@@ -409,4 +473,64 @@ static inline void slim_set_clientdata(struct slim_device *dev, void *data)
dev_set_drvdata(&dev->dev, data);
}

+/* Message APIs Unicast message APIs used by slimbus slave drivers */
+
+/*
+ * Message API access routines for value elements.
+ * @sb: client handle requesting elemental message reads, writes.
+ * @msg: Input structure for start-offset, number of bytes to read.
+ * context: can sleep
+ * Returns:
+ * -EINVAL: Invalid parameters
+ * -ETIMEDOUT: If controller could not complete the request. This may happen if
+ * the bus lines are not clocked, controller is not powered-on, slave with
+ * given address is not enumerated/responding.
+ */
+extern int slim_request_val_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+extern int slim_change_val_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+extern int slim_request_change_val_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+
+
+/*
+ * Message API access routines for information elements.
+ * @sb: client handle requesting elemental message reads, writes.
+ * @msg: Input structure for start-offset, number of bytes to read
+ * wbuf will contain information element(s) bit masks to be cleared.
+ * rbuf will return what the information element value was
+ */
+
+extern int slim_request_inf_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+extern int slim_clear_inf_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+extern int slim_request_clear_inf_element(struct slim_device *sb,
+ struct slim_val_inf *msg);
+
+/*
+ * slim_msg_response: Deliver Message response received from a device to the
+ * framework.
+ * @ctrl: Controller handle
+ * @reply: Reply received from the device
+ * @len: Length of the reply
+ * @tid: Transaction ID received with which framework can associate reply.
+ * Called by controller to inform framework about the response received.
+ * This helps in making the API asynchronous, and controller-driver doesn't need
+ * to manage 1 more table other than the one managed by framework mapping TID
+ * with buffers
+ */
+extern void slim_msg_response(struct slim_controller *ctrl, u8 *reply, u8 tid,
+ u8 len);
+
+/* end of message apis */
+
+static inline bool slim_tid_txn(u8 mc)
+{
+ return (mc == SLIM_MSG_MC_REQUEST_INFORMATION ||
+ mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION ||
+ mc == SLIM_MSG_MC_REQUEST_VALUE ||
+ mc == SLIM_MSG_MC_REQUEST_CLEAR_INFORMATION);
+}
#endif /* _LINUX_SLIMBUS_H */
--
1.8.2.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/