[RFC 08/14] SoundWire: Add API for Slave registers read/write

From: Hardik Shah
Date: Fri Oct 21 2016 - 08:40:28 EST


This API is used by:
1. Bus driver to read/write MIPI defined registers.
2. Slave driver to read/write SoundWire Slave implementation
defined registers. Slave driver should use regmap driver to
access implementation defined registers, regmap driver uses
read/write APIs internally.

Signed-off-by: Hardik Shah <hardik.t.shah@xxxxxxxxx>
Signed-off-by: Sanyog Kale <sanyog.r.kale@xxxxxxxxx>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@xxxxxxxxxxxxxxx>
---
sound/sdw/sdw.c | 188 +++++++++++++++++++++++++++++++++++++++++++++++++-
sound/sdw/sdw_priv.h | 144 ++++++++++++++++++++++++++++++++++++++
2 files changed, 329 insertions(+), 3 deletions(-)

diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index d4e79b8a..c98e4d7 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -335,9 +335,191 @@ static int sdw_match(struct device *dev, struct device_driver *driver)
};

/**
- * snd_sdw_master_register_driver: SoundWire Master driver registration with
- * bus. This API will register the Master driver with the SoundWire
- * bus. It is typically called from the driver's module-init function.
+ * sdw_transfer: Local function where logic is placed to handle NOPM and PM
+ * variants of the Slave transfer functions.
+ *
+ * @mstr: Handle to SDW Master
+ * @msg: One or more messages to be transferred
+ * @num: Number of messages to be transferred.
+ *
+ * Returns negative error, else the number of messages transferred.
+ *
+ */
+static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num,
+ struct sdw_deferred_xfer_data *data)
+{
+ unsigned long orig_jiffies;
+ int ret, try, i;
+ int program_scp_addr_page = false;
+ u8 prev_adr_pg1 = 0;
+ u8 prev_adr_pg2 = 0;
+
+ for (i = 0; i < num; i++) {
+
+ /* Reset timeout for every message */
+ orig_jiffies = jiffies;
+
+ /* Inform Master driver to program SCP addr or not */
+ if ((prev_adr_pg1 != msg[i].addr_page1) ||
+ (prev_adr_pg2 != msg[i].addr_page2))
+ program_scp_addr_page = true;
+
+ for (ret = 0, try = 0; try <= mstr->retries; try++) {
+
+ /* Call deferred or sync handler based on call */
+ if (!data)
+ ret = mstr->driver->ops->xfer_msg(mstr,
+ &msg[i], program_scp_addr_page);
+
+ else if (mstr->driver->ops->xfer_msg_deferred)
+ mstr->driver->ops->xfer_msg_deferred(
+ mstr, &msg[i],
+ program_scp_addr_page,
+ data);
+ else
+ return -ENOTSUPP;
+ if (ret != -EAGAIN)
+ break;
+
+ if (time_after(jiffies, orig_jiffies + mstr->timeout))
+ break;
+ }
+
+
+ /*
+ * Set previous address page as current once message is
+ * transferred.
+ */
+ prev_adr_pg1 = msg[i].addr_page1;
+ prev_adr_pg2 = msg[i].addr_page2;
+ }
+
+ orig_jiffies = jiffies;
+
+ ret = 0;
+
+ /* Reset page address if its other than 0 */
+ if (msg[i].addr_page1 && msg[i].addr_page2) {
+ for (try = 0; try <= mstr->retries; try++) {
+ /*
+ * Reset the page address to 0, so that always there
+ * is fast path access to MIPI defined Slave
+ * registers.
+ */
+
+ ret = mstr->driver->ops->reset_page_addr(
+ mstr, msg[0].dev_num);
+
+ if (ret != -EAGAIN)
+ break;
+
+ if (time_after(jiffies, orig_jiffies + mstr->timeout))
+ break;
+ }
+ }
+
+ if (!ret)
+ return i + 1;
+
+ return ret;
+}
+
+/**
+ * sdw_bank_switch_deferred: Initiate the transfer of the message but
+ * doesn't wait for the message to be completed. Bus driver waits
+ * outside context of this API for master driver to signal message
+ * transfer complete. This is not Public API, this is used by Bus
+ * driver only for Bank switch.
+ *
+ * @mstr: Master which will transfer the message.
+ * @msg: Message to be transferred. Message length of only 1 is supported.
+ * @data: Deferred information for the message to be transferred. This is
+ * filled by Master on message transfer complete.
+ *
+ * Returns immediately after initiating the transfer, Bus driver needs to
+ * wait on xfer_complete, part of data, which is set by Master driver on
+ * completion of message transfer.
+ *
+ */
+void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
+ struct sdw_deferred_xfer_data *data)
+{
+
+ pm_runtime_get_sync(&mstr->dev);
+
+ sdw_transfer(mstr, msg, 1, data);
+
+ pm_runtime_mark_last_busy(&mstr->dev);
+ pm_runtime_put_sync_autosuspend(&mstr->dev);
+
+}
+
+/**
+ * snd_sdw_slave_transfer: Transfer message on bus.
+ *
+ * @master: Master which will transfer the message.
+ * @msg: Array of messages to be transferred.
+ * @num: Number of messages to be transferred, messages include read and
+ * write messages, but not the ping commands. The read and write
+ * messages are transmitted as a part of read and write SoundWire
+ * commands with a parameter containing the payload.
+ *
+ * Returns the number of messages successfully transferred else appropriate
+ * error code.
+ */
+int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg,
+ unsigned int num)
+{
+ int ret;
+
+ /*
+ * Master reports the successfully transmitted messages onto the
+ * bus. If there are N message to be transmitted onto bus, and if
+ * Master gets error at (N-2) message it will report number of
+ * message transferred as N-2 Error is reported if ACK is not
+ * received for all messages or NACK is received for any of the
+ * transmitted messages. Currently both ACK not getting received
+ * and NACK is treated as error. But for upper level like regmap,
+ * both (Absence of ACK or NACK) errors are same as failure.
+ */
+
+ /*
+ * Make sure Master is woken up before message transfer Ideally
+ * function calling this should have wokenup Master as this will be
+ * called by Slave driver, and it will do runtime_get for itself,
+ * which will make sure Master is woken up as Master is parent Linux
+ * device of Slave. But if Slave is not implementing RTPM, it may
+ * not do this, so bus driver has to do it always irrespective of
+ * what Slave does.
+ */
+ pm_runtime_get_sync(&master->dev);
+
+ if (in_atomic() || irqs_disabled()) {
+ ret = mutex_trylock(&master->msg_lock);
+ if (!ret) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ } else {
+ mutex_lock(&master->msg_lock);
+ }
+
+ ret = sdw_transfer(master, msg, num, NULL);
+
+ mutex_unlock(&master->msg_lock);
+out:
+ /* Put Master to sleep once message is transferred */
+ pm_runtime_mark_last_busy(&master->dev);
+ pm_runtime_put_sync_autosuspend(&master->dev);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_slave_transfer);
+
+/**
+ * snd_sdw_master_register_driver: This API will register the Master driver
+ * with the SoundWire bus. It is typically called from the driver's
+ * module-init function.
*
* @driver: Master Driver to be associated with Master interface.
* @owner: Module owner, generally THIS module.
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
index 5911aa6..0af1c99 100644
--- a/sound/sdw/sdw_priv.h
+++ b/sound/sdw/sdw_priv.h
@@ -99,4 +99,148 @@ struct snd_sdw_core {
struct idr idr;
};

+/**
+ * sdw_bank_switch_deferred: Initiate the transfer of the message but
+ * doesn't wait for the message to be completed. Bus driver waits
+ * outside context of this API for master driver to signal message
+ * transfer complete. This is not Public API, this is used by Bus
+ * driver only for Bank switch.
+ *
+ * @mstr: Master which will transfer the message.
+ * @msg: Message to be transferred. Message length of only 1 is supported.
+ * @data: Deferred information for the message to be transferred. This is
+ * filled by Master on message transfer complete.
+ *
+ * Returns immediately after initiating the transfer, Bus driver needs to
+ * wait on xfer_complete, part of data, which is set by Master driver on
+ * completion of message transfer.
+ */
+void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
+ struct sdw_deferred_xfer_data *data);
+/*
+ * Helper function for bus driver to write messages. Since bus driver
+ * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
+ * set to 0.
+ */
+static inline int sdw_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp, u16 addr,
+ u16 len, u8 *buf, u8 dev_num,
+ struct sdw_master *mstr,
+ int num_msg)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_WRITE;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+
+ return snd_sdw_slave_transfer(mstr, msg, num_msg);
+}
+
+/*
+ * Helper function for bus driver to read messages. Since bus driver
+ * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
+ * set to 0.
+ */
+static inline int sdw_rd_msg(struct sdw_msg *msg, bool xmit_on_ssp, u16 addr,
+ u16 len, u8 *buf, u8 dev_num,
+ struct sdw_master *mstr,
+ int num_msg)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_READ;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+
+ return snd_sdw_slave_transfer(mstr, msg, num_msg);
+}
+
+/*
+ * Helper function for bus driver to write messages (nopm version). Since
+ * bus driver operates on MIPI defined Slave registers, addr_page1 and
+ * addr_page2 is set to 0.
+ */
+static inline int sdw_wr_msg_nopm(struct sdw_msg *msg, bool xmit_on_ssp,
+ u16 addr, u16 len, u8 *buf,
+ u8 dev_num,
+ struct sdw_master *mstr,
+ int num_msg)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_WRITE;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+
+ return snd_sdw_slave_transfer(mstr, msg, num_msg);
+}
+
+/*
+ * Helper function for bus driver to read messages (nopm version). Since
+ * bus driver operates on MIPI defined Slave registers, addr_page1 and
+ * addr_page2 is set to 0.
+ */
+static inline int sdw_rd_msg_nopm(struct sdw_msg *msg, bool xmit_on_ssp,
+ u16 addr, u16 len, u8 *buf,
+ u8 dev_num,
+ struct sdw_master *mstr,
+ int num_msg)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_READ;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+
+ return snd_sdw_slave_transfer(mstr, msg, num_msg);
+}
+
+/*
+ * Helper function for bus driver to create read messages. Since bus driver
+ * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
+ * set to 0.
+ */
+static inline void sdw_create_rd_msg(struct sdw_msg *msg, bool xmit_on_ssp,
+ u16 addr, u16 len, u8 *buf, u8 dev_num)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_READ;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+}
+
+/*
+ * Helper function for bus driver to create write messages. Since bus driver
+ * operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
+ * set to 0.
+ */
+static inline void sdw_create_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp,
+ u16 addr, u16 len, u8 *buf, u8 dev_num)
+{
+ msg->xmit_on_ssp = xmit_on_ssp;
+ msg->r_w_flag = SDW_MSG_FLAG_WRITE;
+ msg->addr = addr;
+ msg->len = len;
+ msg->buf = buf;
+ msg->dev_num = dev_num;
+ msg->addr_page1 = 0x0;
+ msg->addr_page2 = 0x0;
+}
+
#endif /* _LINUX_SDW_PRIV_H */
--
1.7.9.5