Following RFC series adds SoundWire bus driver interface based on the
MIPI SoundWire specification 1.1
The SoundWire protocol is a robust, scalable, low complexity, low power,
low latency, two-pin (clock and data) multi-drop bus that allows for the
transfer of multiple audio streams and embedded control/commands.
SoundWire provides synchronization capabilities and supports both PCM
and PDM, multichannel data, isochronous and asynchronous modes.
SoundWire does borrow a number of concepts from existing interfaces such
as HDAudio, AC97, SLIMbus, which already provide control/audio on the
same wires, or legacy interfaces such as I2C/I2S, TDM, PDM.
The capabilities of SoundWire make it unique however in that it can be
implemented in peripherals such as microphones or amplifiers, mix PCM
and PDM formats and enable clock scaling to reduce power consumption.
More details about the SoundWire protocol can be obtained from MIPI
website as listed below (accessible to members only).
1. http://mipi.org/learning-center/webinars
2. https://members.mipi.org/wg/All-Members/document/download/65078
3. https://members.mipi.org/wg/Contributors/document/70055
Hardik Shah (12):
SoundWire: Add SoundWire bus driver documentation
SoundWire: Add SoundWire stream documentation
SoundWire: Add error handling and locking documentation
SoundWire: Add device_id table for SoundWire bus
SoundWire: Add SoundWire bus driver interfaces
SoundWire: Add register/unregister APIs
SoundWire: Add SoundWire Slaves register definitions
SoundWire: Add API for Slave registers read/write
SoundWire: Add support to handle Slave status change
SoundWire: Add support for clock stop
SoundWire: Add tracing for Slave register read/write
regmap: SoundWire: Add regmap support for SoundWire bus
Sanyog Kale (2):
SoundWire: Add stream and port configuration
SoundWire: Add support for SoundWire stream management
Documentation/sound/alsa/sdw/error_handling.txt | 71 +
Documentation/sound/alsa/sdw/locking.txt | 64 +
Documentation/sound/alsa/sdw/stream.txt | 346 +++
Documentation/sound/alsa/sdw/summary.txt | 253 ++
MAINTAINERS | 12 +
drivers/base/regmap/Kconfig | 3 +
drivers/base/regmap/Makefile | 1 +
drivers/base/regmap/regmap-sdw.c | 240 ++
include/linux/mod_devicetable.h | 13 +
include/linux/regmap.h | 37 +
include/sound/sdw/sdw_registers.h | 277 ++
include/sound/sdw_bus.h | 905 ++++++
include/sound/sdw_master.h | 627 ++++
include/sound/sdw_slave.h | 563 ++++
include/trace/events/sdw.h | 209 ++
sound/Kconfig | 2 +
sound/Makefile | 1 +
sound/sdw/Kconfig | 6 +
sound/sdw/Makefile | 1 +
sound/sdw/sdw.c | 3749 +++++++++++++++++++++++
sound/sdw/sdw_priv.h | 811 +++++
sound/sdw/sdw_runtime.c | 2807 +++++++++++++++++
22 files changed, 10998 insertions(+)
create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt
create mode 100644 Documentation/sound/alsa/sdw/locking.txt
create mode 100644 Documentation/sound/alsa/sdw/stream.txt
create mode 100644 Documentation/sound/alsa/sdw/summary.txt
create mode 100644 drivers/base/regmap/regmap-sdw.c
create mode 100644 include/sound/sdw/sdw_registers.h
create mode 100644 include/sound/sdw_bus.h
create mode 100644 include/sound/sdw_master.h
create mode 100644 include/sound/sdw_slave.h
create mode 100644 include/trace/events/sdw.h
create mode 100644 sound/sdw/Kconfig
create mode 100644 sound/sdw/Makefile
create mode 100644 sound/sdw/sdw.c
create mode 100644 sound/sdw/sdw_priv.h
create mode 100644 sound/sdw/sdw_runtime.c
--
1.7.9.5
Add device_id table for the SoundWire Master and Slave devices
and driver registration.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
include/linux/mod_devicetable.h | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index ed84c07..f443152 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -672,5 +672,18 @@ struct fsl_mc_device_id {
const char obj_type[16];
};
+/* SoundWire */
+#define SOUNDWIRE_NAME_SIZE 32
+#define SOUNDWIRE_MODULE_PREFIX "sdw:"
+
+struct sdw_slave_id {
+ char name[SOUNDWIRE_NAME_SIZE];
+ kernel_ulong_t driver_data; /* Data private to the driver */
+};
+
+struct sdw_master_id {
+ char name[SOUNDWIRE_NAME_SIZE];
+ kernel_ulong_t driver_data; /* Data private to the driver */
+};
#endif /* LINUX_MOD_DEVICETABLE_H */
--
1.7.9.5
This patch adds stream documentation describing SoundWire stream and
stream states.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
Documentation/sound/alsa/sdw/stream.txt | 346 +++++++++++++++++++++++++++++++
1 file changed, 346 insertions(+)
create mode 100644 Documentation/sound/alsa/sdw/stream.txt
diff --git a/Documentation/sound/alsa/sdw/stream.txt b/Documentation/sound/alsa/sdw/stream.txt
new file mode 100644
index 0000000..a1a2ed0
--- /dev/null
+++ b/Documentation/sound/alsa/sdw/stream.txt
@@ -0,0 +1,346 @@
+Audio Stream in SoundWire
+=========================
+An audio stream is a logical or virtual connection created between
+
+1. System memory buffer(s) and Codec(s)
+2. DSP memory buffer(s) and Codec(s)
+3. FIFO(s) and Codec(s)
+4. Codec(s) and Codec(s)
+
+which is typically driven by a DMA(s) channel through the data link. An
+audio stream contains one or more channels of data. All channels within
+stream must have same sample rate and same sample size.
+
+Assume a stream with two channels (Left & Right) is opened using
+SoundWire interface. Below are some of different way a stream can be
+represented in SoundWire.
+
+Stream Sample in memory (System memory, DSP memory or FIFOs):
+-------------------------
+| L | R | L | R | L | R |
+-------------------------
+
+Example 1: Stereo Stream with L and R channels is rendered from Master
+to Slave. Both Master and Slave is using single port.
+
++---------------+ Clock Signal +---------------+
+| Master +---------------------------------------+ Slave |
+| Interface | | Interface |
+| | | 1 |
+| | Data Signal | |
+| L + R +---------------------------------------+ L + R |
+| (Data) | Data Direction | (Data) |
++---------------+ +-----------------------> +---------------+
+
+
+Example 2: Stereo Stream with L and R channels is captured from Slave to
+Master. Both Master and Slave is using single port.
+
+
++---------------+ Clock Signal +---------------+
+| Master +---------------------------------------+ Slave |
+| Interface | | Interface |
+| | | 1 |
+| | Data Signal | |
+| L + R +---------------------------------------+ L + R |
+| (Data) | Data Direction | (Data) |
++---------------+ <-----------------------+ +---------------+
+
+
+Example 3: Stereo Stream with L and R channels is rendered by Master.
+Each of the L and R channel is received by two different Slaves. Master
+and both Slaves are using single port.
+
+
++---------------+ Clock Signal +---------------+
+| Master +------------+--------------------------+ Slave |
+| Interface | | | Interface |
+| | | | 1 |
+| | | Data Signal | |
+| L + R +------+--------------------------------+ L |
+| (Data) | | | Data Direction | (Data) |
++---------------+ | | +-------------> +---------------+
+ | |
+ | |
+ | | +---------------+
+ | +------------------------> | Slave |
+ | | Interface |
+ | | 2 |
+ | | |
+ +------------------------------> | R |
+ | (Data) |
+ +---------------+
+
+
+Example 4: Stereo Stream with L and R channel is rendered by 2 Masters,
+each rendering one channel, and is received by two different Slaves,
+each receiving one channel. Both Masters and both Slaves are using
+single port.
+
+
++---------------+ Clock Signal +---------------+
+| Master +---------------------------------------+ Slave |
+| Interface | | Interface |
+| 1 | | 1 |
+| | Data Signal | |
+| L +---------------------------------------+ L |
+| (Data) | Data Direction | (Data) |
++---------------+ +-----------------------> +---------------+
+
++---------------+ Clock Signal +---------------+
+| Master +---------------------------------------+ Slave |
+| Interface | | Interface |
+| 2 | | 2 |
+| | Data Signal | |
+| R +---------------------------------------+ R |
+| (Data) | Data Direction | (Data) |
++---------------+ +-----------------------> +---------------+
+
+
+Example 5: Stereo Stream with L and R channel is rendered by two
+different Ports of the Master and is received by only single Port of the
+Slave interface.
+
+
++------------------------+
+| |
+| +--------------+ +-------------------+
+| | || | |
+| | Data Port || L Channel | |
+| | 1 |------------+ | |
+| | L Channel || | +-----+----+ |
+| | (Data) || | L + R Channel || Data | |
+| Master +--------------+ | +---+---------> || Port | |
+| Interface | | || 1 | |
+| +--------------+ | || | |
+| | || | +----------+ |
+| | Data Port |------------+ | |
+| | 2 || R Channel | Slave |
+| | R Channel || | Interface |
+| | (Data) || | 1 |
+| +--------------+ Clock Signal | L + R |
+| +---------------------------> | (Data) |
++------------------------+ | |
+ +-------------------+
+
+
+Example 6: Stereo stream with L and R channel is rendered by Slave
+Interface 1 to Slave Interface 2. Master is driving the Clock. Audio
+stream data flow is from Slave interface 1 to Slave Interface 2.
+
+
+ +---------------+
++---------------+ Clock Signal | |
+| Master +--+----------------------------------> | Slave |
+| Interface | | Data Signal | Interface |
+| +--------+----------------------------+ | 1 |
++---------------+ | | | |
+ | | | L + R |
+ | | +----------------+ | (Data) |
+ | | | +---------------+
+ | | |
+ | | | Data Direction
+ | | | +---------------+
+ | | | | |
+ | | +----------------> | Slave |
+ | | | Interface |
+ | | Clock Signal | 2 |
+ | +------------------------------+ |
+ | Data Signal | L + R |
+ +------------------------------------+ (Data) |
+ +---------------+
+
+
+SoundWire Stream Management flow
+================================
+
+SoundWire Stream definitions:
+1. Current stream: This is classified as the stream on which operation
+has to be performed like prepare, enable, disable, de-prepare etc.
+
+2. Active stream: This is classified as the stream which is already
+active on bus other than current stream. There can be multiple active
+streams on the bus.
+
+SoundWire bus driver manages stream operations for each stream getting
+rendered/captured on the SoundWire bus.
+
+This section explains what Bus driver operations are done for each of
+the stream getting allocated/released on bus driver. Following are the
+stream states maintained by the Bus driver for each of the audio stream
+getting opened.
+
+
+SoundWire stream states
+=======================
+Below figure shows the SoundWire stream states and possible state
+transition diagram.
+
+|--------------| |-------------| |--------------| |--------------|
+| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE |
+| STATE | | STATE | | STATE | | STATE |
+|--------------| |-------------| |--------------| |--------------|
+ ^ |
+ | |
+ | |
+ | |
+ | \/
+ |--------------| |--------------| |--------------|
+ | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
+ | STATE | | STATE | | STATE |
+ |--------------| |--------------| |--------------|
+
+
+SoundWire Stream State Operations
+==================================
+Below section explains the operations done by the bus driver on
+Master(s) and Slave(s) as part of stream state transitions.
+
+SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry
+state of the stream. Operations performed before entering in this
+state:
+1. An unique stream tag is assigned to stream. This stream tag is used
+as a reference for all the operations performed on stream.
+
+2. The resources required for holding stream runtime information are
+allocated and initialized. This holds all stream related information
+such as stream type (PCM/PDM) and parameters, Master and Slave interface
+associated with the stream, reference counting, stream state etc.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_ALLOC.
+
+
+SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations
+performed before entering in this state:
+1. The resources allocated for stream information in
+SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters,
+Masters and Slaves runtime information associated with the stream.
+
+2. All the Masters and Slaves associated with the stream updates the
+port configuration to bus driver. This includes port numbers allocated
+by Master(s) and Slave(s) for this stream.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_CONFIG.
+
+
+SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed
+before entering in this state:
+1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP
+interval are computed based on current stream as well as already active
+streams on bus. Re-computation is required to accommodate current stream
+on the bus.
+
+2. Transport parameters of all Master and Slave ports are computed for
+the current as well as already active stream based on above calculated
+frame shape and clock frequency.
+
+3. Computed bus and transport parameters are programmed in Master and
+Slave registers. The banked registers programming is done on the
+alternate bank (bank currently unused). Port channels are enabled for
+the already active streams on the alternate bank (bank currently
+unused). This is done in order to not to disrupt already active
+stream(s).
+
+4. Once all the new values are programmed, bus initiates switch to
+alternate bank. Once switch is successful, the port channels enabled on
+previous bank for already active streams are disabled.
+
+5. Ports of Master and Slave for current stream are prepared.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_PREPARE.
+
+
+SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed
+before entering in this state:
+1. All the values computed in SDW_STATE_STRM_PREPARE state are
+programmed in alternate bank (bank currently unused). It includes
+programming of already active streams as well.
+
+2. All the Master and Slave port channels for the current stream are
+enabled on alternate bank (bank currently unused).
+
+3. Once all the new values are programmed, bus initiates switch to
+alternate bank. Once the switch is successful, the port channels enabled
+on previous bank for already active streams are disabled.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_ENABLE.
+
+
+SDW_STATE_STRM_DISABLE --> Disable state of stream. Operations performed
+before entering in this state:
+1. Disable for Master and Slave ports channels is performed on on
+alternate bank (bank currently unused) registers for current stream.
+
+2. All the current configuration of bus and Master and Slave ports are
+programmed into alternate bank (bank currently unused). It includes
+programming of already active streams port channels on alternate bank
+(bank currently unused).
+
+3. Bus initiates switch to alternate bank. Once the switch is
+successful, the port channels of current stream are disabled. All the
+port channels enabled on previous bank for active stream are disabled.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_DISABLE.
+
+
+SDW_STATE_STRM_DEPREPARE: De-prepare state of stream. Operations
+performed before entering in this state:
+1. Check the bandwidth required per Master. If its zero, de-prepare
+current stream and move stream state to SDW_STATE_STRM_DEPREPARE, rest
+of the steps are not required. If bandwidth required per Master is non
+zero that means some more streams are running on Master and continue
+with next step.
+
+2. Bus parameters and transport parameters are computed for the active
+stream(s) on the given Master.
+
+3. All the computed values for active stream(s) are programmed into
+alternate bank (bank currently unused) in Master and Slave registers.
+
+4. Bus initiates switch to alternate bank. Once the switch is
+successful, all the port channels enabled on previous bank for active
+stream are disabled.
+
+5. De-prepare ports of the Master and Slave associated with current
+stream.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_DEPREPARE.
+
+
+SDW_STATE_STRM_RELEASE: Release state of stream. Operations performed
+before entering in this state:
+1. Release port resources for all Master and Slave ports used for
+current stream.
+
+2. Release Master and Slave runtime resources used for current stream.
+
+3. Release stream runtime resources used for current stream.
+
+After all above operations are successful, stream state is set to
+SDW_STATE_STRM_RELEASE.
+
+Future Enhancements
+===================
+1. Slave to Slave communication: Currently stream between Slaves is not
+supported. Master should be always part of the stream(s).
+
+2. Stream Linking for synchronized start: Currently multiple streams
+cannot be synchronously started together with single bank switch. This
+may require ASoC framework changes as well.
+
+Not Supported
+=============
+1. A single port with multiple channels supported cannot be used between
+two streams or across stream. For example a port with 4 channels cannot
+be used to handle 2 independent stereo streams even though it's possible
+in theory in SoundWire.
+
+2. Bandwidth allocation is done in contiguous slots for stream.
+Non-contiguous slots created due to bandwidth fragmentation are not
+taken care in bandwidth calculation.
--
1.7.9.5
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 <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
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
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
include/sound/sdw_bus.h | 7 ++
include/trace/events/sdw.h | 209 ++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw.c | 37 ++++++++
3 files changed, 253 insertions(+)
create mode 100644 include/trace/events/sdw.h
diff --git a/include/sound/sdw_bus.h b/include/sound/sdw_bus.h
index 3ea0c71..ad3586e 100644
--- a/include/sound/sdw_bus.h
+++ b/include/sound/sdw_bus.h
@@ -894,5 +894,12 @@ int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave,
*/
int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg,
unsigned int num);
+
+/* Function to enable tracing */
+void sdw_transfer_trace_reg(void);
+
+/* Function to disable tracing */
+void sdw_transfer_trace_unreg(void);
+
#endif /* _LINUX_SDW_BUS_H */
diff --git a/include/trace/events/sdw.h b/include/trace/events/sdw.h
new file mode 100644
index 0000000..447b86e
--- /dev/null
+++ b/include/trace/events/sdw.h
@@ -0,0 +1,209 @@
+/*
+ * sdw.h - SDW message transfer tracepoints.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM sdw
+
+#if !defined(_TRACE_SDW_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SDW_H
+
+#include <linux/mod_devicetable.h>
+#include <sound/sdw_bus.h>
+#include <linux/tracepoint.h>
+
+/*
+ * __sdw_transfer() write request
+ */
+TRACE_EVENT_FN(sdw_write,
+ TP_PROTO(const struct sdw_master *mstr,
+ const struct sdw_msg *msg,
+ int num),
+ TP_ARGS(mstr, msg, num),
+ TP_STRUCT__entry(
+ __field(int, master_nr)
+ __field(__u16, msg_nr)
+ __field(__u8, addr_page1)
+ __field(__u8, addr_page2)
+ __field(__u16, addr)
+ __field(__u16, flag)
+ __field(__u16, len)
+ __dynamic_array(__u8, buf, msg->len)),
+ TP_fast_assign(
+ __entry->master_nr = mstr->nr;
+ __entry->msg_nr = num;
+ __entry->addr = msg->addr;
+ __entry->flag = msg->r_w_flag;
+ __entry->len = msg->len;
+ __entry->addr_page1 = msg->addr_page1;
+ __entry->addr_page2 = msg->addr_page2;
+ memcpy(__get_dynamic_array(buf), msg->buf, msg->len);
+ ),
+ TP_printk("sdw-%d #%u a=%03x addr_page1=%04x addr_page2=%04x f=%04x l=%u [%*phD]",
+ __entry->master_nr,
+ __entry->msg_nr,
+ __entry->addr,
+ __entry->addr_page1,
+ __entry->addr_page2,
+ __entry->flag,
+ __entry->len,
+ __entry->len, __get_dynamic_array(buf)
+ ),
+ sdw_transfer_trace_reg,
+ sdw_transfer_trace_unreg);
+
+/*
+ * __sdw_transfer() read request
+ */
+TRACE_EVENT_FN(sdw_read,
+ TP_PROTO(const struct sdw_master *mstr, const struct sdw_msg *msg,
+ int num),
+ TP_ARGS(mstr, msg, num),
+ TP_STRUCT__entry(
+ __field(int, master_nr)
+ __field(__u16, msg_nr)
+ __field(__u8, addr_page1)
+ __field(__u8, addr_page2)
+ __field(__u16, addr)
+ __field(__u16, flag)
+ __field(__u16, len)
+ __dynamic_array(__u8, buf, msg->len)),
+ TP_fast_assign(
+ __entry->master_nr = mstr->nr;
+ __entry->msg_nr = num;
+ __entry->addr = msg->addr;
+ __entry->flag = msg->r_w_flag;
+ __entry->len = msg->len;
+ __entry->addr_page1 = msg->addr_page1;
+ __entry->addr_page2 = msg->addr_page2;
+ memcpy(__get_dynamic_array(buf), msg->buf, msg->len);
+ ),
+ TP_printk("sdw-%d #%u a=%03x addr_page1=%04x addr_page2=%04x f=%04x l=%u [%*phD]",
+ __entry->master_nr,
+ __entry->msg_nr,
+ __entry->addr,
+ __entry->addr_page1,
+ __entry->addr_page2,
+ __entry->flag,
+ __entry->len,
+ __entry->len, __get_dynamic_array(buf)
+ ),
+ sdw_transfer_trace_reg,
+ sdw_transfer_trace_unreg);
+
+/*
+ * __sdw_transfer() read reply
+ */
+TRACE_EVENT_FN(sdw_reply,
+ TP_PROTO(const struct sdw_master *mstr,
+ const struct sdw_msg *msg,
+ int num),
+ TP_ARGS(mstr, msg, num),
+ TP_STRUCT__entry(
+ __field(int, master_nr)
+ __field(__u16, msg_nr)
+ __field(__u16, addr)
+ __field(__u16, flag)
+ __field(__u16, len)
+ __dynamic_array(__u8, buf, msg->len)),
+ TP_fast_assign(
+ __entry->master_nr = mstr->nr;
+ __entry->msg_nr = num;
+ __entry->addr = msg->addr;
+ __entry->flag = msg->r_w_flag;
+ __entry->len = msg->len;
+ memcpy(__get_dynamic_array(buf), msg->buf, msg->len);
+ ),
+ TP_printk("sdw-%d #%u a=%03x f=%04x l=%u [%*phD]",
+ __entry->master_nr,
+ __entry->msg_nr,
+ __entry->addr,
+ __entry->flag,
+ __entry->len,
+ __entry->len, __get_dynamic_array(buf)
+ ),
+ sdw_transfer_trace_reg,
+ sdw_transfer_trace_unreg);
+
+/*
+ * __sdw_transfer() result
+ */
+TRACE_EVENT_FN(sdw_result,
+ TP_PROTO(const struct sdw_master *mstr, int num, int ret),
+ TP_ARGS(mstr, num, ret),
+ TP_STRUCT__entry(
+ __field(int, master_nr)
+ __field(__u16, nr_msgs)
+ __field(__s16, ret)
+ ),
+ TP_fast_assign(
+ __entry->master_nr = mstr->nr;
+ __entry->nr_msgs = num;
+ __entry->ret = ret;
+ ),
+ TP_printk("sdw-%d n=%u ret=%d",
+ __entry->master_nr,
+ __entry->nr_msgs,
+ __entry->ret
+ ),
+ sdw_transfer_trace_reg,
+ sdw_transfer_trace_unreg);
+
+#endif /* _TRACE_SDW_H */
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index 449060e..71d2550 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -70,6 +70,8 @@
#include "sdw_priv.h"
+#define CREATE_TRACE_POINTS
+#include <trace/events/sdw.h>
/*
* Global SoundWire core instance contains list of Masters registered, core
* lock and SoundWire stream tags.
@@ -335,6 +337,17 @@ static int sdw_match(struct device *dev, struct device_driver *driver)
.match = sdw_match,
.pm = &soundwire_pm,
};
+static struct static_key sdw_trace_msg = STATIC_KEY_INIT_FALSE;
+
+void sdw_transfer_trace_reg(void)
+{
+ static_key_slow_inc(&sdw_trace_msg);
+}
+
+void sdw_transfer_trace_unreg(void)
+{
+ static_key_slow_dec(&sdw_trace_msg);
+}
static int sdw_find_free_dev_num(struct sdw_master *mstr,
struct sdw_msg *msg)
@@ -601,6 +614,21 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num,
u8 prev_adr_pg1 = 0;
u8 prev_adr_pg2 = 0;
+ /*
+ * sdw_trace_msg gets enabled when trace point sdw_slave_transfer gets
+ * enabled. This is an efficient way of keeping the for-loop from
+ * being executed when not needed.
+ */
+ if (static_key_false(&sdw_trace_msg)) {
+ int j;
+
+ for (j = 0; j < num; j++)
+ if (msg[j].r_w_flag & SDW_MSG_FLAG_READ)
+ trace_sdw_read(mstr, &msg[j], j);
+ else
+ trace_sdw_write(mstr, &msg[j], j);
+ }
+
for (i = 0; i < num; i++) {
/* Reset timeout for every message */
@@ -665,6 +693,15 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num,
}
}
+ if (static_key_false(&sdw_trace_msg)) {
+ int j;
+
+ for (j = 0; j < msg->len; j++)
+ if (msg[j].r_w_flag & SDW_MSG_FLAG_READ)
+ trace_sdw_reply(mstr, &msg[j], j);
+ trace_sdw_result(mstr, j, ret);
+ }
+
if (!ret)
return i + 1;
--
1.7.9.5
This patch adds the SoundWire bus driver interfaces for following.
1. APIs to register/unregister SoundWire Master device and driver.
2. APIs to register/unregister SoundWire Slave driver.
3. API to Read/Write Slave registers.
4. API for Master driver to update Slave status to bus driver.
5. APIs for Master driver to prepare and initiate clock stop.
6. APIs for SoundWire stream configuration.
7. APIs to prepare, enable, deprepare and disable SoundWire stream.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
MAINTAINERS | 12 +
include/sound/sdw_bus.h | 898 ++++++++++++++++++++++++++++++++++++++++++++
include/sound/sdw_master.h | 627 +++++++++++++++++++++++++++++++
include/sound/sdw_slave.h | 563 +++++++++++++++++++++++++++
4 files changed, 2100 insertions(+)
create mode 100644 include/sound/sdw_bus.h
create mode 100644 include/sound/sdw_master.h
create mode 100644 include/sound/sdw_slave.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 1cd38a7..73e85a8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11334,6 +11334,18 @@ F: include/sound/dmaengine_pcm.h
F: sound/core/pcm_dmaengine.c
F: sound/soc/soc-generic-dmaengine-pcm.c
+SOUND - SOUNDWIRE
+M: Hardik Shah <[email protected]>
+M: Sanyog Kale <[email protected]>
+R: Pierre-Louis Bossart <[email protected]>
+L: [email protected] (moderated for non-subscribers)
+S: Supported
+F: Documentation/sound/sdw/
+F: include/sound/sdw_*
+F: include/sound/sdw/
+F: sound/sdw/
+F: drivers/base/regmap-sdw.c
+
SP2 MEDIA DRIVER
M: Olli Salonen <[email protected]>
L: [email protected]
diff --git a/include/sound/sdw_bus.h b/include/sound/sdw_bus.h
new file mode 100644
index 0000000..3ea0c71
--- /dev/null
+++ b/include/sound/sdw_bus.h
@@ -0,0 +1,898 @@
+/*
+ * sdw_bus.h - Definition for SoundWire bus interface. This file has all
+ * the SoundWire bus interfaces common to both Master and Slave
+ * interfaces.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ *
+ * This header file refers to the MIPI SoundWire 1.1 Spec.
+ * [1.1] https://members.mipi.org/wg/All-Members/document (accessible to
+ * MIPI members).
+ *
+ * The comments in the file try to follow the same conventions with a
+ * capital letter for all standard definitions such as Master, Slave,
+ * Data Port, etc. When possible, the constant numeric values are kept
+ * the same as in the MIPI specifications. All of the constants reflect
+ * the MIPI SoundWire definitions and are not vendor specific. Some of
+ * the constants are for bus driver usage and not MIPI defined. These
+ * are specified at appropriate place in this file.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_BUS_H
+#define _LINUX_SDW_BUS_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+
+/* Broadcast address for Slaves. */
+#define SDW_SLAVE_BDCAST_ADDR 15 /* As per MIPI 1.1 Spec */
+
+/* Total number of valid rows possible for SoundWire frame. */
+#define MAX_NUM_ROWS 23 /* As per MIPI 1.1 Spec */
+
+/* Total number of valid columns possible for SoundWire frame. */
+#define MAX_NUM_COLS 8 /* As per MIPI 1.1 Spec */
+
+/* Number of control bits in SoundWire frame. */
+#define SDW_BUS_CONTROL_BITS 48 /* As per MIPI 1.1 Spec */
+
+/**
+ * Number of dev_id registers. This represents the device ID of slaves
+ * which includes manufacturer ID, part ID and unique device ID.
+ */
+#define SDW_NUM_DEV_ID_REGISTERS 6 /* As per MIPI 1.1 Spec */
+
+#define SDW_MAX_DEVICES 11 /* Max. Slave devices on bus */
+
+#define SDW_SLAVE_ENUM_ADDR 0 /* Enumeration addr. for Slaves */
+
+#define SDW_PORT_SINK 0x0 /* Sink Port */
+
+#define SDW_PORT_SOURCE 0x1 /* Source Port */
+
+#define SDW_MAX_PORT_DIRECTIONS 0x2 /* Source or Sink */
+
+#define SDW_MSG_FLAG_READ 0x0 /* Read Flag for MSG and BRA transfer */
+
+#define SDW_MSG_FLAG_WRITE 0x1 /* Write Flag MSG and BRA transfer */
+
+/* Total number of Row and Column combination possible for SoundWire frame. */
+#define MAX_NUM_ROW_COLS (MAX_NUM_ROWS * MAX_NUM_COLS)
+
+/**
+ * The following set of constants for flow control, Port type and transport
+ * packing are bit masks since devices can expose combinations of
+ * capabilities.
+ */
+
+/* Port flow mask for ISOCHRONOUS mode. */
+#define SDW_PORT_FLOW_MODE_ISOCH 0x1
+
+
+/* Port flow mask for TX_CONTROLLED mode. */
+#define SDW_PORT_FLOW_MODE_TX_CNTRL 0x2
+
+/* Port flow mask for RX_CONTROLLED mode. */
+#define SDW_PORT_FLOW_MODE_RX_CNTRL 0x4
+
+/* Port flow mask for ASYNCHRONOUS mode. */
+#define SDW_PORT_FLOW_MODE_ASYNC 0x8
+
+/* Sample packaging mask for Block per Port mode. */
+#define SDW_PORT_BLK_PER_PORT 0x1
+
+/* Sample packaging mask for Block per Channel mode. */
+#define SDW_PORT_BLK_PER_CH 0x2
+
+
+/* Port encoding mask definitions are part of SoundWire DisCo Spec */
+
+/* Mask to specify data encoding supported by Port, type 2's complement. */
+#define SDW_PORT_ENC_TWOS_CMPLMNT 0x1
+
+/* Mask to specify data encoding supported by Port, type sign magnitude. */
+#define SDW_PORT_ENC_SIGN_MAGNITUDE 0x2
+
+/* Mask to specify data encoding supported by Port, type IEEE 32 float. */
+#define SDW_PORT_ENC_IEEE_32_FLOAT 0x4
+
+/* sdw_driver_type: Driver type on SoundWire bus. */
+enum sdw_driver_type {
+ SDW_DRIVER_TYPE_MASTER = 0,
+ SDW_DRIVER_TYPE_SLAVE = 1,
+};
+
+/**
+ * sdw_command_response: Response to the command. Command responses are
+ * part of SoundWire Spec. The values in the enum is different than
+ * actual Spec, since the response in the Spec is defined based on
+ * the combination of ACK/NAK bits.
+ */
+enum sdw_command_response {
+ SDW_CMD_OK = 0,
+ SDW_CMD_IGNORED = 1,
+ SDW_CMD_FAILED = 2,
+};
+
+/**
+ * sdw_dpn_type: Data Port type
+ *
+ * @SDW_DP_TYPE_FULL: Full Data Port supported.
+ * @SDW_DP_TYPE_SIMPLE: Simplified Data Port defined in SoundWire 1.1
+ * Spec. DPN_SampleCtrl2, DPN_OffsetCtrl2, DPN_HCtrl and
+ * DPN_BlockCtrl3 are not implemented for Simplified Data Port.
+ * @SDW_DP_TYPE_REDUCED: Reduced SoundWire Data Port, defined in SoundWire
+ * 1.1 Spec. DPN_SampleCtrl2, DPN_HCtrl are not implemented for
+ * Reduced Data Port.
+ */
+enum sdw_dpn_type {
+ SDW_DP_TYPE_FULL = 0,
+ SDW_DP_TYPE_SIMPLE = 1,
+ SDW_DP_TYPE_REDUCED = 2,
+};
+
+/* sdw_dpn_grouping: Maximum block group count supported. */
+enum sdw_dpn_grouping {
+ SDW_BLK_GRP_CNT_1 = 0,
+ SDW_BLK_GRP_CNT_2 = 1,
+ SDW_BLK_GRP_CNT_3 = 2,
+ SDW_BLK_GRP_CNT_4 = 3,
+};
+
+/**
+ * sdw_prep_ch_behavior: Specifies the dependencies between channel prepare
+ * sequence and bus clock configuration. This property is not required
+ * for ports implementing a simplified ChannelPrepare State Machine
+ * (SCPSM) This is not part of SoundWire Spec, but defined in SoundWire
+ * DisCo properties.
+ *
+ * @SDW_CHAN_PREP_RATE_ANY: Channel prepare can happen at any bus clock
+ * rate.
+ * @SDW_CHAN_PREP_RATE_COMPAT: Channel prepare sequence needs to happen
+ * after bus clock is changed to a frequency supported by this mode.
+ *
+ * TODO: This flag is not used, prepare is done after bus clock is
+ * changed. Bus driver will be extended later to use this.
+ */
+enum sdw_prep_ch_behavior {
+ SDW_CHAN_PREP_RATE_ANY = 0,
+ SDW_CHAN_PREP_RATE_COMPAT = 1,
+};
+
+/**
+ * sdw_slave_status: Slave status reported by PING commands.
+ *
+ * @SDW_SLAVE_STAT_NOT_PRESENT: Slave is not present.
+ * @SDW_SLAVE_STAT_ATTACHED_OK: Slave is attached to the bus.
+ * @SDW_SLAVE_STAT_ALERT: Some alert condition on the Slave. An alert status
+ * will be reported by Slave, if the relevant Slave interrupt mask
+ * register is programmed. By default, on reset interrupt masks are not
+ * enabled.
+ * @SDW_SLAVE_STAT_RESERVED: Reserved.
+ */
+enum sdw_slave_status {
+ SDW_SLAVE_STAT_NOT_PRESENT = 0,
+ SDW_SLAVE_STAT_ATTACHED_OK = 1,
+ SDW_SLAVE_STAT_ALERT = 2,
+ SDW_SLAVE_STAT_RESERVED = 3,
+};
+
+/**
+ * sdw_stream_type: Stream type used by bus driver for the bandwidth
+ * allocation. This is not SoundWire Spec definition but required by
+ * implementations that need to route PDM data through decimator
+ * hardware.
+ */
+enum sdw_stream_type {
+ SDW_STREAM_PCM = 0,
+ SDW_STREAM_PDM = 1,
+};
+
+/**
+ * sdw_ch_prepare_mode: Channel prepare state machine.
+ *
+ * @SDW_CP_MODE_SIMPLE: Simplified channel prepare state machine
+ * @SDW_CP_MODE_NORMAL: Normal channel prepare state machine.
+ */
+enum sdw_ch_prepare_mode {
+ SDW_CP_MODE_SIMPLE = 0,
+ SDW_CP_MODE_NORMAL = 1,
+};
+
+/**
+ * sdw_clk_stop_mode: Clock Stop mode.
+ *
+ * @SDW_CLOCK_STOP_MODE_0: Clock Stop mode 0. ClockStopMode0 is used when
+ * the Slave is capable of continuing operation seamlessly when the
+ * Clock is restarted, as though time had stood still while the Clock
+ * was stopped.
+ *
+ * @SDW_CLOCK_STOP_MODE_1: Clock Stop mode 1. ClockStopMode1 is used when
+ * the Slave might have entered a deeper power-saving mode that does
+ * not retain state while the Clock is stopped, so is not capable of
+ * continuing operation seamlessly when the Clock restarts.
+ */
+enum sdw_clk_stop_mode {
+ SDW_CLOCK_STOP_MODE_0 = 0,
+ SDW_CLOCK_STOP_MODE_1 = 1,
+};
+
+/**
+ * sdw_data_direction: Data direction w.r.t Port. e.g for playback between
+ * the Master and Slave, where Slave is codec, data direction for the
+ * Master Port will be OUT, since its transmitting the data, while for
+ * the Slave (codec) it will be IN, since its receiving the data. This
+ * is not SoundWire Spec definition, it is used by bus driver for
+ * stream configuration. For all the sink Ports data direction is
+ * always IN, while for all the source Ports data direction is always
+ * OUT.
+ *
+ * @SDW_DATA_DIR_IN: Data is input to Port.
+ * @SDW_DATA_DIR_OUT: Data is output from Port.
+ */
+enum sdw_data_direction {
+ SDW_DATA_DIR_IN = 0,
+ SDW_DATA_DIR_OUT = 1,
+};
+
+/**
+ * sdw_port_data_mode: Data Port mode. It can be normal mode where audio
+ * data is received and transmitted, or any of the 3 different test
+ * modes defined by the SoundWire Spec 1.1. Test modes are normally
+ * used for testing at component and system level.
+ *
+ * @SDW_PORT_DATA_MODE_NORMAL: Normal data mode where audio data is received
+ * and transmitted.
+ *
+ * @SDW_PORT_DATA_MODE_STATIC_1: This simple test mode uses static value of
+ * logic 1. The encoding will result in signal transitions at every
+ * bitslot owned by this Port.
+ *
+ * @SDW_PORT_DATA_MODE_STATIC_0: This simple test mode uses static value of
+ * logic 0. The encoding will result in no signal transitions (the
+ * voltage level prior to the bitSlots owned by the Port will be
+ * maintained by the bus holder). Here enum says as _0 but enum value
+ * is 2. This is confusing while debugging, but kept to match the Spec
+ * values.
+ *
+ * @SDW_PORT_DATA_MODE_PRBS: This test mode uses a PRBS generator to produce
+ * a pseudo random data pattern that is transported from source Data
+ * Port to sink Data Port where similar pattern generators check the
+ * received pattern.
+ */
+enum sdw_port_data_mode {
+ SDW_PORT_DATA_MODE_NORMAL = 0,
+ SDW_PORT_DATA_MODE_STATIC_1 = 1,
+ SDW_PORT_DATA_MODE_STATIC_0 = 2,
+ SDW_PORT_DATA_MODE_PRBS = 3,
+};
+
+/**
+ * sdw_port_prep_ops: Prepare operations for Master Data Ports
+ *
+ * @SDW_OPS_PORT_PRE_PREP: Pre prepare operation for the Ports.
+ * @SDW_OPS_PORT_PREP: Prepare operation for the Ports.
+ * @SDW_OPS_PORT_POST_PREP: Post prepare operation for the Ports.
+ */
+enum sdw_port_prep_ops {
+ SDW_OPS_PORT_PRE_PREP = 0,
+ SDW_OPS_PORT_PREP = 1,
+ SDW_OPS_PORT_POST_PREP = 2,
+};
+
+struct sdw_master;
+struct sdw_slave;
+
+/**
+ * sdw_port_aud_mode_prop: Audio properties for the Port mode. The
+ * SoundWire specification only requires command and control to be
+ * supported for all bus frequencies. Audio transport may be restricted
+ * to specific modes (bus frequencies, sample rate, etc). The following
+ * capabilities are defined in the SoundWire DisCo Spec and provide
+ * additional information needed to configure the bus. All frequencies
+ * and sampling rates are in Hz.
+ *
+ * @max_bus_freq: Maximum frequency Port can support for the clock. The use
+ * of max_ and min_ frequency requires num_bus_freq_cfgs to
+ * be zero
+ *
+ * @min_bus_freq: Minimum frequency Port can support for the clock.
+ * @num_bus_freq_cfgs: Array size for the frequencies supported by Port.
+ * @clk_freq_buf: Buffer of frequencies supported by the Port.
+ * @max_sample_rate: Maximum sampling rate supported by this Port. Use of
+ * max_ and min_ requires num_sample_rate_cfgs to be zero.
+ *
+ * @min_sample_rate: Minimum sampling rate supported by Port.
+ * @num_sample_rate_cfgs: Array size for the number of sampling freq.
+ * supported by Port.
+ *
+ * @sample_rate_buf: Array for the sampling freq configs supported.
+ * @ch_prepare_behavior: Specifies the dependencies between Channel Prepare
+ * Prepare sequence and Bus clock configuration.
+ * 0: Channel prepare can happen at any bus clock rate
+ * 1: Channel prepare sequence shall happen only after Bus clock is
+ * changed to a frequency supported by this mode or compatible modes
+ * described by the next field. This may be required, e.g. when the
+ * Slave internal audio clocks are derived from the bus clock. This
+ * property applies to all channels within this port. This property may
+ * be omitted for ports implementing a Simplified ChannelPrepare State
+ * Machine (SCPSM). Currently bus driver ignores this fields and always
+ * prepares the port after Bus clock is changed. This will be enhanced
+ * in future to optimize the prepare time.
+ *
+ * @glitchless_transitions_mask: Glitchless transition mask from one mode to
+ * other mode. Each bit refers to a mode number that can be reached
+ * from current mode without audible issues.
+ */
+struct sdw_port_aud_mode_prop {
+ unsigned int max_bus_freq;
+ unsigned int min_bus_freq;
+ unsigned int num_bus_freq_cfgs;
+ unsigned int *clk_freq_buf;
+ unsigned int max_sample_rate;
+ unsigned int min_sample_rate;
+ unsigned int num_sample_rate_cfgs;
+ unsigned int *sample_rate_buf;
+ enum sdw_prep_ch_behavior ch_prepare_behavior;
+ unsigned int glitchless_transitions_mask;
+
+};
+
+/**
+ * sdw_slave_addr: Structure representing the read-only unique device ID
+ * and the SoundWire logical device number (assigned by bus driver).
+ *
+ * @slave: Slave device reference for easy access, to which this address is
+ * assigned.
+ *
+ * @dev_id: 6-byte device ID of the Slave
+ * @dev_num: Slave address, "Device Number" field of the SCP_DevNumber
+ * registers. This gets programmed into the register. Currently
+ * Group_Id field is not used.
+ *
+ * @assigned: Flag if the logical address(aka Slave device number) is
+ * assigned to some Slave or is free.
+ *
+ * @status: mirrors Slave status reported by PING commands.
+ */
+struct sdw_slave_addr {
+ struct sdw_slave *slave;
+ unsigned int dev_id[SDW_NUM_DEV_ID_REGISTERS];
+ unsigned int dev_num;
+ bool assigned;
+ enum sdw_slave_status status;
+};
+
+/**
+ * sdw_dpn_caps: Capabilities of the Data Port, other than Data Port 0 for
+ * SoundWire Master and Slave. This follows the definitions in the
+ * SoundWire DisCo Spec. All fields are listed in order of the
+ * SoundWire DisCo Spec. Name of the fields are altered to follow Linux
+ * convention. All the fields which are required by bus driver but not
+ * present in DisCo Spec are listed at the end of the structure. There
+ * is a common dpn capability structure for Master and Slave ports.
+ * Some of the fields doesn't make sense for Master which are called
+ * out explicitly in fields description. Port properties can be
+ * different for same Port, based on Port is configured as source or
+ * sink Port.
+ *
+ * @max_bps: Maximum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @min_bps: Minimum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @num_bps: Length of supported bits per sample buffer. The use of max_ and
+ * min_ bps requires num_bps to be zero
+ *
+ * @bps_buf: Array of the bits per sample buffer.
+ * @type: Type of Data Port. Simplified or Normal Data Port.
+ * @grouping: Max Block group count supported for this Port.
+ * @prepare_ch: Channel prepare scheme. Simplified channel prepare or normal
+ * channel prepare. All Channels in the Port are assumed to share the
+ * same scheme. For Masters this should be always simplified channel
+ * prepare.
+ *
+ * @ch_prep_timeout: A port-specific timeout value in milliseconds. This
+ * value indicates the worst-case latency of the channel prepare state
+ * machine transitions. After receiving a successful channel
+ * prepare/de-prepare command, the slave should complete the operation
+ * before the expiration of the timeout. Software stack may poll for
+ * completion periodically or wait for this timeout value. If the
+ * requested action has not completed at the end of this timeout,
+ * software shall interpret this as an error condition. This is don't
+ * care for Master.
+ *
+ * @imp_def_intr_mask: Implementation defined interrupt mask. Master should
+ * always set this to 0. Bus driver doesn't use this for Master.
+ *
+ * @min_ch_cnt: Minimum number of channels supported.
+ * @max_ch_cnt: Maximum number of channels supported.
+ * @num_ch_cnt: Buffer length for the channels supported. The use of max_ and
+ * min_ ch_num requires num_ch_cnt to be zero.
+ *
+ * @ch_cnt_buf: Array of the channel count.
+ * @port_flow_mode_mask: Transport flow modes supported by Port.
+ * @max_async_buffer: Number of samples this port can buffer in asynchronous
+ * modes. The SoundWire specification only requires minimal buffering,
+ * this property is only required if the Slave implements a buffer that
+ * exceeds the SoundWire requirements. Bus driver currently doesn't
+ * support this.
+ *
+ * @blk_pack_mode: Block packing mode mask.
+ * @port_encoding: Port Data encoding type mask.
+ * @num_audio_modes: Number of audio modes supported by Port. This should be
+ * 0 for Master. Bus driver doesn't use this for Master.
+ *
+ * @mode_properties: Port audio mode properties buffer of size
+ * num_audio_modes. This is not used for Master. This needs to be
+ * populated only for Slave.
+ *
+ * @port_number: Port number.
+ */
+struct sdw_dpn_caps {
+ unsigned int max_bps;
+ unsigned int min_bps;
+ unsigned int num_bps;
+ unsigned int *bps_buf;
+ enum sdw_dpn_type type;
+ enum sdw_dpn_grouping grouping;
+ enum sdw_ch_prepare_mode prepare_ch;
+ unsigned int ch_prep_timeout;
+ u8 imp_def_intr_mask;
+ unsigned int min_ch_cnt;
+ unsigned int max_ch_cnt;
+ unsigned int num_ch_cnt;
+ unsigned int *ch_cnt_buf;
+ unsigned int port_flow_mode_mask;
+ unsigned int blk_pack_mode;
+ unsigned int port_encoding;
+ unsigned int num_audio_modes;
+ struct sdw_port_aud_mode_prop *mode_properties;
+ unsigned int port_number;
+};
+
+/**
+ * sdw_prepare_ch: Prepare/De-prepare the Data Port channel. This is similar
+ * to the prepare API of Alsa, where most of the hardware interfaces
+ * are prepared for the playback/capture to start. All the parameters
+ * are known to the hardware at this point for starting
+ * playback/capture next.
+ *
+ * @num: Port number.
+ * @ch_mask: Channels to be prepared/deprepared specified by ch_mask.
+ * @prepare: Prepare/de-prepare channel, true (prepare) or false
+ * (de-prepare).
+ *
+ * @bank: Register bank, which bank Slave/Master driver should program for
+ * implementation defined registers. This is the inverted value of the
+ * current bank.
+ */
+struct sdw_prepare_ch {
+ unsigned int num;
+ unsigned int ch_mask;
+ bool prepare;
+ unsigned int bank;
+};
+
+/**
+ * sdw_bus_params: Bus params for the Slave/Master to be ready for next
+ * bus changes.
+ *
+ * @clk_freq: Clock frequency for the bus in Hz.
+ * @num_rows: Number of rows in new frame to be enabled
+ * @num_cols: Number of columns in new frame to be enabled. The MIPI
+ * specification lists restrictions on values for rows and cols that
+ * will be enforced by the bus driver.
+ *
+ * @bank: Register bank, which bank Slave/Master driver should program for
+ * implementation defined registers. This is the inverted value of the
+ * current bank. The implementation of this bus driver follows the
+ * recommendations of the MIPI specification and will never modify
+ * registers in the current bank to avoid audible issues and bus
+ * conflicts. Bus reconfigurations are always handled through a
+ * synchronized bank switch mechanism. The use of Port-specific banks
+ * is not supported for now.
+ */
+struct sdw_bus_params {
+ unsigned int clk_freq;
+ u16 num_rows;
+ unsigned int num_cols;
+ unsigned int bank;
+};
+
+/**
+ * sdw_bra_block: Data block to be sent/received using SoundWire Bulk
+ * Register Access Protocol (BRA).
+ *
+ * @dev_num: Slave address, "Device Number" field of the SCP_DevNumber
+ * registers. This gets programmed into the register. Currently
+ * Group_Id field is not used.
+ *
+ * @r_w_flag: Read/Write operation. Read(0) or Write(1) based on above #def
+ * @num_bytes: Number of Data bytes to be transferred.
+ * @reg_offset: Register offset for the first byte of the read/write
+ * transaction.
+ *
+ * @values: Array containing value for write operation and to be filled for
+ * read operation.
+ */
+struct sdw_bra_block {
+ unsigned int dev_num;
+ u8 r_w_flag;
+ unsigned int num_bytes;
+ unsigned int reg_offset;
+ u8 *values;
+};
+
+/**
+ * sdw_msg: Message to be sent on bus. This is similar to i2c_msg on I2C
+ * bus. Message is sent from the bus driver to the Slave device. Slave
+ * driver can also initiate transfer of the message to program
+ * implementation defined registers. Message is formatted and
+ * transmitted on to the bus by Master interface in hardware-specific
+ * way. Bus driver initiates the Master interface callback to transmit
+ * the message on bus.
+ *
+ * @addr: Address of the register to be read.
+ * @len: Length of the message to be transferred. Successive increment in
+ * the register address for every message. Bus driver sets this field
+ * to 0, if transfer fails. E.g. If calling function sets len = 2 for
+ * two byte transfer, and if transfer fails for 2nd byte, "len" field
+ * is set to 0 by bus driver.
+ *
+ * @dev_num: Slave address, "Device Number" field of the SCP_DevNumber
+ * registers. Currently Group_Id field is not used.
+ *
+ * @addr_page1: SCP address page 1 Slave register. 32-bit address of the
+ * SoundWire Slave register is constructed using 16-bit lower bit
+ * address as part of command, 8-bit as part of this register and MSB
+ * 8-bits as a part of addr_page2 register. This registers needs to be
+ * programmed first for any transfer with register address greater than
+ * 0xFFFF.
+ *
+ * @addr_page2: SCP address page 2
+ * @r_w_flag: Message operation to be read or write.
+ * @buf: Buf to be written or read from the register.
+ * @xmit_on_ssp: Send message at SSP (Stream Synchronization Point). It
+ * should be used when a command needs to be issued during the next
+ * SSP. For all normal reads/writes this should be zero. This will be
+ * used for broadcast write to SCP_FrameCtrl register by bus driver
+ * only. Slave driver should always set xmit_on_ssp to 0. This flag
+ * applies only if len = 1 in message. Multiple length messages cannot
+ * be transmitted on SSP, for this caller needs to create multiple
+ * messages.
+ *
+ */
+struct sdw_msg {
+ u16 addr;
+ u16 len;
+ u8 dev_num;
+ u8 addr_page1;
+ u8 addr_page2;
+ u8 r_w_flag;
+ u8 *buf;
+ bool xmit_on_ssp;
+};
+
+/**
+ * sdw_stream_config: Stream configuration set by the Master(s) and Slave(s)
+ * via sdw_config_stream API. Both Master(s) and Slave(s) needs to
+ * provide stream configuration because stream configuration for
+ * Master(s) and Slave(s) could be different for a given stream. E.g.
+ * in case of Stream associated with Single Master and a Slave, both
+ * Master and Slave shall have same "sdw_stream_config" except the
+ * "direction" (assuming Master and Slave supports stereo channels per
+ * port). In case of stereo stream attached to single Master and two
+ * Slaves, "channel_count" for each Slave shall be 1 and for the Master
+ * it shall be 2, (assuming Master supports stereo channels per port
+ * and Slave supports mono channel per port).
+ *
+ * @frame_rate: Audio frame rate of the stream (not the bus frame rate
+ * defining command bandwidth).
+ *
+ * @channel_count: Channel count of the stream.
+ * @bps: Number of bits per audio sample.
+ * @direction: Data direction w.r.t Port. This is used by bus driver to
+ * identify source and sink of the stream.
+ *
+ * @type: Stream type PCM or PDM. This is internally used by bus driver for
+ * bandwidth allocation.
+ */
+struct sdw_stream_config {
+ unsigned int frame_rate;
+ unsigned int channel_count;
+ unsigned int bps;
+ enum sdw_data_direction direction;
+ enum sdw_stream_type type;
+};
+
+/**
+ * sdw_port_config: SoundWire Port configuration for a given port (Master
+ * and Slave) associated with the stream.
+ *
+ * @num: Port number to be configured
+ * @ch_mask: Which channels needs to be enabled for this Port.
+ */
+struct sdw_port_config {
+ unsigned int num;
+ unsigned int ch_mask;
+};
+
+/**
+ * sdw_ports_config: Ports configuration set by the Master(s) and
+ * Slave(s) via "sdw_config_ports" API. Both Master(s) and Slave(s)
+ * needs to provide port configuration because port configuration for
+ * Master(s) and Slave(s) could be different for a given stream. E.g.
+ * in case of Stream associated with Single Master and a Slave, both
+ * Master and Slave shall have same "sdw_ports_config" except the
+ * "num" inside "sdw_port_config"(assuming Master and Slave
+ * supports stereo channels per port). In case of stereo stream
+ * attached to single Master and two Slaves, "num_ports" for each Slave
+ * shall be 1 and for the Master it shall be 2 for "sdw_ports_config",
+ * "ch_mask" for Slave which handles left channel shall be 0x1, and for
+ * Slave which handles right channel shall be 0x2. "num" should
+ * be based on port allocated by Master and Slaves(assuming Master
+ * supports stereo channels per port and Slave supports mono channel
+ * per port).
+ *
+ * @num_ports: Number of ports to be configured.
+ * @port_cfg: Port configuration for each Port.
+ */
+struct sdw_ports_config {
+ unsigned int num_ports;
+ struct sdw_port_config *port_config;
+};
+
+/**
+ * sdw_slave_status: Status of all the SoundWire Slave devices.
+ *
+ * @status: Array of status of SoundWire Slave devices. There can be 11
+ * Slaves with non-zero device number, before enumeration all Slaves
+ * reports as device0.
+ */
+struct sdw_status {
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
+};
+
+/**
+ * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is
+ * a unique identifier for each SoundWire stream across all SoundWire
+ * bus instances. Stream tag is a software concept defined by bus
+ * driver for stream management and not by MIPI SoundWire Spec. Each
+ * SoundWire Stream is individually configured and controlled using the
+ * stream tag. Multiple Master(s) and Slave(s) associated with the
+ * stream, uses stream tag as an identifier. All the operations on the
+ * stream e.g. stream configuration, port configuration, prepare and
+ * enable of the ports are done based on stream tag. This API shall be
+ * called once per SoundWire stream either by the Master or Slave
+ * associated with the stream.
+ *
+ * @stream_tag: Stream tag returned by bus driver.
+ */
+int snd_sdw_alloc_stream_tag(unsigned int *stream_tag);
+
+/**
+ * snd_sdw_release_stream_tag: Free the already assigned stream tag.
+ * Reverses effect of "sdw_alloc_stream_tag"
+ *
+ * @stream_tag: Stream tag to be freed.
+ */
+void snd_sdw_release_stream_tag(unsigned int stream_tag);
+
+/**
+ * snd_sdw_config_stream: Configures the SoundWire stream. All the Master(s)
+ * and Slave(s) associated with the stream calls this API with
+ * "sdw_stream_config". This API configures SoundWire stream based on
+ * "sdw_stream_config" provided by each Master(s) and Slave(s)
+ * associated with the stream. Master calls this function with Slave
+ * handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle.
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_config_stream(struct sdw_master *mstr, struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ unsigned int stream_tag);
+
+/**
+ * snd_sdw_release_stream: De-associates Master(s) and Slave(s) from stream.
+ * Reverse effect of the sdw_config_stream. Master calls this with
+ * Slave handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle,
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_release_stream(struct sdw_master *mstr, struct sdw_slave *slave,
+ unsigned int stream_tag);
+
+/**
+ * snd_sdw_config_ports: Configures Master or Slave Port(s) associated with
+ * the stream. All the Master(s) and Slave(s) associated with the
+ * stream calls this API with "sdw_ports_config". Master calls this
+ * function with Slave handle as NULL, Slave calls this with Master
+ * handle as NULL.
+ *
+ * @mstr: Master handle where the Slave is connected.
+ * @slave: Slave handle.
+ * @ports_config: Port configuration for each Port of SoundWire Slave.
+ * @stream_tag: Stream tag, where this Port is connected.
+ */
+int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave,
+ struct sdw_ports_config *ports_config,
+ unsigned int stream_tag);
+
+/**
+ * snd_sdw_prepare_and_enable: Prepare and enable all the ports of all the
+ * Master(s) and Slave(s) associated with this stream tag. Following
+ * will be done as part of prepare operation.
+ * 1. Bus parameters such as bandwidth, frame shape, clock frequency,
+ * SSP interval are computed based on current stream as well as already
+ * active streams on bus. Re-computation is required to accommodate
+ * current stream on the bus.
+ * 2. Transport parameters of all Master and Slave ports are computed
+ * for the current as well as already active stream based on above
+ * calculated frame shape and clock frequency.
+ * 3. Computed bus and transport parameters are programmed in Master
+ * and Slave registers. The banked registers programming is done on the
+ * alternate bank (bank currently unused). Port channels are enabled
+ * for the already active streams on the alternate bank (bank currently
+ * unused). This is done in order to not to disrupt already active
+ * stream.
+ * 4. Once all the new values are programmed, switch is made to
+ * alternate bank. Once switch is successful, the port channels enabled
+ * on previous bank for already active streams are disabled.
+ * 5. Ports of Master and Slave for new stream are prepared.
+ *
+ * Following will be done as part of enable operation.
+ * 1. All the values computed in SDW_STATE_STRM_PREPARE state are
+ * programmed in alternate bank (bank currently unused). It includes
+ * programming of already active streams as well.
+ * 2. All the Master and Slave port channels for the new stream are
+ * enabled on alternate bank (bank currently unused).
+ * 3. Once all the new values are programmed, switch is made on the
+ * alternate bank. Once the switch is successful, the port channels
+ * enabled on previous bank for already active streams are disabled.
+ *
+ * This shall be called either by Master or Slave, which is responsible
+ * for doing data transfer between SoundWire link and the system
+ * memory.
+ *
+ * @stream_tag: Audio stream to be enabled. Each stream has unique
+ * stream_tag. All the channels of all the ports of Slave(s) and
+ * Master(s) attached to this stream will be prepared and enabled
+ * simultaneously with bank switch.
+ */
+int snd_sdw_prepare_and_enable(unsigned int stream_tag);
+
+/**
+ * snd_sdw_disable_and_deprepare: Disable and de-prepare all the ports of
+ * all the Master(s) and Slave(s) associated with stream tag. Following
+ * will be done as part of disable operation.
+ * 1. Disable for Master and Slave ports channels is performed on
+ * alternate bank (bank currently unused) registers for current stream.
+ * 2. All the current configuration of bus and Master and Slave ports
+ * are programmed into alternate bank (bank currently unused). It
+ * includes programming of already active streams port channels on
+ * alternate bank (bank currently unused).
+ * 3. Switch is made on new bank. Once the switch is successful, the
+ * port channels of current stream are disabled. All the port channels
+ * enabled on previous bank for active stream are disabled.
+ *
+ * Following will be done as part of de-prepare operation.
+ * 1. Check the bandwidth required per Master. If its zero, de-prepare
+ * current stream and move stream state SDW_STATE_STRM_UNPREPARE, rest
+ * of the steps are not required. If bandwidth required per Master is
+ * non zero that means some more streams are running on Master and
+ * continue with next step.
+ * 2. Bus parameters and transport parameters are computed for the
+ * streams active on the given Master.
+ * 3. All the computed values for active stream are programmed into
+ * alternate bank (bank currently unused) in Master and Slave registers
+ * including already active streams port channels on alternate bank
+ * (bank currently unused).
+ * 4. Switch is made to alternate bank where all the values for active
+ * stream were programmed. On successful switch of bank, all the port
+ * channels enabled on previous bank for active stream are disabled.
+ * 5. De-prepare ports of the Master and Slave associated with current
+ * stream.
+ *
+ * This shall be called either by Master or Slave, which is
+ * responsible for doing data transfer between SoundWire link and the
+ * system memory.
+ * Note: Both disable and de-prepare operations are performed in single
+ * call. De-prepare operation can be deferred for some specific timeout
+ * value after disable operation, to avoid bus re-configurations
+ * between short play and pause periods.
+ *
+ * @stream_tag: Audio stream to be disabled. Each stream has unique
+ * stream_tag. All the channels of all the ports of Slave(s) and
+ * Master(s) attached to this stream will be disabled and de-prepared
+ * simultaneously with bank switch.
+ */
+int snd_sdw_disable_and_deprepare(unsigned int stream_tag);
+
+/**
+ * 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);
+#endif /* _LINUX_SDW_BUS_H */
+
diff --git a/include/sound/sdw_master.h b/include/sound/sdw_master.h
new file mode 100644
index 0000000..e709c51
--- /dev/null
+++ b/include/sound/sdw_master.h
@@ -0,0 +1,627 @@
+/*
+ * sdw_master.h - Definition for SoundWire Master interface. This file
+ * has all the definitions which are required for only Soundwire
+ * Master driver. Some interfaces are common for both Slave and
+ * Master driver. Please refer to sdw_bus.h for common interfaces.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ *
+ * This header file refers to the MIPI SoundWire 1.1 Spec.
+ * [1.1] https://members.mipi.org/wg/All-Members/document (accessible to
+ * MIPI members).
+ *
+ * The comments in the file try to follow the same conventions with a
+ * capital letter for all standard definitions such as Master, Slave,
+ * Data Port, etc. When possible, the constant numeric values are kept
+ * the same as in the MIPI specifications. All of the constants reflect
+ * the MIPI SoundWire definitions and are not vendor specific. Some of
+ * the constants are for bus driver usage and not MIPI defined. These
+ * are specified at appropriate place in this file.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_MASTER_H
+#define _LINUX_SDW_MASTER_H
+
+#include <sound/sdw_bus.h>
+
+/**
+ * sdw_deferred_xfer_data: Data to be provided by bus driver for calling
+ * xfer_msg_deferred callback of Master driver.
+ *
+ * @result: Result of the asynchronous transfer.
+ * @xfer_complete: Bus driver will wait on this. Master needs to ack on this
+ * for transfer complete.
+ *
+ * @msg: Message to be transferred.
+ */
+struct sdw_deferred_xfer_data {
+ int result;
+ struct completion xfer_complete;
+ struct sdw_msg *msg;
+};
+
+/**
+ * sdw_master_dp0_caps: Master Data Port 0 capabilities.
+ *
+ * @max_bps: Maximum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @min_bps: Minimum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @num_bps: Array size of the buffer containing the supported bps use of
+ * max and min bps requires num_bps to be 0
+ *
+ * @bps_buf: Array containing supported sample sizes.
+ * @bra_max_data_per_frame: Maximum Data size per BRA(Bulk Access Register)
+ * packet.
+ */
+struct sdw_master_dp0_caps {
+ unsigned int max_bps;
+ unsigned int min_bps;
+ unsigned int num_bps;
+ unsigned int *bps_buf;
+ unsigned int bra_max_data_per_frame;
+};
+
+/**
+ * sdw_master_caps: Capabilities of the Master. This is filled by the
+ * software registering Master device like board or device entry table.
+ * All fields are listed in order of the SoundWire DisCo Spec. Name of
+ * the fields are altered to follow Linux convention. All the fields
+ * which are required by bus driver but not present in DisCo Spec are
+ * listed at the end of the structure.
+ *
+ * @clk_stp_mode0: True if Master interface supports ClockStop Mode0
+ * @clk_stp_mode1: True if Master interface supports ClockStop Mode1. Both
+ * of these fields are not used by bus driver as of now. This will be
+ * implemented in later version of the bus driver.
+ *
+ * @max_clk_freq: Max soundwire clock frequency at SDW clock line. This is
+ * in Hz.
+ *
+ * @num_clk_gears: Number of clock gears supported by Master.
+ * @clk_gears: A array containing clock gears integer supported by Master.
+ * @num_clk_freq: Number of clock frequencies supported by Master. This is
+ * alternate way of representing the clock gear. To use this
+ * num_clock_gears must be 0.
+ *
+ * @clk_freq_buf: Array representing the clock frequencies supported by the
+ * Master.
+ *
+ * @def_frame_rate: Master default frame rate in Hz. The frame rate defines
+ * the control bandwidth on a SoundWire bus.
+ *
+ * @def_frame_row_size: Number of rows 47<n<257. Not all values are valid as
+ * per Table 19 of the SoundWire MIPI specification Version 1.1.
+ *
+ * @def_frame_col_size: Number of rows 1<n<17. Only even numbers are
+ * valid.
+ *
+ * @dynamic_frame_shape: If false, bus driver may not change the frame shape
+ * dynamically and must only use the default/initial settings.
+ *
+ * @command_error_threshold: Number of times software may retry sending a
+ * single command. Once the threshold has been reached, an error
+ * condition exists and the command should not be retried.
+ *
+ * @bank_switch_timeout: Bus driver to wait for the specific timeout for
+ * bank switch to complete, after initiating the the bank switch
+ * operation. This is in milli-seconds.
+ *
+ * @monitor_handover_supported: Does Master support monitor handover.
+ * @highphy_capable: Is Master High-PHY capable? Basic-PHY and High-PHY are
+ * two modes a device can run. Bus driver doesn't support High-PHY mode
+ * as of now.
+ *
+ * @sdw_dp0_present: Data port0 present?
+ * @sdw_dp0_caps: Capabilities of the Data Port 0 of the Master.
+ * @num_data_ports: Array size for the number of Data Ports present in
+ * Master.
+ *
+ * @sdw_dpn_caps: Array containing information about SoundWire Master Data
+ * Port Capabilities.
+ */
+struct sdw_master_caps {
+ bool clk_stp_mode0;
+ bool clk_stp_mode1;
+ unsigned int max_clk_freq;
+ unsigned int num_clk_gears;
+ unsigned int *clk_gears;
+ unsigned int num_clk_freq;
+ unsigned int *clk_freq_buf;
+ unsigned int def_frame_rate;
+ unsigned int def_frame_row_size;
+ unsigned int def_frame_col_size;
+ bool dynamic_frame_shape;
+ unsigned int command_error_threshold;
+ unsigned int bank_switch_timeout;
+ bool monitor_handover_supported;
+ bool highphy_capable;
+ bool sdw_dp0_present;
+ struct sdw_master_dp0_caps sdw_dp0_caps;
+ unsigned int num_data_ports;
+ struct sdw_dpn_caps *sdw_dpn_caps;
+};
+
+/**
+ * sdw_master: Structure representing the Master interface of the SoundWire.
+ *
+ * @dev: Master interface for this driver.
+ * @bus: Bus handle for easy access of bus from Master.
+ * @name: Name of the Master driver.
+ * @nr: Master instance number. This represents SoundWire bus. This is
+ * logical number incremented per every Master getting registered.
+ *
+ * @timeout: Timeout before getting message response, in ms.
+ * @retries: How many times to retry before giving up on Slave response.
+ * @link_sync_mask: Bit mask representing all the other Master links with
+ * which this link is synchronized.
+ *
+ * @slv_list: List of SoundWire Slaves registered to the bus.
+ * @sdw_addr: Array containing Slave SoundWire bus Slave address
+ * information. Its a part of Slave list as well, but for easier access
+ * its part of array to find the Slave reference by directly indexing
+ * the Slave number into the array.
+ *
+ * @lock: Global lock for bus functions. This lock is used for
+ * following
+ * 1. Enumerating the Slaves and adding it to the Master list.
+ * 2. Bus reconfiguration operations.
+ *
+ * @msg_lock: Used to serialize the messages on the bus.
+ * @caps: Capabilities of the SoundWire Master interface.
+ * @driver: Driver handling the Master.
+ * @slv_released_complete: Flag to indicate Slave release completion.
+ * Internally used by bus driver. This is to make sure that Master is
+ * freed only after all Slaves devices are freed.
+ *
+ * @link_id: This is the link_id of the Master. This has direct relationship
+ * with the Slaves address. This is provided by the software
+ * registering the Master. This is read either from ACPI or from board
+ * data.
+ *
+ * @num_slv: Number of SoundWire Slaves assigned DeviceNumber after
+ * enumeration. The SoundWire specification does not place a
+ * restriction on how many Slaves are physically connected, as long as
+ * only 11 are active concurrently. This bus driver adds a pragmatic
+ * restriction to 11 Slaves, the current implementation assigns a
+ * DeviceNumber once and will never use the same DeviceNumber for
+ * different devices.
+ */
+struct sdw_master {
+ struct device dev;
+ struct sdw_bus *bus;
+ char name[SOUNDWIRE_NAME_SIZE];
+ unsigned int nr;
+ unsigned int timeout;
+ unsigned int retries;
+ unsigned int link_sync_mask;
+ struct list_head slv_list;
+ struct sdw_slave_addr sdw_addr[SDW_MAX_DEVICES];
+ struct mutex lock;
+ struct mutex msg_lock;
+ struct sdw_master_caps caps;
+ struct sdw_master_driver *driver;
+ struct completion slv_released_complete;
+ struct list_head mstr_rt_list;
+ unsigned int link_id;
+ unsigned int num_slv;
+};
+#define to_sdw_master(d) container_of(d, struct sdw_master, dev)
+
+/**
+ * sdw_port_params: This is used to program the Data Port based on Data Port
+ * stream params. These parameters are not banked and not expected to
+ * change dynamically to avoid audio artifacts.
+ *
+ * @num: Port number.
+ * @bps: Word length of the Port
+ * @flow_mode: Port Data flow mode.
+ * @data_mode: Test modes or normal mode.
+ */
+struct sdw_port_params {
+ unsigned int num;
+ unsigned int bps;
+ unsigned int flow_mode;
+ unsigned int data_mode;
+};
+
+/**
+ * sdw_transport_params: This is used to program the Data Port based on Data
+ * Port transport params. All these parameters are banked and can be
+ * modified during a bank switch without any artefact's in audio
+ * stream. Bus driver modifies these parameters as part new stream
+ * getting enabled/disabled on the bus. Registers are explained next to
+ * each field where values will be filled. Those are MIPI defined
+ * registers for Slave devices.
+ *
+ * @blk_grp_ctrl_valid: Does Port implement block group control?
+ * @port_num: Port number for which params are to be programmed.
+ * @blk_grp_ctrl: Block group control value.
+ * @sample_interval: Sample interval.
+ * @offset1: Blockoffset of the payload Data.
+ * @offset2: Blockoffset of the payload Data.
+ * @hstart: Horizontal start of the payload Data.
+ * @hstop: Horizontal stop of the payload Data.
+ * @blk_pkg_mode: Block per channel or block per Port.
+ * @lane_ctrl: Data lane Port uses for Data transfer. Currently only single
+ * data lane is supported in bus driver.
+ */
+struct sdw_transport_params {
+ bool blk_grp_ctrl_valid;
+ unsigned int port_num;
+ unsigned int blk_grp_ctrl; /* DPN_BlockCtrl2 */
+ unsigned int sample_interval; /* DPN_SampleCtrl1 & DPN_SampleCtrl2 */
+ unsigned int offset1; /* DPN_OffsetCtrl1 */
+ unsigned int offset2; /* DPN_OffsetCtrl2 */
+ unsigned int hstart; /* DPN_HCtrl */
+ unsigned int hstop; /* DPN_HCtrl */
+ unsigned int blk_pkg_mode; /* DPN_BlockCtrl3 */
+ unsigned int lane_ctrl; /* DPN_LaneCtrl */
+};
+
+/**
+ * sdw_enable_ch: Enable/disable Data Port channel. This is for triggering
+ * On/Off the Port. This readily translates to the trigger functions of
+ * ALSA. Actual trigger/enabling of the channels of the Port is done
+ * through with a bank switch since transport parameters may need to
+ * change due to a clock or frame shape change.
+ *
+ * @num: Port number for which params are to be programmed.
+ * @ch_mask: Active channel mask for this Port.
+ * @enable: Enable/disable channel, true (enable) or false
+ * (disable).
+ */
+struct sdw_enable_ch {
+ unsigned int num;
+ unsigned int ch_mask;
+ bool enable;
+};
+
+/**
+ * sdw_master_port_ops: Callback functions from bus driver to Master driver
+ * to set Master Data ports. Since Master registers are not standard,
+ * commands are passed to Master from bus and Master converts commands
+ * to register settings based on Master register map.
+ *
+ * @dpn_set_port_params: Set the Port parameters for the Master Port. This
+ * is mandatory callback to be provided by Master, if it support data
+ * ports.
+ *
+ * @dpn_set_port_transport_params: Set transport parameters for the Master
+ * Port. This is mandatory callback to be provided by Master if it
+ * supports data ports.
+ *
+ * @dpn_port_prep: Port prepare operations for the Master Data Port. Called
+ * before and after Port prepare as well as for Master Data Port
+ * prepare.
+ *
+ * @dpn_port_enable_ch: Enable the channels of particular Master Port.
+ * Actual enabling of the port is done as a part of bank switch This
+ * call is to enable the channels in alternate bank. This is mandatory
+ * if Master supports data ports.
+ */
+struct sdw_master_port_ops {
+ int (*dpn_set_port_params)(struct sdw_master *master,
+ struct sdw_port_params *port_params, unsigned int bank);
+ int (*dpn_set_port_transport_params)(struct sdw_master *master,
+ struct sdw_transport_params *transport_params,
+ unsigned int bank);
+ int (*dpn_port_prep)(struct sdw_master *master,
+ struct sdw_prepare_ch *prepare_ch,
+ enum sdw_port_prep_ops prep_ops);
+ int (*dpn_port_enable_ch)(struct sdw_master *master,
+ struct sdw_enable_ch *enable_ch, unsigned int bank);
+};
+
+/**
+ * sdw_master_ops: Callback operations from bus driver to Master driver.
+ * Bus driver calls these functions to control the bus parameters in
+ * Master hardware specific way. Its like i2c_algorithm to access the
+ * bus in Master specific way.
+ *
+ * @xfer_msg: Callback function to Master driver to read/write Slave
+ * registers. This is mandatory callback to be provided by Master
+ * driver to do Slave register read/writes.
+ *
+ * @reset_page_addr: Callback function to Master driver to reset the SCP
+ * page address registers of Slave. This is called by bus driver after
+ * message transfer, to reset the page address registers to 0. This is
+ * to make sure MIPI defined Slave registers are accessed in fast
+ * manner as all the MIPI defined Slave registers are till 0xFFFF,
+ * where SCP page address is always 0. xfer_msg callback can also be
+ * used to reset page address with dummy message. But this callback is
+ * more explicit where master driver is informed to reset the page
+ * address registers of the Slave. This is mandatory callback to be
+ * provided by Master driver.
+ *
+ * @xfer_bra: Callback function to Master driver for BRA transfer. This is
+ * required if BRA is supported on DP0.
+ *
+ * @monitor_handover: Allow monitor to be owner of command, if requested.
+ * This is optional based on Master support for handovers.
+ *
+ * @set_ssp_interval: Set SSP interval. This is mandatory callback to be
+ * provided by Master driver.
+ *
+ * @set_bus_params: Set the clock frequency and frame shape based on
+ * bandwidth requirement. Master driver sets the frequency and frame
+ * shape in hardware specific way. This is mandatory if Master supports
+ * dynamic frame shape and clock scaling.
+ *
+ * @pre_bank_switch: This is required to be implemented by Master driver if
+ * stream is handled by more than one Master. This is called by the the
+ * bus driver before bank switch operation is initiated. If there are
+ * two masters handling the stream, for both masters this will be
+ * called before scheduling bank switch operation. Normally master
+ * prepares itself in this call for synchronous bank switch.
+ *
+ * @xfer_msg_deferred: This is required to be implemented by Master driver
+ * if stream is handled by more than one Master interface. Bus driver
+ * initiates bank switch and wait for the bank switch to be completed.
+ * For streams handled by multiple Masters, bus driver initiates bank
+ * switch using this API for all Masters of the associated with the
+ * stream and waits for bank switch to be completed on all Masters. Its
+ * up to master implementation on how to synchronize bank switches on
+ * Multiple masters.
+ *
+ * @post_bank_switch: This is required to be implemented by Master driver if
+ * stream is handled by more than one Master. This is called by the bus
+ * driver after bank switch operation is initiated. If there are two
+ * masters handling the stream, for both masters this will be called
+ * after initiating bank switch operation. Normally master completes
+ * the bank switch operation on all the Masters associated with this
+ * stream in this call.
+ */
+struct sdw_master_ops {
+ enum sdw_command_response (*xfer_msg)(struct sdw_master *master,
+ struct sdw_msg *msg, bool program_scp_addr_page);
+ enum sdw_command_response (*reset_page_addr)(struct sdw_master *master,
+ unsigned int dev_num);
+ int (*xfer_bra)(struct sdw_master *master,
+ struct sdw_bra_block *block);
+ int (*monitor_handover)(struct sdw_master *master,
+ bool handover);
+ int (*set_ssp_interval)(struct sdw_master *master,
+ unsigned int ssp_interval, unsigned int bank);
+ int (*set_bus_params)(struct sdw_master *master,
+ struct sdw_bus_params *params);
+ int (*pre_bank_switch)(struct sdw_master *master);
+ void (*xfer_msg_deferred)(struct sdw_master *master,
+ struct sdw_msg *msg, bool program_scp_addr_page,
+ struct sdw_deferred_xfer_data *data);
+ int (*post_bank_switch)(struct sdw_master *master);
+};
+
+/**
+ * sdw_master_driver: Manage SoundWire Master device driver.
+ *
+ * @driver_type: To distinguish between Master and Slave driver. This should
+ * be set by driver based on its handling Slave or Master SoundWire
+ * interface.
+ *
+ * @probe: Binds this driver to a SoundWire Master.
+ * @remove: Unbinds this driver from the SoundWire Master.
+ * @shutdown: Standard shutdown callback used during power down/halt.
+ * @suspend: Standard suspend callback used during system suspend
+ * @resume: Standard resume callback used during system resume
+ * @driver: SoundWire device drivers should initialize name and owner field
+ * of this structure.
+ *
+ * @ops: Callback operations from bus driver to Master driver for
+ * programming and controlling bus parameters and to program Slave
+ * registers.
+ *
+ * @port_ops: Commands to setup the Master ports. Master register map is
+ * not defined by standard. So these ops represents the commands to
+ * setup Master ports.
+ *
+ * @id_table: List of SoundWire devices supported by this driver. This list
+ * should be NULL terminated.
+ */
+struct sdw_master_driver {
+ enum sdw_driver_type driver_type;
+ struct device_driver driver;
+ int (*probe)(struct sdw_master *master, const struct sdw_master_id *);
+ int (*remove)(struct sdw_master *master);
+ void (*shutdown)(struct sdw_master *master);
+ int (*suspend)(struct sdw_master *master);
+ int (*resume)(struct sdw_master *master);
+ struct sdw_master_ops *ops;
+ struct sdw_master_port_ops *port_ops;
+ const struct sdw_master_id *id_table;
+};
+#define to_sdw_master_driver(d) \
+ container_of(d, struct sdw_master_driver, driver)
+
+/**
+ * snd_sdw_master_add: Registers the SoundWire Master interface. This needs
+ * to be called for each Master interface supported by SoC. This
+ * represents One clock and data line (Optionally multiple data lanes)
+ * of Master interface.
+ *
+ * @master: the Master to be added.
+ */
+int snd_sdw_master_add(struct sdw_master *master);
+
+/**
+ * snd_sdw_master_del - unregister SDW Master
+ *
+ * @master: the Master being unregistered
+ */
+void snd_sdw_master_del(struct sdw_master *master);
+
+/**
+ * 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.
+ *
+ * @driver: Master Driver to be associated with Master interface.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_master_register_driver(struct sdw_master_driver *driver,
+ struct module *owner);
+
+/**
+ * sdw_unregister_master_driver: Undo effects of
+ * snd_sdw_master_driver_register
+ *
+ * @drv: SoundWire Master driver to be unregistered
+ */
+static inline void sdw_master_unregister_driver(struct sdw_master_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+
+/**
+ * snd_sdw_master_update_slave_status: Update the status of the Slave to the
+ * bus driver. Master calls this function based on the interrupt it
+ * gets once the Slave changes its state or from interrupts for the
+ * Master hardware that caches status information reported in PING
+ * commands.
+ *
+ * @master: Master handle for which status is reported.
+ * @status: Array of status of each Slave.
+ *
+ * This function can be called from interrupt context by Master driver to
+ * report Slave status without delay.
+ */
+int snd_sdw_master_update_slave_status(struct sdw_master *master,
+ struct sdw_status *status);
+
+/**
+ * snd_sdw_master_get: Return the Master handle from Master number.
+ * Increments the reference count of the module. Similar to
+ * i2c_get_adapter.
+ *
+ * @nr: Master number.
+ *
+ * Returns Master handle on success, else NULL
+ */
+struct sdw_master *snd_sdw_master_get(int nr);
+
+/**
+ * snd_sdw_master_put: Reverses the effect of sdw_master_get
+ *
+ * @master: Master handle.
+ */
+void snd_sdw_master_put(struct sdw_master *master);
+
+/**
+ * snd_sdw_master_prepare_for_clk_stop: Prepare all the Slaves for clock
+ * stop. Iterate through each of the enumerated Slaves. Prepare each
+ * Slave according to the clock stop mode supported by Slave. Use
+ * dynamic value from Slave callback if registered, else use static
+ * values from Slave capabilities registered.
+ * 1. Get clock stop mode for each Slave.
+ * 2. Call pre_prepare callback of each Slave if registered.
+ * 3. Write ClockStopPrepare bit in SCP_SystemCtrl register for each of
+ * the enumerated Slaves.
+ * 4. Broadcast the read message to read the SCP_Stat register to make
+ * sure ClockStop Prepare is finished for all Slaves.
+ * 5. Call post_prepare callback of each Slave if registered after
+ * Slaves are in ClockStopPrepare state.
+ *
+ * @master: Master handle for which clock state has to be changed.
+ */
+int snd_sdw_master_prepare_for_clk_stop(struct sdw_master *master);
+
+/**
+ * snd_sdw_master_deprepare_after_clk_start: De-prepare all the Slaves
+ * exiting clock stop mode 0 after clock resumes. Clock is already
+ * resumed before this. De-prepare for the Slaves which were there in
+ * clock stop mode 1 is done after they enumerated back. This is because
+ * Slave specific callbacks needs to be invoked as part of de-prepare,
+ * which can be invoked only after Slave enumerates.
+ * 1. Get clock stop mode for each Slave.
+ * 2. Call pre_prepare callback of each Slave exiting from clock stop
+ * mode 0.
+ * 3. De-Prepare each Slave exiting from clock stop mode 0
+ * 4. Broadcast the Read message to make sure all Slaves are
+ * de-prepared for clock stop.
+ * 5. Call post_prepare callback of each Slave exiting from clock stop
+ * mode0
+ *
+ * @master: Master handle
+ */
+int snd_sdw_master_deprepare_after_clk_start(struct sdw_master *master);
+
+/**
+ * snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the
+ * SCP_CTRL register with clock_stop_now bit set.
+ *
+ * @master: Master handle for which clock has to be stopped.
+ */
+int snd_sdw_master_stop_clock(struct sdw_master *master);
+
+/* Return the adapter number for a specific adapter */
+static inline int sdw_master_get_id(struct sdw_master *master)
+{
+ return master->nr;
+}
+
+static inline void *sdw_master_get_drvdata(const struct sdw_master *master)
+{
+ return dev_get_drvdata(&master->dev);
+}
+
+static inline void sdw_master_set_drvdata(struct sdw_master *master,
+ void *data)
+{
+ dev_set_drvdata(&master->dev, data);
+}
+#endif /* _LINUX_SDW_MASTER_H */
diff --git a/include/sound/sdw_slave.h b/include/sound/sdw_slave.h
new file mode 100644
index 0000000..6577933
--- /dev/null
+++ b/include/sound/sdw_slave.h
@@ -0,0 +1,563 @@
+/*
+ * sdw_slave.h - Definition for SoundWire Slave interface. This file has
+ * all the definitions which are required for only SoundWire Slave
+ * driver. Some interfaces are common for both Slave and Master
+ * driver. Please refer to sdw_bus.h for common interfaces.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ *
+ * This header file refers to the MIPI SoundWire 1.1 Spec.
+ * [1.1] https://members.mipi.org/wg/All-Members/document (accessible to
+ * MIPI members).
+ *
+ * The comments in the file try to follow the same conventions with a
+ * capital letter for all standard definitions such as Master, Slave,
+ * Data Port, etc. When possible, the constant numeric values are kept
+ * the same as in the MIPI specifications. All of the constants reflect
+ * the MIPI SoundWire definitions and are not vendor specific. Some of
+ * the constants are for bus driver usage and not MIPI defined. These
+ * are specified at appropriate place in this file.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_SLAVE_H
+#define _LINUX_SDW_SLAVE_H
+
+#include <sound/sdw_bus.h>
+
+/**
+ * sdw_portn_intr_mask: Implementation defined interrupt mask for Slave
+ * Ports other than Data Port 0.
+ *
+ * @mask: Mask for the implementation defined interrupt.
+ */
+struct sdw_portn_intr_mask {
+ u8 mask;
+};
+
+/**
+ * sdw_impl_def_intr_mask: Implementation defined interrupt mask for Slave.
+ * Slave Ports can be sink or source or bidirectional. If Slave has
+ * bidirectional Ports, implementation defined interrupt mask should be
+ * provided for both directions. Bus driver programs the mask in the
+ * register before Port prepare based on Port configured as a sink or
+ * source.
+ *
+ * @portn_mask: Implementation defined mask for Slave Ports other than port0.
+ * Mask bits are exactly same as defined in MIPI Spec 1.1. Array size
+ * shall be same as number of Ports in Slave. For bidirectional ports,
+ * masks can be different for Source and Sink ports.
+ *
+ * @control_port_mask: Implementation defined interrupt mask for control
+ * Port. Mask Bits are exactly same as defined in MIPI Spec 1.1.
+ *
+ * @port0_mask: Implementation defined interrupt mask for Data Port 0.
+ * Mask bits are exactly same as defined in MIPI Spec 1.1.
+ */
+struct sdw_impl_def_intr_mask {
+ struct sdw_portn_intr_mask *portn_mask[SDW_MAX_PORT_DIRECTIONS];
+ u8 control_port_mask;
+ u8 port0_mask;
+};
+
+/**
+ * sdw_slave_bra_caps: Bulk Register Access (BRA) Capabilities of the Slave.
+ * This list all the fields listed in MIPI DisCo Spec.
+ *
+ * @max_bus_freq: Maximum bus frequency of this mode, in Hz
+ * @min_bus_freq: Minimum bus frequency of this mode, in Hz when using
+ * min-max properties, all values in the defined range are allowed. Use
+ * the config list in the next field if only discrete values are
+ * supported.
+ *
+ * @num_bus_freq: Number of discrete bus frequency configurations. Use of
+ * max_ and min_ bus frequencies requires num_bus_freq to be zero.
+ *
+ * @bus_freq_buf: Array of bus frequency configs.
+ * @max_data_per_frame: Maximum Data payload, in bytes per frame. Excludes
+ * header, CRC, footer. Maximum value defined by SoundWire protocol is
+ * 470 bytes.
+ *
+ * @min_us_between_transactions: Amount of delay, in microseconds,
+ * required to be inserted between BRA transactions. Use if Slave needs
+ * idle time between BRA transactions.
+ *
+ * @max_bandwidth: Maximum bandwidth (in bytes/s) that can be written/read
+ * (header, CRCs, footer excluded).
+ *
+ * @mode_block_alignment: Size of basic block in bytes. The Data payload
+ * size shall be an integer multiple of this basic block size.
+ * padding/repeating of the same value is required for transactions
+ * smaller than this basic block size.
+ */
+struct sdw_slave_bra_caps {
+ unsigned int max_bus_freq;
+ unsigned int min_bus_freq;
+ unsigned int num_bus_freq;
+ unsigned int *bus_freq_buf;
+ unsigned int max_data_per_frame;
+ unsigned int min_us_between_transactions;
+ unsigned int max_bandwidth;
+ unsigned int mode_block_alignment;
+};
+
+/**
+ * sdw_slave_dp0_caps: Capabilities of the Data Port 0 of Slave. All fields
+ * are listed in order of the SoundWire DisCo Spec. Name of the fields
+ * are altered to follow Linux convention. All the fields which are
+ * required by bus driver but not present in DisCo Spec are listed at
+ * the end of the structure.
+ *
+ * @max_bps: Maximum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @min_bps: Minimum bits per sample supported by Port. This is same as word
+ * length in SoundWire Spec.
+ *
+ * @num_bps: Array size of the buffer containing the supported bits per sample
+ * configuration. The use of max_ and min_ bps requires num_bps to be
+ * zero
+ *
+ * @bps_buf: Array containing supported sample sizes.
+ * @bra_use_flow_control: Flow control is required or not for bra block
+ * transfer.
+ *
+ * @impl_def_response: If True (nonzero), implementation-defined response is
+ * supported. This information may be used by a device driver to
+ * request that a generic bus driver forwards the response to the
+ * client device driver.
+ *
+ * @bra_initiator: Slave BRA initiator role supported
+ * @prepare_ch: Type of channel prepare scheme. Simplified or Normal channel
+ * prepare.
+ *
+ * @imp_def_intr_mask: Implementation defined interrupt mask for DP0 Port.
+ * These interrupts will be enabled by bus driver.
+ *
+ * @impl_def_bpt: If True (nonzero), implementation-defined. Payload Type is
+ * supported. This information is used to bypass the BRA protocol and
+ * may only be of interest when a device driver is aware of the
+ * Capabilities of the Master and Slave devices devices. This is not
+ * supported as of now.
+ *
+ * @bra_cap: BRA capabilities of the Slave. Currently only one mode is
+ * supported.
+ */
+struct sdw_slave_dp0_caps {
+ unsigned int max_bps;
+ unsigned int min_bps;
+ unsigned int num_bps;
+ unsigned int *bps_buf;
+ bool bra_use_flow_control;
+ bool impl_def_response;
+ bool bra_initiator;
+ enum sdw_ch_prepare_mode prepare_ch;
+ unsigned int imp_def_intr_mask;
+ bool impl_def_bpt;
+ struct sdw_slave_bra_caps bra_cap;
+};
+
+/**
+ * sdw_slave_caps: Capabilities of the SoundWire Slave. This is the
+ * structure for Slave driver to register its capabilities to bus
+ * driver. This structure is based on the MIPI DisCo Spec for
+ * registering Slave capabilities. It lists all the capabilities
+ * required by bus driver from DisCo Spec. It omits few of the
+ * capabilities from DisCo Spec, which bus driver doesn't require.
+ * Some of the DisCo Slave properties also provides information about
+ * Slave containing SoundWire controller and number of Master
+ * interfaces. This is not required by bus driver as Slave properties.
+ *
+ * @wake_up_unavailable: Slave is not capable of waking up the Master by
+ * driving the data line to High.
+ *
+ * @test_mode: Slave supports test modes.
+ * @clk_stp1_mode: Clock stop 1 mode supported by this Slave. SoundWire
+ * specification requires all compliant Slaves to support Clock Stop
+ * mode 0.
+ *
+ * @simple_clk_stp_prep: Simplified clock stop prepare supported.
+ * @clk_stp_prep_timeout: A slave-specific timeout in milliseconds. This
+ * value indicates the worst-case latency of the Clock Stop Prepare
+ * state machine transitions. After receiving a successful clock stop
+ * prepare/de-prepare command, the Slave should complete the operation
+ * before the expiration of the timeout. Software may poll for
+ * completion periodically or wait for this timeout value. If the
+ * requested action has not completed at the end of this timeout,
+ * software shall interpret this as an error condition.
+ *
+ * @clk_stp_prep_hard_reset_behavior: When set, the Slave keeps its prepare
+ * status after exit from clock stop mode1 and needs to be de-prepared
+ * by software. Otherwise, the slave does not need a de-prepare command
+ * upon resuming from clock stop mode1.
+ *
+ * @highphy_capable: Slave is highphy_capable or not? Currently highphy is
+ * not supported in bus driver.
+ *
+ * @paging: Paging registers supported for Slave?
+ * @bank_delay_support: Bank switching delay for Slave
+ * @port_15_read_behavior: Slave behavior when the Master attempts a Read to
+ * the Port15 alias
+ * 0: Command_Ignored
+ * 1: Command_OK, Data is OR of all registers
+ *
+ * @scp_impl_def_intr_mask: Implementation defined interrupt mask for Slave
+ * control port
+ *
+ * @lane_control_support: Lane control support for Slave
+ * @dp0_present: DP0 is supported by Slave.
+ * @dp0_caps: Data Port 0 Capabilities of the Slave.
+ * @num_ports: Number of SoundWire Data ports present. The representation
+ * assumes contiguous Port numbers starting at 1.
+ *
+ * @dpn_caps: Capabilities of the SoundWire Slave ports. This includes
+ * both Source and Sink Ports.
+ *
+ * @num_src_ports: Number of Source ports present on Slave. If Slave has
+ * bidirectional ports, it is counted as both Source and Sink ports.
+ * E.g. if Slave has two bidirectional ports, num_src_ports in
+ * capabilities shall be 2. This is used by bus driver to get the port
+ * capabilities based on port configured as Source or Sink for the
+ * particular stream.
+ *
+ * @num_sink_ports: Number of Sink ports present on Slave. If Slave has
+ * bidirectional ports, it is counted as both Source and Sink ports.
+ * E.g. if Slave has two bidirectional ports, num_sink_ports in
+ * capabilities shall be 2. This is used by bus driver to get the port
+ * capabilities based on port configured as Source or Sink for the
+ * particular stream.
+ *
+ * @num_ports: Total number of ports on the Slave. E.g. If Slave has 2
+ * bidirectional ports and 2 source ports and 2 sink ports, total
+ * number of ports reported to bus driver shall be 6.
+ */
+struct sdw_slave_caps {
+ bool wake_up_unavailable;
+ bool test_mode;
+ bool clk_stp1_mode;
+ bool simple_clk_stp_prep;
+ unsigned int clk_stp_prep_timeout;
+ bool clk_stp_prep_hard_reset_behavior;
+ bool highphy_capable;
+ bool paging;
+ bool bank_delay_support;
+ unsigned int port_15_read_behavior;
+ u8 scp_impl_def_intr_mask;
+ bool lane_control_support;
+ bool dp0_present;
+ struct sdw_slave_dp0_caps *dp0_caps;
+ unsigned int num_src_ports;
+ unsigned int num_sink_ports;
+ struct sdw_dpn_caps *dpn_caps[SDW_MAX_PORT_DIRECTIONS];
+ unsigned int num_ports;
+};
+
+/**
+ * sdw_portn_intr_stat: Implementation defined interrupt status for Slave
+ * Ports other than Data Port 0
+ *
+ * @num: Port number for which status is reported.
+ * @status: status of the implementation defined interrupts.
+ */
+struct sdw_portn_intr_stat {
+ unsigned int num;
+ u8 status;
+};
+
+/**
+ * sdw_impl_def_intr_stat: Implementation defined interrupt status for
+ * Slave.
+ *
+ * @num_ports: Number of ports in Slave other than Data Port 0.
+ * @portn_stat: Implementation defined status for Slave ports other than
+ * port0. Mask bits are exactly same as defined in MIPI Spec 1.1. Array
+ * size is same as number of ports in Slave.
+ *
+ * @control_port_stat: Implementation defined interrupt status mask for
+ * control port. Mask Bits are exactly same as defined in MIPI Spec
+ * 1.1.
+ *
+ * @port0_stat: Implementation defined interrupt status mask for Data
+ * Port 0. Mask bits are exactly same as defined in MIPI Spec 1.1.
+ */
+struct sdw_impl_def_intr_stat {
+ unsigned int num_ports;
+ struct sdw_portn_intr_stat *portn_stat;
+ u8 control_port_stat;
+ u8 port0_stat;
+};
+
+/**
+ * sdw_slave_priv: Slave device private structure. This is used by bus
+ * driver and need not to be used by Slave driver.
+ *
+ * @name: Name of the driver to use with the device.
+ * @addr: Slave logical and dev_id address structures. This is non-null
+ * for all the Slave which are enumerated.
+ *
+ * @driver: Slave's driver, pointer to access routine.
+ * @node: Node to add the Slave to the list of Slave devices physically
+ * connected to and managed by same Master.
+ *
+ * @port_ready: Port ready completion flag for each Port of the Slave. This
+ * field is not used for simplified channel prepare.
+ *
+ * @caps: Slave capabilities.
+ * @slave_cap_updated: Did Slave device driver updated Slave capabilities to
+ * bus. This is used by bus driver for Slave configurations like
+ * ClockStopMode etc.
+ *
+ * @dev_id: 6-byte unique device identification.
+ */
+struct sdw_slave_priv {
+ char name[SOUNDWIRE_NAME_SIZE];
+ struct sdw_slave_addr *addr;
+ struct sdw_slave_driver *driver;
+ struct list_head node;
+ struct completion *port_ready;
+ struct sdw_slave_caps caps;
+ bool slave_cap_updated;
+ unsigned int dev_id[SDW_NUM_DEV_ID_REGISTERS];
+};
+
+/**
+ * sdw_slave: Represents SoundWire Slave device (similar to 'i2c_client' on
+ * I2C).
+ *
+ * @dev: Driver model representation of the device.
+ * @mstr: SoundWire Master, instance physically connected to Slave.
+ * @link_id: SoundWire Master link_id to which this Slave is connected. This
+ * is required by Slave driver to get the ACPI or any platform specific
+ * configuration based on dev_id and link_id to which Slave is
+ * connected.
+ *
+ * @dev_num: DeviceNumber of the Slave, assigned by bus driver.
+ * @priv: Bus driver private data structure, Slave device driver should not
+ * use it.
+ */
+struct sdw_slave {
+ struct device dev;
+ struct sdw_master *mstr;
+ unsigned int link_id;
+ unsigned int dev_num;
+ struct sdw_slave_priv priv;
+};
+#define to_sdw_slave(d) container_of(d, struct sdw_slave, dev)
+
+/**
+ * sdw_slave_driver: Manage SoundWire Slave device driver.
+ *
+ * @driver_type: To distinguish between Master and Slave driver. Driver
+ * should set this based on its controlling Master or Slave device.
+ *
+ * @probe: Binds this driver to a SoundWire Slave device (in Linux Device
+ * model sense).
+ *
+ * @remove: Unbinds this driver from the SoundWire Slave.
+ * @shutdown: Standard shutdown callback used during power down/halt.
+ * @suspend: Standard suspend callback used during system suspend.
+ * @resume: Standard resume callback used during system resume.
+ * @driver: Generic driver structure, according to driver model.
+ * @slave_irq: When interrupts are enabled, the Master driver is notified of
+ * alert conditions through the PREQ mechanism and fetches the Slave
+ * status. Bus driver handles all interrupts specified in the
+ * SoundWire specification, such as bus clash, parity or Port test
+ * fail. Slave driver handles all implementation-defined interrupts,
+ * such as jack detect and pll-locked. If the slave_irq callback is
+ * defined, the bus driver invokes it and lets the Slave driver handle
+ * the status. Bus driver ACKs all interrupts including implementation
+ * defined interrupts once Slave handles it. This is mandatory if Slave
+ * is supporting implementation defined interrupts.
+ *
+ * @pre_bus_config: Slave callback function to let Slave configure
+ * implementation defined registers prior to any bus configuration
+ * changes. Bus configuration changes will be signaled by a bank switch
+ * initiated by the bus driver once all Slaves drivers have performed
+ * their imp-def configuration sequence (if any). If this callback is
+ * not implemented the bus driver will assume the Slave can tolerate
+ * bus configurations changes at any time. This is optional based on
+ * Slave implementation.
+ *
+ * @port_prep: Slave driver callback to allow Slave Port to be
+ * prepared/de-prepared by configuring impl defined register as part of
+ * Port prepare state machine. This fn is called before and after
+ * DPn_Prepare ctrl is written based on "sdw_port_prep_ops" ops. Post
+ * prepare is called after Port is prepared/de-prepared.
+ *
+ * @status_change_event: Slave device status change event to Slave driver.
+ * Bus driver calls this callback to Slave driver every time there is
+ * status change of the Slave. Slave device use this to make sure that
+ * its in attached state after resuming, before doing any register
+ * access. This is mandatory callback to be provided by Slave to update
+ * the status change events.
+ *
+ * @pre_clk_stop_prep: Common Slave driver callback to prepare for clock
+ * stop and deprepare after clock is resumed - depending upon flag
+ * value. This is called before prepare bit is set for clock stop,
+ * while in resume case its called before prepare bit is reset. This is
+ * optional based on Slave implementation.
+ *
+ * @post_clk_stop_prep: Common Slave driver call back for Slave operations
+ * to be done after clock stop prepare is done for clock stop and after
+ * clock stop de-prepare is done after clock resume. This is called
+ * after prepare is done for clock stop and deprepare is done for clock
+ * resume. This is optional based on Slave implementation.
+ *
+ * @get_dyn_clk_stp_mod: Get the clock stop mode from Slave dynamically
+ * before preparing Slave for prepare. Slave registers the ClockStop
+ * capability as part of registering Slave capability. This API
+ * provides Slave with dynamically updating the ClockStop mode based on
+ * use case. If this is not defined by Slave, bus driver will use
+ * static capability for ClockStop Mode registered to bus driver as
+ * part of Slave capabilities. This is optional based on Slave
+ * implementation.
+ *
+ * @id_table: List of SoundWire Slaves supported by this driver. Multiple
+ * Slaves from the same vendor may be handled by the same Slave driver
+ * as long as hardware differences are handled within this Slave driver
+ * (same as for I2S codecs).
+ */
+struct sdw_slave_driver {
+ enum sdw_driver_type driver_type;
+ struct device_driver driver;
+ int (*probe)(struct sdw_slave *slave, const struct sdw_slave_id *);
+ int (*remove)(struct sdw_slave *slave);
+ void (*shutdown)(struct sdw_slave *slave);
+ int (*suspend)(struct sdw_slave *slave);
+ int (*resume)(struct sdw_slave *slave);
+ int (*slave_irq)(struct sdw_slave *slave,
+ struct sdw_impl_def_intr_stat *intr_stat);
+ int (*pre_bus_config)(struct sdw_slave *slave,
+ struct sdw_bus_params *params);
+ int (*port_prep)(struct sdw_slave *slave,
+ struct sdw_prepare_ch *prepare_ch,
+ enum sdw_port_prep_ops pre_ops);
+ int (*status_change_event)(struct sdw_slave *slave,
+ enum sdw_slave_status status);
+ int (*pre_clk_stop_prep)(struct sdw_slave *slave,
+ enum sdw_clk_stop_mode mode, bool prep);
+ int (*post_clk_stop_prep)(struct sdw_slave *slave,
+ enum sdw_clk_stop_mode mode, bool prep);
+ enum sdw_clk_stop_mode (*get_dyn_clk_stp_mod)(struct sdw_slave *slave);
+ const struct sdw_slave_id *id_table;
+};
+#define to_sdw_slave_driver(d) container_of(d, struct sdw_slave_driver, driver)
+
+/**
+ * snd_sdw_slave_driver_register: SoundWire Slave driver registration with
+ * bus. This API will register the Slave driver with the SoundWire bus.
+ * It is typically called from the driver's module-init function.
+ *
+ * @driver: Driver to be associated with Slave.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_slave_driver_register(struct sdw_slave_driver *driver,
+ struct module *owner);
+
+/**
+ * snd_sdw_slave_register_caps: Register Slave device capabilities to the
+ * bus driver. Since bus driver handles bunch of Slave register
+ * programming it should be aware of Slave device capabilities. Slave
+ * device is attached to bus based on enumeration. Once Slave driver is
+ * attached to device and probe of Slave driver is called on device and
+ * driver binding, Slave driver should call this function to register
+ * its capabilities to bus. This should be the very first function to
+ * bus driver from Slave driver once Slave driver is registered and
+ * probed.
+ *
+ * @slave: SoundWire Slave handle.
+ * @cap: Slave caps to be registered to bus driver.
+ */
+int snd_sdw_slave_register_caps(struct sdw_slave *slave,
+ struct sdw_slave_caps *cap);
+
+/**
+ * snd_sdw_slave_set_intr_mask: Set the implementation defined interrupt
+ * mask. Slave sets the implementation defined interrupt mask as part
+ * of registering Slave capabilities. Slave driver can also modify
+ * implementation defined interrupt dynamically using below function.
+ *
+ * @slave: SoundWire Slave handle for which interrupt needs to be enabled.
+ * @intr_mask: Implementation defined interrupt mask.
+ */
+int snd_sdw_slave_set_intr_mask(struct sdw_slave *slave,
+ struct sdw_impl_def_intr_mask *intr_mask);
+
+/**
+ * sdw_slave_unregister_driver: Undo effects of sdw_slave_driver_register.
+ *
+ * @drv: SoundWire Slave driver to be unregistered.
+ */
+static inline void sdw_slave_unregister_driver(struct sdw_slave_driver *drv)
+{
+ driver_unregister(&drv->driver);
+}
+
+static inline struct sdw_master *sdw_slave_to_master(struct sdw_slave *slv)
+{
+ return slv->mstr;
+}
+
+static inline void *sdw_slave_get_drvdata(const struct sdw_slave *slv)
+{
+ return dev_get_drvdata(&slv->dev);
+}
+
+static inline void sdw_slave_set_drvdata(struct sdw_slave *slv, void *data)
+{
+ dev_set_drvdata(&slv->dev, data);
+}
+
+#endif /* _LINUX_SDW_SLAVE_H */
--
1.7.9.5
Add register definitions for the SoundWire Slave. This is
as per MIPI SoundWire spec 1.1.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
include/sound/sdw/sdw_registers.h | 277 +++++++++++++++++++++++++++++++++++++
1 file changed, 277 insertions(+)
create mode 100644 include/sound/sdw/sdw_registers.h
diff --git a/include/sound/sdw/sdw_registers.h b/include/sound/sdw/sdw_registers.h
new file mode 100644
index 0000000..48ecdf5
--- /dev/null
+++ b/include/sound/sdw/sdw_registers.h
@@ -0,0 +1,277 @@
+/*
+ * sdw_registers.h - SoundWire MIPI spec 1.1 defined SoundWire Slave
+ * register definition file. This defines the register offsets, bit
+ * mask and bit shift in accordance with MIPI spec 1.1.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ *
+ * This header file refers to the MIPI SoundWire 1.1 Spec.
+ * [1.1] https://members.mipi.org/wg/All-Members/document (accessible to
+ * MIPI members).
+ *
+ * The comments in the file try to follow the same conventions with a
+ * capital letter for all standard definitions such as Master, Slave,
+ * Data Port, etc. When possible, the constant numeric values are kept
+ * the same as in the MIPI specifications. All of the constants reflect
+ * the MIPI SoundWire definitions and are not vendor specific. Some of
+ * the constants are for bus driver usage and not MIPI defined. These
+ * are specified at appropriate place in this file.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_REG_H
+#define _LINUX_SDW_REG_H
+
+#define SDW_SCP_ADDRPAGE1_MASK 0xFF
+#define SDW_SCP_ADDRPAGE1_SHIFT 15
+
+#define SDW_SCP_ADDRPAGE2_MASK 0xFF
+#define SDW_SCP_ADDRPAGE2_SHIFT 22
+
+#define SDW_REGADDR_SHIFT 0x0
+#define SDW_REGADDR_MASK 0xFFFF
+
+#define SDW_MAX_REG_ADDR 65536
+
+
+#define SDW_NUM_DATA_PORT_REGISTERS 0x100
+#define SDW_BANK1_REGISTER_OFFSET 0x10
+
+#define SDW_DP0_INTSTAT 0x0
+#define SDW_DP0_INTSTAT_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DP0_INTSTAT_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DP0_INTSTAT_BRA_FAILURE_MASK BIT_MASK(2)
+#define SDW_DP0_INTSTAT_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DP0_INTSTAT_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DP0_INTSTAT_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DP0_INTCLEAR 0x0
+#define SDW_DP0_INTCLEAR_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DP0_INTCLEAR_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DP0_INTCLEAR_BRA_FAILURE_MASK BIT_MASK(2)
+#define SDW_DP0_INTCLEAR_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DP0_INTCLEAR_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DP0_INTCLEAR_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DP0_INTMASK 0x1
+#define SDW_DP0_INTMASK_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DP0_INTMASK_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DP0_INTMASK_BRA_FAILURE_MASK BIT_MASK(2)
+#define SDW_DP0_INTMASK_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DP0_INTMASK_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DP0_INTMASK_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DP0_PORTCTRL 0x2
+#define SDW_DP0_PORTCTRL_PORTDATAMODE_MASK GENMASK(3, 2)
+#define SDW_DP0_PORTCTRL_PORTDATAMODE_SHIFT 2
+#define SDW_DP0_PORTCTRL_NEXTINVERTBANK_MASK BIT_MASK(4)
+#define SDW_DP0_PORTCTRL_NEXTINVERTBANK_SHIFT 4
+#define SDW_DP0_PORTCTRL_BPT_PAYLD_TYPE_MASK GENMASK(7, 6)
+#define SDW_DP0_PORTCTRL_BPT_PAYLD_TYPE_SHIFT 6
+
+
+#define SDW_DP0_BLOCKCTRL1 0x3
+
+#define SDW_DP0_PREPARESTATUS 0x4
+
+#define SDW_DP0_PREPARECTRL 0x5
+
+#define SDW_DP0_CHANNELEN 0x20
+#define SDW_DP0_SAMPLECTRL1 0x22
+#define SDW_DP0_SAMPLECTRL2 0x23
+#define SDW_DP0_OFFSETCTRL1 0x24
+#define SDW_DP0_OFFSETCTRL2 0x25
+#define SDW_DP0_HCTRL 0x26
+#define SDW_DP0_LANECTRL 0x28
+
+#define SDW_SCP_INTSTAT1 0x40
+#define SDW_SCP_INTSTAT1_PARITY_MASK BIT_MASK(0)
+#define SDW_SCP_INTSTAT1_BUS_CLASH_MASK BIT_MASK(1)
+#define SDW_SCP_INTSTAT1_IMPL_DEF_MASK BIT_MASK(2)
+#define SDW_SCP_INTSTAT1_SCP2_CASCADE_MASK BIT_MASK(7)
+
+#define SDW_SCP_INTCLEAR1 0x40
+#define SDW_SCP_INTCLEAR1_PARITY_MASK BIT_MASK(0)
+#define SDW_SCP_INTCLEAR1_BUS_CLASH_MASK BIT_MASK(1)
+#define SDW_SCP_INTCLEAR1_IMPL_DEF_MASK BIT_MASK(2)
+
+#define SDW_SCP_INTMASK1 0x41
+#define SDW_SCP_INTMASK1_PARITY_MASK BIT_MASK(0)
+#define SDW_SCP_INTMASK1_BUS_CLASH_MASK BIT_MASK(1)
+#define SDW_SCP_INTMASK1_IMPL_DEF_MASK BIT_MASK(2)
+
+
+#define SDW_SCP_INTSTAT2 0x42
+#define SDW_SCP_INTSTAT2_SCP3_CASCADE_MASK BIT_MASK(7)
+
+
+#define SDW_SCP_INTSTAT3 0x43
+
+/* Number of interrupt status registers */
+#define SDW_NUM_INT_STAT_REGISTERS 3
+
+/* Number of interrupt clear registers */
+#define SDW_NUM_INT_CLEAR_REGISTERS 1
+
+#define SDW_SCP_CTRL 0x44
+#define SDW_SCP_CTRL_CLK_STP_NOW_MASK BIT_MASK(1)
+#define SDW_SCP_CTRL_FORCE_RESET_MASK BIT_MASK(2)
+#define SDW_SCP_CTRL_CLK_STP_NOW_SHIFT 0x1
+
+#define SDW_SCP_STAT 0x44
+#define SDW_SCP_STAT_CLK_STP_NF_MASK BIT_MASK(0)
+
+#define SDW_SCP_SYSTEMCTRL 0x45
+#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_MASK BIT_MASK(0)
+#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_MASK BIT_MASK(2)
+#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_MASK BIT_MASK(3)
+#define SDW_SCP_SYSTEMCTRL_HIGH_PHY_MASK BIT_MASK(4)
+
+#define SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_SHIFT 0x0
+#define SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_SHIFT 0x2
+#define SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_SHIFT 0x3
+#define SDW_SCP_SYSTEMCTRL_HIGH_PHY_SHIFT 0x4
+
+#define SDW_SCP_DEVNUMBER 0x46
+#define SDW_SCP_HIGH_PHY_CHECK 0x47
+#define SDW_SCP_ADDRPAGE1 0x48
+#define SDW_SCP_ADDRPAGE2 0x49
+#define SDW_SCP_KEEPEREN 0x4A
+#define SDW_SCP_BANKDELAY 0x4B
+#define SDW_SCP_TESTMODE 0x4F
+#define SDW_SCP_DEVID_0 0x50
+#define SDW_SCP_DEVID_1 0x51
+#define SDW_SCP_DEVID_2 0x52
+#define SDW_SCP_DEVID_3 0x53
+#define SDW_SCP_DEVID_4 0x54
+#define SDW_SCP_DEVID_5 0x55
+
+/* Banked Registers */
+#define SDW_SCP_FRAMECTRL 0x60
+#define SDW_SCP_NEXTFRAME 0x61
+
+#define SDW_DPN_INTSTAT 0x0
+#define SDW_DPN_INTSTAT_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DPN_INTSTAT_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DPN_INTSTAT_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DPN_INTSTAT_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DPN_INTSTAT_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DPN_INTCLEAR 0x0
+#define SDW_DPN_INTCLEAR_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DPN_INTCLEAR_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DPN_INTCLEAR_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DPN_INTCLEAR_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DPN_INTCLEAR_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DPN_INTMASK 0x1
+#define SDW_DPN_INTMASK_TEST_FAIL_MASK BIT_MASK(0)
+#define SDW_DPN_INTMASK_PORT_READY_MASK BIT_MASK(1)
+#define SDW_DPN_INTMASK_IMPDEF1_MASK BIT_MASK(5)
+#define SDW_DPN_INTMASK_IMPDEF2_MASK BIT_MASK(6)
+#define SDW_DPN_INTMASK_IMPDEF3_MASK BIT_MASK(7)
+
+#define SDW_DPN_PORTCTRL 0x2
+#define SDW_DPN_PORTCTRL_PORTFLOWMODE_MASK GENMASK(1, 0)
+#define SDW_DPN_PORTCTRL_PORTFLOWMODE_SHIFT 0
+#define SDW_DPN_PORTCTRL_PORTDATAMODE_MASK GENMASK(3, 2)
+#define SDW_DPN_PORTCTRL_PORTDATAMODE_SHIFT 2
+#define SDW_DPN_PORTCTRL_NEXTINVERTBANK_MASK BIT_MASK(4)
+#define SDW_DPN_PORTCTRL_NEXTINVERTBANK_SHIFT 4
+
+#define SDW_DPN_BLOCKCTRL1 0x3
+#define SDW_DPN_BLOCKCTRL1_WORDLENGTH_MASK GENMASK(5, 0)
+#define SDW_DPN_BLOCKCTRL1_WORDLENGTH_SHIFT 0
+
+#define SDW_DPN_PREPARESTATUS 0x4
+#define SDW_DPN_PREPARECTRL 0x5
+#define SDW_DPN_PREPARECTRL_CH_PREPARE_MASK GENMASK(7, 0)
+
+#define SDW_DPN_CHANNELEN 0x20
+#define SDW_DPN_BLOCKCTRL2 0x21
+#define SDW_DPN_SAMPLECTRL1 0x22
+
+#define SDW_DPN_SAMPLECTRL1_LOW_MASK GENMASK(7, 0)
+#define SDW_DPN_SAMPLECTRL2 0x23
+#define SDW_DPN_SAMPLECTRL2_SHIFT 8
+#define SDW_DPN_SAMPLECTRL2_LOW_MASK GENMASK(7, 0)
+#define SDW_DPN_OFFSETCTRL1 0x24
+#define SDW_DPN_OFFSETCTRL2 0x25
+#define SDW_DPN_HCTRL 0x26
+#define SDW_DPN_HCTRL_HSTART_MASK GENMASK(7, 4)
+#define SDW_DPN_HCTRL_HSTOP_MASK GENMASK(3, 0)
+#define SDW_DPN_HCTRL_HSTART_SHIFT 4
+#define SDW_DPN_HCTRL_HSTOP_SHIFT 0
+#define SDW_DPN_BLOCKCTRL3 0x27
+#define SDW_DPN_LANECTRL 0x28
+
+
+#define SDW_NUM_CASC_PORT_INTSTAT1 4
+#define SDW_CASC_PORT_START_INTSTAT1 0
+#define SDW_CASC_PORT_MASK_INTSTAT1 0x8
+#define SDW_CASC_PORT_REG_OFFSET_INTSTAT1 0x0
+
+#define SDW_NUM_CASC_PORT_INTSTAT2 7
+#define SDW_CASC_PORT_START_INTSTAT2 4
+#define SDW_CASC_PORT_MASK_INTSTAT2 1
+#define SDW_CASC_PORT_REG_OFFSET_INTSTAT2 1
+
+#define SDW_NUM_CASC_PORT_INTSTAT3 4
+#define SDW_CASC_PORT_START_INTSTAT3 11
+#define SDW_CASC_PORT_MASK_INTSTAT3 1
+#define SDW_CASC_PORT_REG_OFFSET_INTSTAT3 2
+
+
+#endif
--
1.7.9.5
From: Sanyog Kale <[email protected]>
This patch adds APIs for stream and port configurations for SoundWire
bus driver.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
sound/sdw/sdw.c | 774 ++++++++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw_priv.h | 449 +++++++++++++++++++++++++++++
2 files changed, 1223 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index 71d2550..ffbec9e 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -2358,6 +2358,780 @@ static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave)
}
/**
+ * snd_sdw_release_stream_tag: Free the already assigned stream tag.
+ * Reverses effect of "sdw_alloc_stream_tag"
+ *
+ * @stream_tag: Stream tag to be freed.
+ */
+void snd_sdw_release_stream_tag(unsigned int stream_tag)
+{
+ int i;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Get stream tag data structure */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tag == stream_tags[i].stream_tag) {
+
+ /* Reference count update */
+ sdw_dec_ref_count(&stream_tags[i].ref_count);
+
+ if (stream_tags[i].ref_count == 0)
+ /* Free up resources */
+ kfree(stream_tags[i].sdw_rt);
+ }
+ }
+
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+}
+EXPORT_SYMBOL_GPL(snd_sdw_release_stream_tag);
+
+/**
+ * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is
+ * a unique identifier for each SoundWire stream across all SoundWire
+ * bus instances. Stream tag is a software concept defined by bus
+ * driver for stream management and not by MIPI SoundWire Spec. Each
+ * SoundWire Stream is individually configured and controlled using the
+ * stream tag. Multiple Master(s) and Slave(s) associated with the
+ * stream, uses stream tag as an identifier. All the operations on the
+ * stream e.g. stream configuration, port configuration, prepare and
+ * enable of the ports are done based on stream tag. This API shall be
+ * called once per SoundWire stream either by the Master or Slave
+ * associated with the stream.
+ *
+ * @stream_tag: Stream tag returned by bus driver.
+ */
+int snd_sdw_alloc_stream_tag(unsigned int *stream_tag)
+{
+ int i;
+ int ret = -EINVAL;
+ struct sdw_runtime *sdw_rt;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Allocate new stream tag and initialize resources */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (!stream_tags[i].ref_count) {
+
+ *stream_tag = stream_tags[i].stream_tag;
+
+ /* Initialize stream lock */
+ mutex_init(&stream_tags[i].stream_lock);
+
+ /* Allocate resources for stream runtime handle */
+ sdw_rt = kzalloc(sizeof(*sdw_rt), GFP_KERNEL);
+ if (!sdw_rt) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* Reference count update */
+ sdw_inc_ref_count(&stream_tags[i].ref_count);
+
+ /* Initialize Master and Slave list */
+ INIT_LIST_HEAD(&sdw_rt->slv_rt_list);
+ INIT_LIST_HEAD(&sdw_rt->mstr_rt_list);
+
+ /* Change stream state to ALLOC */
+ sdw_rt->stream_state = SDW_STATE_STRM_ALLOC;
+
+ stream_tags[i].sdw_rt = sdw_rt;
+
+ ret = 0;
+ break;
+ }
+ }
+out:
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_alloc_stream_tag);
+
+/**
+ * sdw_config_mstr_stream: Checks if master runtime handle already
+ * available, if not allocates and initialize Master runtime handle.
+ *
+ * @mstr: Master handle
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @sdw_rt: Stream runtime handle.
+ *
+ * Returns Master runtime handle.
+ */
+static struct sdw_mstr_runtime *sdw_config_mstr_stream(struct sdw_master *mstr,
+ struct sdw_stream_config *stream_config,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_stream_params *str_p;
+
+ /* Retrieve Master handle if already available */
+ list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) {
+ if (mstr_rt->mstr == mstr)
+ return mstr_rt;
+ }
+
+ /* Allocate resources for Master runtime handle */
+ mstr_rt = kzalloc(sizeof(*mstr_rt), GFP_KERNEL);
+ if (!mstr_rt)
+ goto out;
+
+ /* Initialization of Master runtime handle */
+ INIT_LIST_HEAD(&mstr_rt->port_rt_list);
+ INIT_LIST_HEAD(&mstr_rt->slv_rt_list);
+ list_add_tail(&mstr_rt->mstr_strm_node, &sdw_rt->mstr_rt_list);
+ list_add_tail(&mstr_rt->mstr_node, &mstr->mstr_rt_list);
+
+ /* Update PCM parameters for Master */
+ mstr_rt->direction = stream_config->direction;
+ str_p = &mstr_rt->stream_params;
+ str_p->rate = stream_config->frame_rate;
+ str_p->channel_count = stream_config->channel_count;
+ str_p->bps = stream_config->bps;
+
+ /* Add reference for Master device handle */
+ mstr_rt->mstr = mstr;
+
+ /* Add reference for stream runtime handle */
+ mstr_rt->sdw_rt = sdw_rt;
+
+out:
+ return mstr_rt;
+}
+
+/**
+ * sdw_config_slave_stream: Allocate and initialize slave runtime handle.
+ *
+ * @slave: Slave handle
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @sdw_rt: Stream runtime handle.
+ *
+ * Returns Slave runtime handle.
+ */
+static struct sdw_slv_runtime *sdw_config_slv_stream(
+ struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_stream_params *str_p;
+
+ /* Allocate resources for Slave runtime handle */
+ slv_rt = kzalloc(sizeof(*slv_rt), GFP_KERNEL);
+ if (!slv_rt)
+ goto out;
+
+ /* Initialization of Slave runtime handle */
+ INIT_LIST_HEAD(&slv_rt->port_rt_list);
+
+ /* Update PCM parameters for Slave */
+ slv_rt->direction = stream_config->direction;
+ str_p = &slv_rt->stream_params;
+ str_p->rate = stream_config->frame_rate;
+ str_p->channel_count = stream_config->channel_count;
+ str_p->bps = stream_config->bps;
+
+ /* Add reference for Slave device handle */
+ slv_rt->slv = slave;
+
+ /* Add reference for stream runtime handle */
+ slv_rt->sdw_rt = sdw_rt;
+
+out:
+ return slv_rt;
+}
+
+/**
+ * sdw_release_mstr_stream: Removes entry from master runtime list and free
+ * up resources.
+ *
+ * @mstr: Master handle.
+ * @sdw_rt: Master runtime handle.
+ */
+static void sdw_release_mstr_stream(struct sdw_master *mstr,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_mstr_runtime *mstr_rt, *__mstr_rt;
+
+ /* Retrieve Master runtime handle */
+ list_for_each_entry_safe(mstr_rt, __mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ if (mstr_rt->mstr == mstr) {
+
+ if (mstr_rt->direction == SDW_DATA_DIR_OUT)
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->tx_ref_count);
+ else
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->rx_ref_count);
+
+ /* Remove node from the list */
+ list_del(&mstr_rt->mstr_strm_node);
+ list_del(&mstr_rt->mstr_node);
+
+ pm_runtime_mark_last_busy(&mstr->dev);
+ pm_runtime_put_sync_autosuspend(&mstr->dev);
+
+ /* Free up Master runtime handle resources */
+ kfree(mstr_rt);
+ }
+ }
+}
+
+/**
+ * sdw_release_slv_stream: Removes entry from slave runtime list and free up
+ * resources.
+ *
+ * @slave: Slave handle.
+ * @sdw_rt: Stream runtime handle.
+ */
+static void sdw_release_slv_stream(struct sdw_slave *slave,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_slv_runtime *slv_rt, *__slv_rt;
+
+ /* Retrieve Slave runtime handle */
+ list_for_each_entry_safe(slv_rt, __slv_rt, &sdw_rt->slv_rt_list,
+ slave_strm_node) {
+
+ if (slv_rt->slv == slave) {
+
+ if (slv_rt->direction == SDW_DATA_DIR_OUT)
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->tx_ref_count);
+ else
+ /* Reference count update */
+ sdw_dec_ref_count(&sdw_rt->rx_ref_count);
+
+ /* Remove node from the list */
+ list_del(&slv_rt->slave_strm_node);
+
+ pm_runtime_mark_last_busy(&slave->dev);
+ pm_runtime_put_sync_autosuspend(&slave->dev);
+
+ /* Free up Slave runtime handle resources */
+ kfree(slv_rt);
+ }
+ }
+}
+
+/**
+ * snd_sdw_release_stream: De-associates Master(s) and Slave(s) from stream.
+ * Reverse effect of the sdw_config_stream. Master calls this with
+ * Slave handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle,
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_release_stream(struct sdw_master *mstr,
+ struct sdw_slave *slave,
+ unsigned int stream_tag)
+{
+ int i;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Invalid stream tag\n");
+ return -EINVAL;
+ }
+
+ /* Call release API of Master/Slave */
+ if (!slave)
+ sdw_release_mstr_stream(mstr, sdw_rt);
+ else
+ sdw_release_slv_stream(slave, sdw_rt);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_release_stream);
+
+/**
+ * snd_sdw_config_stream: Configures the SoundWire stream. All the Master(s)
+ * and Slave(s) associated with the stream calls this API with
+ * "sdw_stream_config". This API configures SoundWire stream based on
+ * "sdw_stream_config" provided by each Master(s) and Slave(s)
+ * associated with the stream. Master calls this function with Slave
+ * handle as NULL, Slave calls this with Master handle as NULL.
+ *
+ * @mstr: Master handle.
+ * @slave: SoundWire Slave handle, Null if stream configuration is called by
+ * Master driver.
+ *
+ * @stream_config: Stream configuration for the SoundWire audio stream.
+ * @stream_tag: Stream_tag representing the audio stream. All Masters and
+ * Slaves part of the same stream has same stream tag. So Bus driver
+ * holds information of all Masters and Slaves associated with stream
+ * tag.
+ */
+int snd_sdw_config_stream(struct sdw_master *mstr,
+ struct sdw_slave *slave,
+ struct sdw_stream_config *stream_config,
+ unsigned int stream_tag)
+{
+ int i;
+ int ret = 0;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+ struct sdw_stream_tag *stream = NULL;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ stream = &stream_tags[i];
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Valid stream tag not found\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* Acquire stream lock */
+ mutex_lock(&stream->stream_lock);
+
+ /* Get and Initialize Master runtime handle */
+ mstr_rt = sdw_config_mstr_stream(mstr, stream_config, sdw_rt);
+ if (!mstr_rt) {
+ dev_err(&mstr->dev, "Master runtime configuration failed\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* Initialize Slave runtime handle */
+ if (slave) {
+ slv_rt = sdw_config_slv_stream(slave, stream_config, sdw_rt);
+ if (!slv_rt) {
+ dev_err(&mstr->dev, "Slave runtime configuration failed\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ }
+
+ /*
+ * Stream params will be stored based on Tx only, since there can be
+ * only one Tx and multiple Rx, There can be multiple Tx if there is
+ * aggregation on Tx. That is handled by adding the channels to
+ * stream_params for each aggregated Tx slaves
+ */
+ if (!sdw_rt->tx_ref_count && stream_config->direction ==
+ SDW_DATA_DIR_OUT) {
+ sdw_rt->stream_params.rate = stream_config->frame_rate;
+ sdw_rt->stream_params.channel_count =
+ stream_config->channel_count;
+ sdw_rt->stream_params.bps = stream_config->bps;
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->tx_ref_count);
+ }
+
+ /*
+ * Normally there will be only one Tx in system, multiple Tx can
+ * only be there if we support aggregation. In that case there may
+ * be multiple slave or masters handing different channels of same
+ * Tx stream.
+ */
+ else if (sdw_rt->tx_ref_count && stream_config->direction ==
+ SDW_DATA_DIR_OUT) {
+ if (sdw_rt->stream_params.rate !=
+ stream_config->frame_rate) {
+ dev_err(&mstr->dev, "Frame rate for aggregated devices not matching\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ if (sdw_rt->stream_params.bps != stream_config->bps) {
+ dev_err(&mstr->dev, "bps for aggregated devices not matching\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /*
+ * Number of channels gets added, since both devices will be
+ * supporting different channels. Like one Codec supporting
+ * L and other supporting R channel.
+ */
+ sdw_rt->stream_params.channel_count +=
+ stream_config->channel_count;
+
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->tx_ref_count);
+ } else
+ /* Reference count update */
+ sdw_inc_ref_count(&sdw_rt->rx_ref_count);
+
+ sdw_rt->type = stream_config->type;
+
+ /* Change stream state to CONFIG */
+ sdw_rt->stream_state = SDW_STATE_STRM_CONFIG;
+
+ /*
+ * Slaves are added to two list, This is because bandwidth is
+ * calculated for two masters individually, while Ports are enabled
+ * of all the aggregated masters and slaves part of the same stream
+ * tag simultaneously.
+ */
+ if (slave) {
+ list_add_tail(&slv_rt->slave_strm_node, &sdw_rt->slv_rt_list);
+ list_add_tail(&slv_rt->slave_mstr_node, &mstr_rt->slv_rt_list);
+ }
+
+ /* Release stream lock */
+ mutex_unlock(&stream->stream_lock);
+
+ if (slave)
+ pm_runtime_get_sync(&slave->dev);
+ else
+ pm_runtime_get_sync(&mstr->dev);
+
+ return ret;
+
+error:
+ mutex_unlock(&stream->stream_lock);
+ kfree(mstr_rt);
+ kfree(slv_rt);
+out:
+ return ret;
+
+}
+EXPORT_SYMBOL_GPL(snd_sdw_config_stream);
+
+/**
+ * sdw_check_dpn_caps: Check Master and Slave port capabilities. This performs
+ * PCM parameter check based on PCM
+ * parameters received in stream.
+ * @dpn_cap: Capabilities of Master or Slave port.
+ * @strm_params: Stream PCM parameters.
+ */
+static int sdw_check_dpn_caps(struct sdw_dpn_caps *dpn_cap,
+ struct sdw_stream_params *strm_prms)
+{
+ struct sdw_port_aud_mode_prop *mode_prop =
+ dpn_cap->mode_properties;
+ int i, value;
+
+ /* Check for sampling frequency */
+ if (mode_prop->num_sample_rate_cfgs) {
+ for (i = 0; i < mode_prop->num_sample_rate_cfgs; i++) {
+ value = mode_prop->sample_rate_buf[i];
+ if (strm_prms->rate == value)
+ break;
+ }
+
+ if (i == mode_prop->num_sample_rate_cfgs)
+ return -EINVAL;
+ } else {
+
+ if ((strm_prms->rate < mode_prop->min_sample_rate)
+ || (strm_prms->rate >
+ mode_prop->max_sample_rate)) {
+ return -EINVAL;
+ }
+ }
+
+ /* Check for bit rate */
+ if (dpn_cap->num_bps) {
+ for (i = 0; i < dpn_cap->num_bps; i++) {
+ value = dpn_cap->bps_buf[i];
+ if (strm_prms->bps == value)
+ break;
+ }
+
+ if (i == dpn_cap->num_bps)
+ return -EINVAL;
+
+ } else {
+
+ if ((strm_prms->bps < dpn_cap->min_bps)
+ || (strm_prms->bps > dpn_cap->max_bps))
+ return -EINVAL;
+ }
+
+ /* Check for number of channels */
+ if (dpn_cap->num_ch_cnt) {
+ for (i = 0; i < dpn_cap->num_ch_cnt; i++) {
+ value = dpn_cap->ch_cnt_buf[i];
+ if (strm_prms->bps == value)
+ break;
+ }
+
+ if (i == dpn_cap->num_ch_cnt)
+ return -EINVAL;
+
+ } else {
+
+ if ((strm_prms->channel_count < dpn_cap->min_ch_cnt) ||
+ (strm_prms->channel_count > dpn_cap->max_ch_cnt))
+ return -EINVAL;
+
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_mstr_port_configuration: Master Port configuration. This performs
+ * all the port related configuration including allocation port
+ * structure memory, assign PCM parameters and add port node in master
+ * runtime list.
+ *
+ * @mstr: Master handle.
+ * @sdw_rt: Stream runtime information.
+ * @ports_config: Port configuration for Slave.
+ */
+static int sdw_mstr_port_configuration(struct sdw_master *mstr,
+ struct sdw_runtime *sdw_rt,
+ struct sdw_ports_config *ports_config)
+{
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_port_runtime *port_rt;
+ int found = 0;
+ int i;
+ int ret = 0, pn = 0;
+ struct sdw_dpn_caps *dpn_cap = mstr->caps.sdw_dpn_caps;
+
+ /* Get Master device handle */
+ list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) {
+ if (mstr_rt->mstr == mstr) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(&mstr->dev, "Master not found for this port\n");
+ return -EINVAL;
+ }
+
+ /* Allocate resources for port runtime handle */
+ port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports),
+ GFP_KERNEL);
+ if (!port_rt)
+ return -ENOMEM;
+
+ /* Check master capabilities */
+ if (!dpn_cap)
+ return -EINVAL;
+
+ /* Iterate for number of ports to perform initialization */
+ for (i = 0; i < ports_config->num_ports; i++) {
+ port_rt[i].channel_mask = ports_config->port_config[i].ch_mask;
+ port_rt[i].port_num = pn = ports_config->port_config[i].num;
+
+ /* Perform capability check for master port */
+ ret = sdw_check_dpn_caps(&dpn_cap[pn], &mstr_rt->stream_params);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "Master capabilities check failed ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Add node to port runtime list */
+ list_add_tail(&port_rt[i].port_node, &mstr_rt->port_rt_list);
+ }
+
+ return ret;
+
+error:
+ kfree(port_rt);
+ return ret;
+}
+
+struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap,
+ enum sdw_data_direction direction,
+ unsigned int port_num)
+{
+ int i;
+ struct sdw_dpn_caps *dpn_cap;
+ u8 num_ports;
+ bool port_found = 0;
+
+ if (direction == SDW_DATA_DIR_OUT)
+ num_ports = slv_cap->num_src_ports;
+ else
+ num_ports = slv_cap->num_sink_ports;
+
+ for (i = 0; i < num_ports; i++) {
+ dpn_cap = &slv_cap->dpn_caps[direction][i];
+
+ if (dpn_cap->port_number == port_num) {
+ port_found = 1;
+ break;
+ }
+ }
+
+ if (!port_found)
+ return NULL;
+
+ return dpn_cap;
+
+}
+
+/**
+ * sdw_config_slv_port: Slave Port configuration. This performs
+ * all the port related configuration including allocation port
+ * structure memory, assign PCM parameters and add port node in slave
+ * runtime list.
+ * @slave: Slave handle.
+ * @sdw_rt: Stream runtime information.
+ * @ports_config: Port configuration for Slave.
+ */
+static int sdw_config_slv_port(struct sdw_slave *slave,
+ struct sdw_runtime *sdw_rt,
+ struct sdw_ports_config *ports_config)
+{
+ struct sdw_slv_runtime *slv_rt;
+ struct sdw_port_runtime *port_rt;
+ struct sdw_dpn_caps *dpn_cap;
+ int found = 0, ret = 0;
+ int i, pn;
+
+ /* Get Slave device handle */
+ list_for_each_entry(slv_rt, &sdw_rt->slv_rt_list, slave_strm_node) {
+ if (slv_rt->slv == slave) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found) {
+ dev_err(&slave->mstr->dev, "Slave not found for this port\n");
+ return -EINVAL;
+ }
+
+ /* Check whether slave capabilities are valid or invalid */
+ if (!slave->priv.slave_cap_updated) {
+ dev_err(&slave->mstr->dev, "Slave capabilities not updated\n");
+ return -EINVAL;
+ }
+
+ /* Allocate resources for port runtime handle */
+ port_rt = kzalloc((sizeof(*port_rt) * ports_config->num_ports),
+ GFP_KERNEL);
+ if (!port_rt)
+ return -ENOMEM;
+
+ /* Assign PCM parameters */
+ for (i = 0; i < ports_config->num_ports; i++) {
+ port_rt[i].channel_mask = ports_config->port_config[i].ch_mask;
+ port_rt[i].port_num = pn =
+ ports_config->port_config[i].num;
+
+ dpn_cap = sdw_get_slv_dpn_cap(&slave->priv.caps,
+ slv_rt->direction,
+ ports_config->port_config[i].num);
+ if (!dpn_cap) {
+ ret = -EINVAL;
+ dev_err(&slave->mstr->dev, "Slave port capabilities not found ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Perform capability check for slave port */
+ ret = sdw_check_dpn_caps(dpn_cap, &slv_rt->stream_params);
+ if (ret < 0) {
+ dev_err(&slave->mstr->dev, "Slave capabilities check failed ret = %d\n", ret);
+ goto error;
+ }
+
+ /* Add node to port runtime list */
+ list_add_tail(&port_rt[i].port_node, &slv_rt->port_rt_list);
+ }
+
+ return ret;
+
+error:
+ kfree(port_rt);
+ return ret;
+}
+
+/**
+ * snd_sdw_config_ports: Configures Master or Slave Port(s) associated with
+ * the stream. All the Master(s) and Slave(s) associated with the
+ * stream calls this API with "sdw_ports_config". Master calls this
+ * function with Slave handle as NULL, Slave calls this with Master
+ * handle as NULL.
+ *
+ * @mstr: Master handle where the Slave is connected.
+ * @slave: Slave handle.
+ * @ports_config: Port configuration for each Port of SoundWire Slave.
+ * @stream_tag: Stream tag, where this Port is connected.
+ */
+int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave,
+ struct sdw_ports_config *ports_config,
+ unsigned int stream_tag)
+{
+ int ret;
+ int i;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+ struct sdw_runtime *sdw_rt = NULL;
+ struct sdw_stream_tag *stream = NULL;
+
+ /* Retrieve master handle if called by Slave */
+ if (!mstr)
+ mstr = slave->mstr;
+
+ /* Retrieve stream runtime handle */
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tags[i].stream_tag == stream_tag) {
+ sdw_rt = stream_tags[i].sdw_rt;
+ stream = &stream_tags[i];
+ break;
+ }
+ }
+
+ if (!sdw_rt) {
+ dev_err(&mstr->dev, "Invalid stream tag\n");
+ return -EINVAL;
+ }
+
+ /* Acquire stream lock */
+ mutex_lock(&stream->stream_lock);
+
+ /* Perform Master/Slave port configuration */
+ if (!slave)
+ ret = sdw_mstr_port_configuration(mstr, sdw_rt, ports_config);
+ else
+ ret = sdw_config_slv_port(slave, sdw_rt, ports_config);
+
+ /* Release stream lock */
+ mutex_unlock(&stream->stream_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_config_ports);
+
+/**
* snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the
* SCP_CTRL register with clock_stop_now bit set.
*
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
index bbba27a..fc738b8 100644
--- a/sound/sdw/sdw_priv.h
+++ b/sound/sdw/sdw_priv.h
@@ -68,6 +68,31 @@
#define SDW_NUM_OF_MSG3_XFRD 3
#define SDW_NUM_OF_MSG4_XFRD 4
+/**
+ * Below values are not defined in MIPI standard. Completely arbitrary
+ * values that can be changed at will.
+ */
+#define SDW_MAX_STREAM_TAG_KEY_SIZE 80
+#define SDW_NUM_STREAM_TAGS 100 /* Max number of stream tags */
+#define SDW_DOUBLE_RATE_FACTOR 2 /* Double rate */
+
+/* TODO: Description to be provided for SDW_FREQ_MOD_FACTOR */
+#define SDW_FREQ_MOD_FACTOR 3000
+
+/**
+ * SDW_STRM_RATE_GROUPING is place holder number used to hold the frame rate
+ * used in grouping stream for efficiently calculating bandwidth. All the
+ * streams with same frame rates belong to same group. This number is
+ * dynamically increased if the group count number increases above 12.
+ */
+#define SDW_STRM_RATE_GROUPING 12
+
+/* Size of buffer in bytes. */
+#define SDW_BUF_SIZE1 1
+#define SDW_BUF_SIZE2 2
+#define SDW_BUF_SIZE3 3
+#define SDW_BUF_SIZE4 4
+
/* Maximum number of Data Ports. */
#define SDW_MAX_DATA_PORTS 15
@@ -80,6 +105,8 @@
*/
#define SDW_INTR_STAT_READ_MAX_TRIES 10
+extern struct snd_sdw_core snd_sdw_core;
+
/**
* sdw_driver: Structure to typecast both Master and Slave driver to generic
* SoundWire driver, to find out the driver type.
@@ -95,6 +122,278 @@ struct sdw_driver {
container_of(d, struct sdw_driver, driver)
/**
+ * sdw_stream_state: Stream state maintained by bus driver for performing
+ * stream operations.
+ *
+ * @SDW_STATE_STRM_ALLOC: New stream is allocated.
+ * @SDW_STATE_STRM_CONFIG: Stream is configured. PCM/PDM parameters of the
+ * Stream is updated to bus driver.
+ *
+ * @SDW_STATE_STRM_PREPARE: Stream is Prepared. All the ports of Master and
+ * Slave associated with this stream is prepared for enabling.
+ *
+ * @SDW_STATE_STRM_ENABLE: Stream is enabled. All the ports of Master and
+ * Slave associated with this stream are enable and now stream is
+ * active.
+ *
+ * @SDW_STATE_STRM_DISABLE: Stream in disabled state, All the ports of
+ * Master and Slave associated with the stream are disabled, and stream
+ * is not active on bus.
+ *
+ * @SDW_STATE_STRM_DEPREPARE: Stream in de-prepare state. All the ports of
+ * Master and Slave associated with the stream are de-prepared.
+ *
+ * @SDW_STATE_STRM_RELEASE: Stream in release state. Stream is not having
+ * any PCM/PDM configuration. There is not Free state for stream, since
+ * memory for the stream gets freed, and there is no way to update
+ * stream as free.
+ */
+enum sdw_stream_state {
+ SDW_STATE_STRM_ALLOC = 0,
+ SDW_STATE_STRM_CONFIG = 1,
+ SDW_STATE_STRM_PREPARE = 2,
+ SDW_STATE_STRM_ENABLE = 3,
+ SDW_STATE_STRM_DISABLE = 4,
+ SDW_STATE_STRM_DEPREPARE = 5,
+ SDW_STATE_STRM_RELEASE = 6,
+};
+
+/**
+ * sdw_update_bus_ops: Operations performed by bus driver for stream state
+ * transitions. Some of the operations are performed on individual
+ * streams, while some are global operations affecting all the streams
+ * on the bus.
+ *
+ * @SDW_BUS_PORT_PRE: Perform all the operations which is to be done before
+ * initiating the bank switch for stream getting enabled. Master and
+ * Slave driver may need to perform some operations before bank switch.
+ * Call Master and Slave handlers to accomplish device specific
+ * operations before initiating bank switch.
+ *
+ * @SDW_BUS_BANK_SWITCH: Initiate the bank switch operation by broadcasting
+ * SCP_FrameCtrl register. Depending upon the Master implementation
+ * broadcast will be finished as a part of this state, or Master may
+ * set some register as a part of PORT_POST below operation after which
+ * broadcast will be finished. Initiation of the broadcast message is
+ * done as part of this operation. Broadcast message gets transmitted
+ * on the bus during this or next operation is Master dependent.
+ *
+ * @SDW_BUS_PORT_POST: Perform all the operations which are do be done after
+ * initiating the Bank switch. Call Master and Slave handlers to
+ * perform Post bank switch operation.
+ *
+ * @SDW_BUS_BANK_SWITCH_WAIT: Bus driver waits here for the Bank switch to
+ * be completed. This is used for Master(s) running in aggregation mode
+ * where pre and post operations are performed before and after Bank
+ * switch operation. The Bank switch message broadcast will happen only
+ * when clock is enabled which is done as part of post Bank switch
+ * operation. After post Bank switch operation, bus driver waits for
+ * response of Bank switch. The bus driver provides SDW_BUS_PORT_PRE
+ * and SDW_BUS_PORT_POST for Bank switch operation which are as per
+ * Master implementation.
+ *
+ * @SDW_BUS_PORT_DIS_CHN: Disable all the ports of the alternate bank
+ * (unused bank) after the bank switch. Once Bank switch operation is
+ * successful, the running stream(s) enabled port channels on previous
+ * bank needs to be disabled for both Master(s) and Slave(s).
+ */
+enum sdw_update_bus_ops {
+ SDW_BUS_PORT_PRE,
+ SDW_BUS_BANK_SWITCH,
+ SDW_BUS_PORT_POST,
+ SDW_BUS_BANK_SWITCH_WAIT,
+ SDW_BUS_PORT_DIS_CHN,
+};
+
+/**
+ * sdw_stream_tag: Stream tag represents the unique SoundWire Audio stream.
+ * All the ports of the Master(s) and Slave(s) part of the same stream
+ * tags gets enabled/disabled as a part of single bank Switch.If
+ * samples of the stream are split between the Master(s), its Master
+ * responsibility of synchronizing the bank switch of two individual
+ * Masters.
+ *
+ * @stream_tag: Unique stream tag number.
+ * @stream_lock: Lock for stream.
+ * @ref_count: Number of times stream tag is allocated. Stream tag is is
+ * available for allocation if reference count is 0.
+ *
+ * @sdw_rt: Holds the stream runtime information.
+ */
+struct sdw_stream_tag {
+ int stream_tag;
+ struct mutex stream_lock;
+ int ref_count;
+ struct sdw_runtime *sdw_rt;
+};
+
+/**
+ * sdw_stream_params: Stream parameters.
+ *
+ * @rate: Sampling frequency
+ * @channel_count: Number of channels.
+ * @bps: bits per sample.
+ */
+struct sdw_stream_params {
+ unsigned int rate;
+ unsigned int channel_count;
+ unsigned int bps;
+};
+
+/**
+ * sdw_port_runtime: Holds the port parameters for each of the Master(s)
+ * Slave(s) port associated with the stream.
+ *
+ * @port_num: Port number.
+ * @channel_mask: Channels of the Stream handled by this port.
+ * @transport_params: Transport parameters of port.
+ * @port_params: Port parameters
+ * @port_node: Port runtime is added to the Port List of Master(s) or
+ * Slave(s) associated with stream. Node to add the Port runtime to
+ * Master(s) or Slave(s) list.
+ */
+struct sdw_port_runtime {
+ int port_num;
+ int channel_mask;
+ struct sdw_transport_params transport_params;
+ struct sdw_port_params port_params;
+ struct list_head port_node;
+};
+
+/**
+ * sdw_slave_runtime: Holds the Stream parameters for the Slave associated
+ * with the stream.
+ *
+ * @slv: Slave handle associated with this Stream.
+ * @sdw_rt: Stream handle to which this Slave stream is associated.
+ * @direction: Port Direction of the Slave for this Stream. Slave is
+ * transmitting the Data or receiving the Data.
+ *
+ * @stream_params: Stream parameters for Slave.
+ * @port_rt_list: List of Slave Ports associated with this Stream.
+ * @slave_strm_node: Stream runtime data structure maintains list of all the
+ * Slave runtime instances associated with stream. This is the node to
+ * add Slave runtime instance to that list. This list is used for the
+ * stream configuration.
+ *
+ * @slave_mstr_node: Master runtime data structure maintains list of all the
+ * Slave runtime instances. This is the node to add Slave runtime
+ * instance to that list. This list is used for Bandwidth calculation
+ * per bus. Slave runtime instance gets added to two list one for
+ * stream configuration and other for bandwidth calculation. Stream
+ * configuration is per stream where there may be multiple Masters and
+ * Slave associated with Stream. Bandwidth calculation is per Bus,
+ * where there is Single Master and Multiple Slaves associated with
+ * bus.
+ */
+struct sdw_slv_runtime {
+ struct sdw_slave *slv;
+ struct sdw_runtime *sdw_rt;
+ int direction;
+ struct sdw_stream_params stream_params;
+ struct list_head port_rt_list;
+ struct list_head slave_strm_node;
+ struct list_head slave_mstr_node;
+};
+
+/**
+ * sdw_bus_runtime: This structure holds the transport params and BW
+ * required by the stream on the bus. There may be multiple bus
+ * associated with the stream. This holds bus specific parameters of
+ * stream. TODO: Currently sdw_bus_runtime is part of sdw_mstr_runtime
+ * Master handle. Once stream between Slave to Slave is supported by
+ * bus driver, this needs to be made part of sdw_runtime handle.
+ *
+ * @stream_bw: Bus Bandwidth required by this stream (bps).
+ * @hstart: Horizontal Start column for this stream.
+ * @hstop: Horizontal stop column for this stream.
+ * @block_offset: Block offset for this stream.
+ * @sub_block_offset: Sub Block offset for this stream.
+ */
+struct sdw_bus_runtime {
+ unsigned int stream_bw;
+ int hstart;
+ int hstop;
+ int block_offset;
+ int sub_block_offset;
+
+};
+
+/**
+ * sdw_mstr_runtime: Holds the Stream parameters for the Master associated
+ * with the stream.
+ *
+ * @mstr: Master handle associated with this stream.
+ * @sdw_rt: Stream handle to which this Master stream is associated.
+ * @stream_params: Stream parameters.
+ * @port_rt_list: List of this Master Ports associated with this Stream.
+ * @mstr_strm_node: Stream runtime data structure maintains list of all the
+ * Master runtime instances associated with stream. This is the node to
+ * add Master runtime instance to that list. This list is used for the
+ * stream configuration.
+ *
+ * @mstr_node: Master data structure maintains list of all the Master
+ * runtime instances. This is the node to add Master runtime instance
+ * to to that list. This list is used for Bandwidth calculation per
+ * bus. Master runtime instance gets added to two list one for stream
+ * configuration and other for bandwidth calculation. Stream
+ * configuration is per stream where there may be multiple Masters and
+ * Slave associated with Stream. Bandwidth calculation is per Bus,
+ * where there is Single Master and Multiple Slaves associated with
+ * bus.
+ *
+ * @slv_rt_list: List of the Slave_runtime instances associated with this
+ * Master_runtime. Its list of all the Slave(s) stream associated with
+ * this Master. There may be stereo stream from Master to two Slaves,
+ * where L and R samples from Master is received by two different
+ * Slave(s), so this list contains the runtime structure associated
+ * with both Slaves.
+ *
+ * @bus_rt: Bus parameters for the stream. There may be multiple bus
+ * associated with stream. This bus_rt is for current Master.
+ */
+struct sdw_mstr_runtime {
+ struct sdw_master *mstr;
+ struct sdw_runtime *sdw_rt;
+ int direction;
+ struct sdw_stream_params stream_params;
+ struct list_head port_rt_list;
+ struct list_head mstr_strm_node;
+ struct list_head mstr_node;
+ struct list_head slv_rt_list;
+ struct sdw_bus_runtime bus_rt;
+};
+
+/**
+ * sdw_runtime: This structure holds runtime information for each unique
+ * SoundWire stream.
+ *
+ * @tx_ref_count: Number of Transmit devices of stream. This may include
+ * multiple Master(s) and Slave(s) based on how stream samples are
+ * split between Mater and Slaves.
+ * @rx_ref_count: Number of Receive devices of stream. This may include
+ * multiple Master(s) and Slave(s) based on how stream samples are
+ * split between Mater and Slaves.
+ *
+ * @stream_params: Steam parameters.
+ * @slv_rt_list: List of the slaves part of this stream.
+ * @mstr_rt_list: List of Masters part of this stream.
+ * @type: Stream type PCM or PDM.This is not SoundWire concept, its used
+ * inside bus driver for efficient BW management.
+ *
+ * @stream_state: Current State of the stream.
+ */
+struct sdw_runtime {
+ int tx_ref_count;
+ int rx_ref_count;
+ struct sdw_stream_params stream_params;
+ struct list_head slv_rt_list;
+ struct list_head mstr_rt_list;
+ enum sdw_stream_type type;
+ enum sdw_stream_state stream_state;
+};
+
+/**
* sdw_slv_status: List of Slave status.
*
* @node: Node for adding status to list of Slave status.
@@ -110,6 +409,20 @@ struct sdw_slv_status {
*
* @bus_node: Node to add the bus in the sdw_core list.
* @mstr: Master reference for the bus.
+ * @clk_state: State of the clock.
+ * @active_bank: Current bank in use.
+ * @max_clk_dr_freq: Maximum double rate clock frequency. This is maximum
+ * double clock rate supported per bus.
+ * @curr_clk_dr_freq: Current double rate clock frequency in use. This is
+ * current clock rate at which bus is running.
+ *
+ * @clk_div: Current clock divider in use.
+ * @bandwidth: Total bandwidth.
+ * @system_interval: Bus System interval (Stream Synchronization Point).
+ * @stream_interval: Stream interval.
+ * @frame_freq: SoundWire Frame frequency on bus.
+ * @col: Active columns.
+ * @row: Active rows.
* @status_thread: Thread to process the Slave status.
* @kworker: Worker for updating the Slave status.
* @kwork: Work for worker
@@ -121,16 +434,48 @@ struct sdw_slv_status {
* context, spinlock is used to put the status reported by Master into
* the status list which is processed by bus driver in thread context
* later.
+ *
+ * @data: Data to be provided by bus driver for calling xfer_msg_deferred
+ * callback of Master driver.
+
*/
struct sdw_bus {
struct list_head bus_node;
struct sdw_master *mstr;
+ unsigned int clk_state;
+ unsigned int active_bank;
+ unsigned int max_dr_clk_freq;
+ unsigned int curr_dr_clk_freq;
+ unsigned int clk_div;
+ unsigned int bandwidth;
+ unsigned int system_interval;
+ unsigned int stream_interval;
+ unsigned int frame_freq;
+ unsigned int col;
+ unsigned int row;
struct task_struct *status_thread;
struct kthread_worker kworker;
struct kthread_work kwork;
struct list_head status_list;
spinlock_t spinlock;
+ struct sdw_deferred_xfer_data data;
+};
+
+/**
+ * sdw_row_col_pair: Information for each row column pair. This is used by
+ * bus driver for quick BW calculation.
+ *
+ * @row: Number of rows.
+ * @col: Number of columns
+ * @control_bits: Number of controls bits for this row-column pair.
+ * @data_bits: Number of controls bits for this row-column pair.
+ */
+struct sdw_row_col_pair {
+ int row;
+ int col;
+ int control_bits;
+ int data_bits;
};
/**
@@ -138,11 +483,17 @@ struct sdw_bus {
* spawned across masters and has list of bus structure per every
* Master registered.
*
+ * @row_col_pair: Array holding all row-column pair possible as per MIPI
+ * 1.1 Spec. This is used for quick reference for BW calculation
+ * algorithm.
+ *
* @bus_list: List of all the bus instance.
* @core_mutex: Global lock for all bus instances.
* @idr: For identifying the registered buses.
*/
struct snd_sdw_core {
+ struct sdw_stream_tag stream_tags[SDW_NUM_STREAM_TAGS];
+ struct sdw_row_col_pair row_col_pair[MAX_NUM_ROW_COLS];
struct list_head bus_list;
struct mutex core_mutex;
struct idr idr;
@@ -168,6 +519,58 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
struct sdw_deferred_xfer_data *data);
/**
+ * sdw_index_to_col: Structure holding mapping of numbers to columns.
+ *
+ * @index: Holds index to number of columns.
+ * @col: Holds actual columns.
+ */
+struct sdw_index_to_col {
+ int index;
+ int col;
+};
+
+/**
+ * sdw_index_to_row: Structure holding mapping of numbers to rows.
+ *
+ * @index: Holds index to number of rows.
+ * @row: Holds actual rows.
+ */
+struct sdw_index_to_row {
+ int index;
+ int row;
+};
+
+/**
+ * sdw_group_params: Structure holding temporary variable while computing
+ * transport parameters of Master(s) and Slave(s).
+ *
+ * @rate: Holds stream rate.
+ * @full_bw: Holds full bandwidth per group.
+ * @payload_bw: Holds payload bandwidth per group.
+ * @hwidth: Holds hwidth per group.
+ */
+struct sdw_group_params {
+ int rate;
+ int full_bw;
+ int payload_bw;
+ int hwidth;
+};
+
+/**
+ * sdw_group_count: Structure holding group count and stream rate array
+ * while computing transport parameters of Master(s) and Slave(s).
+ *
+ * @group_count: Holds actual group count.
+ * @max_size: Holds maximum capacity of array.
+ * @stream_rates: Pointer to stream rates.
+ */
+struct sdw_group_count {
+ unsigned int group_count;
+ unsigned int max_size;
+ unsigned int *stream_rates;
+};
+
+/**
* sdw_enable_disable_dpn_intr: Enable or Disable Slave Data Port interrupt.
* This is called by bus driver before prepare and after deprepare of
* the ports.
@@ -182,6 +585,52 @@ void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num,
int port_direction, bool enable);
+/**
+ * sdw_create_row_col_pair: Initialization of bandwidth related operations.
+ * This is required to have fast path for the BW calculation when a new
+ * stream is prepared or deprepared. This is called only once as part
+ * of SoundWire Bus driver getting initialized.
+ */
+void sdw_create_row_col_pair(void);
+
+/**
+ * sdw_init_bus_params: Sets up bus data structure for BW calculation. This
+ * is called once per each Master interface registration to the
+ * SoundWire bus.
+ *
+ * @sdw_bus: Bus handle.
+ */
+void sdw_init_bus_params(struct sdw_bus *sdw_bus);
+
+/**
+ * sdw_get_slv_dpn_caps: Get the data port capabilities based on the port
+ * number and port direction.
+ *
+ * @slv_cap: Slave capabilities.
+ * @direction: Port data direction.
+ * @port_num: Port number.
+ */
+struct sdw_dpn_caps *sdw_get_slv_dpn_cap(struct sdw_slave_caps *slv_cap,
+ enum sdw_data_direction direction,
+ unsigned int port_num);
+
+/* Return bus structure */
+static inline struct sdw_bus *sdw_master_to_bus(struct sdw_master *mstr)
+{
+ return mstr->bus;
+}
+
+/* Reference count increment */
+static inline void sdw_inc_ref_count(int *ref_count)
+{
+ (*ref_count)++;
+}
+
+/* Reference count decrement */
+static inline void sdw_dec_ref_count(int *ref_count)
+{
+ (*ref_count)--;
+}
/*
* Helper function for bus driver to write messages. Since bus driver
--
1.7.9.5
From: Sanyog Kale <[email protected]>
This patch adds following changes:
1. Prepare and De-prepare operation for stream.
2. Enable and Disable operation for stream.
3. Computation of Bus and Transport parameters.
4. Programming of Bus and Transport Parameters.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
sound/sdw/Makefile | 2 +-
sound/sdw/sdw.c | 249 +++++
sound/sdw/sdw_priv.h | 50 +
sound/sdw/sdw_runtime.c | 2807 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 3107 insertions(+), 1 deletion(-)
create mode 100644 sound/sdw/sdw_runtime.c
diff --git a/sound/sdw/Makefile b/sound/sdw/Makefile
index 6ed1881..49259c3 100644
--- a/sound/sdw/Makefile
+++ b/sound/sdw/Makefile
@@ -1 +1 @@
-obj-$(CONFIG_SOUND_SDW) += sdw.o
+obj-$(CONFIG_SOUND_SDW) += sdw.o sdw_runtime.o
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index ffbec9e..f0eaac0 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -2240,6 +2240,9 @@ int snd_sdw_master_add(struct sdw_master *master)
INIT_LIST_HEAD(&sdw_bus->status_list);
spin_lock_init(&sdw_bus->spinlock);
+ /* Initialize bandwidth calculation data structures */
+ sdw_init_bus_params(sdw_bus);
+
/*
* Add bus to the list of buses inside core. This is list of Slave
* devices enumerated on this bus. Adding new devices at end. It can
@@ -2358,6 +2361,75 @@ static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave)
}
/**
+ * sdw_acquire_mstr_lock: This function acquires Master lock for the
+ * Master(s) used by the given stream. The advantage of using Master
+ * lock over core lock is Master lock will lock only those Master(s)
+ * associated with given stream giving the advantage of simultaneous
+ * configuration of stream(s) running on different Master(s). On the
+ * other hand, core lock will not allow multiple stream configuration
+ * simultaneously.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ */
+static void sdw_acquire_mstr_lock(struct sdw_stream_tag *stream_tag)
+{
+ struct sdw_runtime *sdw_rt = stream_tag->sdw_rt;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_master *sdw_mstr = NULL;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ /* Get Master structure */
+ sdw_mstr = sdw_mstr_rt->mstr;
+
+ /* Acquire Master lock */
+ mutex_lock(&sdw_mstr->lock);
+ }
+
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+}
+
+/**
+ * sdw_release_mstr_lock: This function releases Master lock for the
+ * Master(s) used by the given stream acquired in sdw_acquire_mstr_lock
+ * API.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ *
+ */
+static void sdw_release_mstr_lock(struct sdw_stream_tag *stream_tag)
+{
+ struct sdw_runtime *sdw_rt = stream_tag->sdw_rt;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_master *sdw_mstr = NULL;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ /* Get Master structure */
+ sdw_mstr = sdw_mstr_rt->mstr;
+
+ /* Release Master lock */
+ mutex_unlock(&sdw_mstr->lock);
+ }
+
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+}
+
+/**
* snd_sdw_release_stream_tag: Free the already assigned stream tag.
* Reverses effect of "sdw_alloc_stream_tag"
*
@@ -3132,6 +3204,174 @@ int snd_sdw_config_ports(struct sdw_master *mstr, struct sdw_slave *slave,
EXPORT_SYMBOL_GPL(snd_sdw_config_ports);
/**
+ * sdw_find_stream: Retrieves stream tag handle by matching stream tag.
+ *
+ * @stream_tag: Stream tag.
+ */
+static struct sdw_stream_tag *sdw_find_stream(int stream_tag)
+{
+ int i;
+ struct sdw_stream_tag *stream_tags = snd_sdw_core.stream_tags;
+ struct sdw_stream_tag *stream = NULL;
+
+ /* Acquire core lock */
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ for (i = 0; i < SDW_NUM_STREAM_TAGS; i++) {
+ if (stream_tag == stream_tags[i].stream_tag) {
+ stream = &stream_tags[i];
+ break;
+ }
+ }
+
+ if (stream == NULL) {
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ WARN_ON(1);
+ return NULL;
+ }
+
+ /* Release core lock */
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ return stream;
+}
+
+/**
+ * snd_sdw_prepare_and_enable: Prepare and enable all the ports of all the
+ * Master(s) and Slave(s) associated with this stream tag. Following
+ * will be done as part of prepare operation.
+ * 1. Bus parameters such as bandwidth, frame shape, clock frequency,
+ * SSP interval are computed based on current stream as well as already
+ * active streams on bus. Re-computation is required to accommodate
+ * current stream on the bus.
+ * 2. Transport parameters of all Master and Slave ports are computed
+ * for the current as well as already active stream based on above
+ * calculated frame shape and clock frequency.
+ * 3. Computed bus and transport parameters are programmed in Master
+ * and Slave registers. The banked registers programming is done on the
+ * alternate bank (bank currently unused). Port channels are enabled
+ * for the already active streams on the alternate bank (bank currently
+ * unused). This is done in order to not to disrupt already active
+ * stream.
+ * 4. Once all the new values are programmed, switch is made to
+ * alternate bank. Once switch is successful, the port channels enabled
+ * on previous bank for already active streams are disabled.
+ * 5. Ports of Master and Slave for new stream are prepared.
+ *
+ * Following will be done as part of enable operation.
+ * 1. All the values computed in SDW_STATE_STRM_PREPARE state are
+ * programmed in alternate bank (bank currently unused). It includes
+ * programming of already active streams as well.
+ * 2. All the Master and Slave port channels for the new stream are
+ * enabled on alternate bank (bank currently unused).
+ * 3. Once all the new values are programmed, switch is made on the
+ * alternate bank. Once the switch is successful, the port channels
+ * enabled on previous bank for already active streams are disabled.
+ *
+ * This shall be called either by Master or Slave, which is responsible
+ * for doing data transfer between SoundWire link and the system
+ * memory.
+ *
+ * @stream_tag: Audio stream to be enabled. Each stream has unique
+ * stream_tag. All the channels of all the ports of Slave(s) and
+ * Master(s) attached to this stream will be prepared and enabled
+ * simultaneously with bank switch.
+ */
+int snd_sdw_prepare_and_enable(unsigned int stream_tag)
+{
+
+ int ret;
+
+ struct sdw_stream_tag *stream = NULL;
+
+ stream = sdw_find_stream(stream_tag);
+ if (!stream)
+ return -EINVAL;
+
+ /* Acquire Master lock */
+ sdw_acquire_mstr_lock(stream);
+
+ ret = sdw_prepare_and_enable_ops(stream);
+ if (ret < 0)
+ pr_err("Error: prepare/enable operation failed\n");
+
+ /* Release Master lock */
+ sdw_release_mstr_lock(stream);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_prepare_and_enable);
+
+/**
+ * snd_sdw_disable_and_deprepare: Disable and de-prepare all the ports of
+ * all the Master(s) and Slave(s) associated with stream tag. Following
+ * will be done as part of disable operation.
+ * 1. Disable for Master and Slave ports channels is performed on
+ * alternate bank (bank currently unused) registers for current stream.
+ * 2. All the current configuration of bus and Master and Slave ports
+ * are programmed into alternate bank (bank currently unused). It
+ * includes programming of already active streams port channels on
+ * alternate bank (bank currently unused).
+ * 3. Switch is made on new bank. Once the switch is successful, the
+ * port channels of current stream are disabled. All the port channels
+ * enabled on previous bank for active stream are disabled.
+ *
+ * Following will be done as part of de-prepare operation.
+ * 1. Check the bandwidth required per Master. If its zero, de-prepare
+ * current stream and move stream state SDW_STATE_STRM_UNPREPARE, rest
+ * of the steps are not required. If bandwidth required per Master is
+ * non zero that means some more streams are running on Master and
+ * continue with next step.
+ * 2. Bus parameters and transport parameters are computed for the
+ * streams active on the given Master.
+ * 3. All the computed values for active stream are programmed into
+ * alternate bank (bank currently unused) in Master and Slave registers
+ * including already active streams port channels on alternate bank
+ * (bank currently unused).
+ * 4. Switch is made to alternate bank where all the values for active
+ * stream were programmed. On successful switch of bank, all the port
+ * channels enabled on previous bank for active stream are disabled.
+ * 5. De-prepare ports of the Master and Slave associated with current
+ * stream.
+ *
+ * This shall be called either by Master or Slave, which is
+ * responsible for doing data transfer between SoundWire link and the
+ * system memory.
+ * Note: Both disable and de-prepare operations are performed in single
+ * call. De-prepare operation can be deferred for some specific timeout
+ * value after disable operation, to avoid bus re-configurations
+ * between short play and pause periods.
+ *
+ * @stream_tag: Audio stream to be disabled. Each stream has unique
+ * stream_tag. All the channels of all the ports of Slave(s) and
+ * Master(s) attached to this stream will be disabled and de-prepared
+ * simultaneously with bank switch.
+ */
+int snd_sdw_disable_and_deprepare(unsigned int stream_tag)
+{
+ int ret;
+ struct sdw_stream_tag *stream = NULL;
+
+ stream = sdw_find_stream(stream_tag);
+ if (!stream)
+ return -EINVAL;
+
+ /* Acquire Master lock */
+ sdw_acquire_mstr_lock(stream);
+
+ ret = sdw_disable_and_deprepare_ops(stream);
+ if (ret < 0)
+ pr_err("Error: disable/de-prepare operations failed\n");
+
+ /* Release Master lock */
+ sdw_release_mstr_lock(stream);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_disable_and_deprepare);
+
+/**
* snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the
* SCP_CTRL register with clock_stop_now bit set.
*
@@ -3487,6 +3727,15 @@ static int sdw_init(void)
if (retval)
bus_unregister(&sdw_bus_type);
+
+ /*
+ * Initialization of bandwidth and runtime stream
+ * management related operations required for bus driver.
+ * Currently pre-calculation of row-column combination is performed
+ * which is required to expedite computation of bus frame shape.
+ */
+ sdw_create_row_col_pair();
+
return retval;
}
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
index fc738b8..ae9a898 100644
--- a/sound/sdw/sdw_priv.h
+++ b/sound/sdw/sdw_priv.h
@@ -603,6 +603,24 @@ int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num,
void sdw_init_bus_params(struct sdw_bus *sdw_bus);
/**
+ * sdw_prepare_and_enable_ops: This is called by the bus driver for doing
+ * operations related to stream prepare and enable. sdw_bus_ops are
+ * performed on bus for preparing and enabling of the streams.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ */
+int sdw_prepare_and_enable_ops(struct sdw_stream_tag *stream_tag);
+
+/**
+ * sdw_disable_and_deprepare_ops: This is called by the bus driver for doing
+ * operations related to stream disable and de-prepare.sdw_bus_ops are
+ * performed on bus for disabling and de-preparing of the streams.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ */
+int sdw_disable_and_deprepare_ops(struct sdw_stream_tag *stream_tag);
+
+/**
* sdw_get_slv_dpn_caps: Get the data port capabilities based on the port
* number and port direction.
*
@@ -758,4 +776,36 @@ static inline void sdw_create_wr_msg(struct sdw_msg *msg, bool xmit_on_ssp,
msg->addr_page2 = 0x0;
}
+/* Retrieve and return channel count from channel mask */
+static inline int sdw_chn_mask_to_chn(int chn_mask)
+{
+ int c = 0;
+
+ for (c = 0; chn_mask; chn_mask >>= 1)
+ c += chn_mask & 1;
+
+ return c;
+}
+
+/* Fill transport parameter data structure */
+static inline void sdw_fill_xport_params(struct sdw_transport_params *params,
+ int port_num,
+ bool grp_ctrl_valid,
+ int grp_ctrl,
+ int off1, int off2,
+ int hstart, int hstop,
+ int pack_mode, int lane_ctrl)
+{
+
+ params->port_num = port_num;
+ params->blk_grp_ctrl_valid = grp_ctrl_valid;
+ params->blk_grp_ctrl = grp_ctrl;
+ params->offset1 = off1;
+ params->offset2 = off2;
+ params->hstart = hstart;
+ params->hstop = hstop;
+ params->blk_pkg_mode = pack_mode;
+ params->lane_ctrl = lane_ctrl;
+}
+
#endif /* _LINUX_SDW_PRIV_H */
diff --git a/sound/sdw/sdw_runtime.c b/sound/sdw/sdw_runtime.c
new file mode 100644
index 0000000..08b7f89
--- /dev/null
+++ b/sound/sdw/sdw_runtime.c
@@ -0,0 +1,2807 @@
+/*
+ * sdw_runtime.c - SoundWire bus driver stream runtime operations.
+ *
+ * Author: Sanyog Kale <[email protected]>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/lcm.h>
+#include <sound/sdw_bus.h>
+#include <sound/sdw_master.h>
+#include <sound/sdw_slave.h>
+#include <sound/sdw/sdw_registers.h>
+
+#include "sdw_priv.h"
+
+/* Array of supported rows as per MIPI SoundWire Specification 1.1 */
+static int rows[MAX_NUM_ROWS] = {48, 50, 60, 64, 72, 75, 80, 90,
+ 96, 125, 144, 147, 100, 120, 128, 150,
+ 160, 180, 192, 200, 240, 250, 256};
+
+/* Array of supported columns as per MIPI SoundWire Specification 1.1 */
+static int cols[MAX_NUM_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
+
+/* Mapping of index to rows */
+static struct sdw_index_to_row sdw_index_row_mapping[MAX_NUM_ROWS] = {
+ {0, 48}, {1, 50}, {2, 60}, {3, 64}, {4, 75}, {5, 80}, {6, 125},
+ {7, 147}, {8, 96}, {9, 100}, {10, 120}, {11, 128}, {12, 150},
+ {13, 160}, {14, 250}, {16, 192}, {17, 200}, {18, 240}, {19, 256},
+ {20, 72}, {21, 144}, {22, 90}, {23, 180},
+};
+
+/* Mapping of index to columns */
+static struct sdw_index_to_col sdw_index_col_mapping[MAX_NUM_COLS] = {
+ {0, 2}, {1, 4}, {2, 6}, {3, 8}, {4, 10}, {5, 12}, {6, 14}, {7, 16},
+};
+
+/**
+ * sdw_create_row_col_pair: Initialization of bandwidth related operations.
+ * This is required to have fast path for the BW calculation when a new stream
+ * is prepared or deprepared. This is called only once as part of SoundWire Bus
+ * driver getting initialized.
+ */
+void sdw_create_row_col_pair(void)
+{
+ int r, c, rowcolcount = 0;
+ int control_bits = SDW_BUS_CONTROL_BITS;
+
+ /* Run loop for all columns */
+ for (c = 0; c < MAX_NUM_COLS; c++) {
+
+ /* Run loop for all rows */
+ for (r = 0; r < MAX_NUM_ROWS; r++) {
+ snd_sdw_core.row_col_pair[rowcolcount].col = cols[c];
+ snd_sdw_core.row_col_pair[rowcolcount].row = rows[r];
+ snd_sdw_core.row_col_pair[rowcolcount].control_bits =
+ control_bits;
+ snd_sdw_core.row_col_pair[rowcolcount].data_bits =
+ (cols[c] * rows[r]) - control_bits;
+ rowcolcount++;
+ }
+ }
+}
+
+/**
+ * sdw_init_bus_params: Sets up bus data structure for BW calculation. This
+ * is called once per each Master interface registration to the
+ * SoundWire bus.
+ *
+ * @sdw_bus: Bus handle.
+ */
+void sdw_init_bus_params(struct sdw_bus *sdw_bus)
+{
+ struct sdw_master_caps *sdw_mstr_cap = NULL;
+
+ /* Initialize required parameters in bus structure */
+ sdw_mstr_cap = &sdw_bus->mstr->caps;
+ sdw_bus->max_dr_clk_freq = sdw_mstr_cap->max_clk_freq *
+ SDW_DOUBLE_RATE_FACTOR;
+
+ /*
+ * Assumption: At power on, bus is running at maximum frequency.
+ */
+ sdw_bus->curr_dr_clk_freq = sdw_bus->max_dr_clk_freq;
+}
+
+/**
+ * sdw_find_col_index: Performs column to index mapping. The retrieved
+ * number is used for programming register. This API is called by
+ * sdw_bank_switch.
+ *
+ * @col: number of columns.
+ *
+ * Returns column index from the mapping else lowest column mapped index.
+ */
+static int sdw_find_col_index(int col)
+{
+ int i;
+
+ for (i = 0; i <= MAX_NUM_COLS; i++) {
+ if (sdw_index_col_mapping[i].col == col)
+ return sdw_index_col_mapping[i].index;
+ }
+
+ return 0; /* Lowest Column number = 2 */
+}
+
+/**
+ * sdw_find_row_index: Performs row to index mapping. The retrieved number
+ * is used for programming register. This API is called by
+ * sdw_bank_switch.
+ *
+ * @row: number of rows.
+ *
+ * Returns row index from the mapping else lowest row mapped index.
+ */
+static int sdw_find_row_index(int row)
+{
+ int i;
+
+ for (i = 0; i <= MAX_NUM_ROWS; i++) {
+ if (sdw_index_row_mapping[i].row == row)
+ return sdw_index_row_mapping[i].index;
+ }
+
+ return 0; /* Lowest Row number = 48 */
+}
+
+/**
+ * sdw_program_slv_xport_params: Programs Slave transport registers on
+ * alternate bank (bank currently unused). This API is called by
+ * sdw_program_xport_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @slv_rt: Slave runtime handle.
+ * @t_slv_params: Transport parameters to be configured.
+ * @p_slv_params: Port parameters to be configured.
+ */
+static int sdw_program_slv_xport_params(struct sdw_bus *sdw_bus,
+ struct sdw_slv_runtime *slv_rt,
+ struct sdw_transport_params *t_slv_params,
+ struct sdw_port_params *p_slv_params)
+{
+ struct sdw_msg wr_msg, wr_msg1, wr_msg2, wr_msg3, rd_msg;
+ struct sdw_slave_caps *caps = &slv_rt->slv->priv.caps;
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ int ret;
+ int bank_to_use, type;
+ u16 addr, len;
+ u8 wbuf[SDW_BUF_SIZE3] = {0, 0, 0};
+ u8 wbuf1[SDW_BUF_SIZE4] = {0, 0, 0, 0};
+ u8 wbuf2[SDW_BUF_SIZE1] = {0};
+ u8 wbuf3[SDW_BUF_SIZE2] = {0, 0};
+ u8 rbuf[SDW_BUF_SIZE1] = {0};
+ struct sdw_dpn_caps *dpn_cap;
+
+ dpn_cap = sdw_get_slv_dpn_cap(caps, slv_rt->direction,
+ t_slv_params->port_num);
+ if (!dpn_cap)
+ return -EINVAL;
+
+ /* Get port capability info */
+ type = dpn_cap->type;
+
+ /*
+ * Optimization scope: Reduce number of writes on the bus.
+ * Mirroring should be considered.
+ */
+
+ /*
+ * Fill buffer contents for all messages.
+ * 1. wr_msg holds values to program blockctrl2, samplectrl1 and
+ * samplectrl2 registers.
+ * 2. wr_msg1 holds values to program offset_ctrl1, offset_ctrl2,
+ * hctrl and blockctrl3 registers.
+ * 3. wr_msg2 holds values to program lanectrl register.
+ * 4. wr_msg3 holds values to program portctrl and blockctrl1
+ * register. If the Slave port(s) doesn't implement block group,
+ * then blockctrl2 and blockctrl3 registers are not programmed.
+ * Similarly if the Slave port(s) doesn't support lane control, then
+ * lanectrl register is not programmed.
+ */
+
+ /* Fill DPN_BlockCtrl2 value */
+ wbuf[0] = t_slv_params->blk_grp_ctrl;
+
+ /* Fill DPN_SampleCtrl1 value */
+ wbuf[1] = (t_slv_params->sample_interval - 1) &
+ SDW_DPN_SAMPLECTRL1_LOW_MASK;
+
+ /* Fill DPN_SampleCtrl2 register value */
+ wbuf[2] = ((t_slv_params->sample_interval - 1) &
+ SDW_DPN_SAMPLECTRL2_LOW_MASK) >>
+ SDW_DPN_SAMPLECTRL2_SHIFT;
+
+ /* Fill DPN_OffsetCtrl1 register value */
+ wbuf1[0] = t_slv_params->offset1;
+
+ /* Fill DPN_OffsetCtrl1 register value */
+ wbuf1[1] = t_slv_params->offset2;
+
+ /* Fill DPN_HCtrl register value */
+ wbuf1[2] = (t_slv_params->hstop |
+ (t_slv_params->hstart << SDW_DPN_HCTRL_HSTART_SHIFT));
+
+ /* Fill DPN_BlockCtrl3 register value */
+ wbuf1[3] = t_slv_params->blk_pkg_mode;
+
+ /* Fill DPN_LaneCtrl register value */
+ wbuf2[0] = t_slv_params->lane_ctrl;
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ addr = SDW_DPN_PORTCTRL +
+ (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num);
+
+ /* Transfer message to read port_ctrl Slave register */
+ ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf,
+ slv_rt->slv->dev_num, sdw_mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave port_ctrl reg read failed\n");
+ goto out;
+ }
+
+ /* Fill DP0_PortCtrl register value */
+ wbuf3[0] = (p_slv_params->flow_mode | (p_slv_params->data_mode <<
+ SDW_DPN_PORTCTRL_PORTDATAMODE_SHIFT) | (rbuf[0]));
+
+ /* Fill DP0_BlockCtrl1 register value */
+ wbuf3[1] = (p_slv_params->bps - 1);
+
+ addr = ((SDW_DPN_BLOCKCTRL2 +
+ (1 * (!t_slv_params->blk_grp_ctrl_valid)) +
+ (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) +
+ (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num));
+
+ if (type == SDW_DP_TYPE_FULL)
+ len = (SDW_BUF_SIZE2 +
+ (1 * (t_slv_params->blk_grp_ctrl_valid)));
+ else
+ len = (SDW_BUF_SIZE1 +
+ (1 * (t_slv_params->blk_grp_ctrl_valid)));
+
+ ret = sdw_wr_msg(&wr_msg, false, addr, len,
+ &wbuf[0 + (1 * (!t_slv_params->blk_grp_ctrl_valid))],
+ slv_rt->slv->dev_num,
+ sdw_mstr, SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave block_ctrl2/sample_ctrl1/2 reg write failed\n");
+ goto out;
+ }
+
+ /* Create write message wr_msg1 to program transport Slave register */
+ addr = ((SDW_DPN_OFFSETCTRL1 +
+ (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) +
+ (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num));
+
+ if (type == SDW_DP_TYPE_FULL)
+ len = SDW_BUF_SIZE4;
+ else
+ len = SDW_BUF_SIZE1;
+ ret = sdw_wr_msg(&wr_msg1, false, addr, len, &wbuf1[0],
+ slv_rt->slv->dev_num, sdw_mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave offset_ctrl1/2/h_ctrl/block_ctrl2 reg write failed\n");
+ goto out;
+ }
+
+ if (caps->lane_control_support) {
+ wr_msg2.addr = ((SDW_DPN_HCTRL +
+ (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) +
+ (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num));
+ ret = sdw_wr_msg(&wr_msg2, false, addr, SDW_BUF_SIZE1,
+ wbuf2, slv_rt->slv->dev_num,
+ sdw_mstr, SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave lane_ctrl reg write failed\n");
+ goto out;
+ }
+ }
+
+ addr = SDW_DPN_PORTCTRL +
+ (SDW_NUM_DATA_PORT_REGISTERS * t_slv_params->port_num);
+
+ ret = sdw_wr_msg(&wr_msg3, false, addr, SDW_BUF_SIZE2, &wbuf3[0],
+ slv_rt->slv->dev_num, sdw_mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave port_ctrl/block_ctrl1 reg write failed\n");
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * sdw_program_mstr_xport_params: Programs Master transport parameters
+ * registers on alternate bank (bank currently unused). This API is
+ * called by sdw_program_xport_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @t_mstr_params: Transport parameters to be configured.
+ * @p_mstr_params: Port parameters to be configured.
+ */
+static int sdw_program_mstr_xport_params(struct sdw_bus *sdw_bus,
+ struct sdw_transport_params *t_mstr_params,
+ struct sdw_port_params *p_mstr_params)
+{
+ struct sdw_master_driver *ops = sdw_bus->mstr->driver;
+ int bank_to_use, ret;
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ /* Perform Master transport parameters API call */
+ ret = ops->port_ops->dpn_set_port_transport_params(sdw_bus->mstr,
+ t_mstr_params, bank_to_use);
+ if (ret < 0)
+ return ret;
+
+ /* Perform Master port parameters API call */
+ ret = ops->port_ops->dpn_set_port_params(sdw_bus->mstr,
+ p_mstr_params, bank_to_use);
+ if (ret < 0)
+ return ret;
+
+ return ret;
+}
+
+/**
+ * sdw_program_xport_params: Programs transport parameters of Master and
+ * Slave registers. This function calls individual Master and Slave API
+ * to configure transport and port parameters. This API is called by
+ * sdw_program_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ */
+static int sdw_program_xport_params(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt)
+{
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_port_runtime *port_rt, *port_slv_rt;
+ struct sdw_transport_params *t_params, *t_slv_params;
+ struct sdw_port_params *p_params, *p_slv_params;
+ int ret = 0;
+
+ /*
+ * Check stream state before programming transport parameters There
+ * are two flows in which transport parameters are programmed.
+ * 1. For new stream enabling, no stream state check required.
+ * 2. For active streams enabling, stream state check is required.
+ * For second flow, transport parameters will be only programmed if
+ * stream is in de-prepare state. It applies for both Master and
+ * Slave.
+ */
+
+ if (sdw_mstr_rt->sdw_rt->stream_state == SDW_STATE_STRM_DEPREPARE)
+ return 0;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt,
+ &sdw_mstr_rt->slv_rt_list, slave_mstr_node) {
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list,
+ port_node) {
+
+ /* Transport and port parameters for Slave */
+ t_slv_params = &port_slv_rt->transport_params;
+ p_slv_params = &port_slv_rt->port_params;
+
+ /* Assign port parameters */
+ p_slv_params->num = port_slv_rt->port_num;
+ p_slv_params->bps = slv_rt->stream_params.bps;
+
+ /*
+ * TODO: Currently only Isochronous mode supported,
+ * asynchronous support to be added.
+ */
+
+ /* Isochronous Mode */
+ port_slv_rt->port_params.flow_mode =
+ SDW_PORT_FLOW_MODE_ISOCH;
+
+ /* Normal Mode */
+ port_slv_rt->port_params.data_mode =
+ SDW_PORT_DATA_MODE_NORMAL;
+
+ /* Program transport & port parameters for Slave */
+ ret = sdw_program_slv_xport_params(sdw_bus, slv_rt,
+ t_slv_params, p_slv_params);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ /* Iterate for all Master port(s) in port list */
+ list_for_each_entry(port_rt,
+ &sdw_mstr_rt->port_rt_list, port_node) {
+
+ /* Transport and port parameters for Master*/
+ t_params = &port_rt->transport_params;
+ p_params = &port_rt->port_params;
+
+ /* Assign port parameters */
+ p_params->num = port_rt->port_num;
+ p_params->bps = sdw_mstr_rt->stream_params.bps;
+
+ /*
+ * TODO: Currently only Isochronous mode supported,
+ * asynchronous support to be added.
+ */
+
+ /* Isochronous Mode */
+ p_params->flow_mode = SDW_PORT_FLOW_MODE_ISOCH;
+
+ /* Normal Mode */
+ p_params->data_mode = 0x0;
+
+ /* Program transport & port parameters for Slave */
+ ret = sdw_program_mstr_xport_params(sdw_bus, t_params,
+ p_params);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_enable_disable_slv_ports: Enable & disables port(s) channel(s) for
+ * Slave(s). The Slave(s) port(s) channel(s) are enable or disabled on
+ * alternate bank (bank currently unused). This API is called by
+ * sdw_enable_disable_ports.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_slv_rt: Runtime Slave handle.
+ * @port_slv_rt: Runtime port handle.
+ * @chn_en: Enable or disable the channel.
+ */
+static int sdw_enable_disable_slv_ports(struct sdw_bus *sdw_bus,
+ struct sdw_slv_runtime *sdw_slv_rt,
+ struct sdw_port_runtime *port_slv_rt,
+ bool chn_en)
+{
+ struct sdw_msg wr_msg, rd_msg;
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ int ret;
+ int bank_to_use;
+ u16 addr;
+ u8 wbuf[SDW_BUF_SIZE1] = {0};
+ u8 rbuf[SDW_BUF_SIZE1] = {0};
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ /* Get channel enable register address for Slave */
+ addr = ((SDW_DPN_CHANNELEN +
+ (SDW_BANK1_REGISTER_OFFSET * bank_to_use)) +
+ (SDW_NUM_DATA_PORT_REGISTERS *
+ port_slv_rt->port_num));
+
+ /* Transfer message to read channel enable Slave register */
+ ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf,
+ sdw_slv_rt->slv->dev_num,
+ sdw_bus->mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&sdw_mstr->dev,
+ "Channel enable read failed\n");
+ return -EINVAL;
+ }
+
+ if (chn_en)
+ wbuf[0] = (rbuf[0] | port_slv_rt->channel_mask);
+ else
+ wbuf[0] = (rbuf[0] & ~(port_slv_rt->channel_mask));
+
+ /* Transfer message to write channel enable Slave register */
+ ret = sdw_wr_msg(&wr_msg, false, addr, SDW_BUF_SIZE1, wbuf,
+ sdw_slv_rt->slv->dev_num,
+ sdw_bus->mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&sdw_mstr->dev,
+ "Channel enable write failed\n");
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_enable_disable_mstr_ports: Enable & disables port(s) channel(s) for
+ * Master. The Master port(s) channel(s) are enable or disabled on
+ * alternate bank (bank currently unused). This API is called by
+ * sdw_enable_disable_ports.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @port_slv_rt: Runtime port handle.
+ * @chn_en: Operations to be performed.
+ */
+static int sdw_enable_disable_mstr_ports(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_port_runtime *port_rt,
+ bool chn_en)
+{
+ struct sdw_master_driver *ops = sdw_bus->mstr->driver;
+ struct sdw_enable_ch enable_ch;
+ int bank_to_use, ret = 0;
+
+ /* Fill enable_ch data structure with values */
+ enable_ch.num = port_rt->port_num;
+ enable_ch.ch_mask = port_rt->channel_mask;
+ enable_ch.enable = chn_en; /* Enable/Disable */
+
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ /* Perform Master port(s) channel(s) enable/disable API call */
+ if (ops->port_ops->dpn_port_enable_ch) {
+ ret = ops->port_ops->dpn_port_enable_ch(sdw_bus->mstr,
+ &enable_ch, bank_to_use);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_enable_disable_ports: Enable/disable port(s) channel(s) for Master
+ * and Slave. This function calls individual API's of Master and Slave
+ * respectively to perform enable or disable operation. This API is
+ * called by sdw_program_params, sdw_update_bus_params_ops and
+ * sdw_disable_op.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_rt: Runtime stream handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @chn_en: Operations to be performed.
+ */
+static int sdw_enable_disable_ports(struct sdw_bus *sdw_bus,
+ struct sdw_runtime *sdw_rt,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ bool chn_en)
+{
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_mstr_runtime *mstr_rt = NULL;
+ struct sdw_port_runtime *port_slv, *port_mstr;
+ int ret = 0;
+
+ /*
+ * There are two flows in which channels are enabled and disabled.
+ * 1. For new stream enabling/disabling, no stream state check
+ * required.
+ * 2. For active streams enabling/disabling, stream state check is
+ * required.
+ * Currently goto is used in API to select above operation
+ * TODO: Avoid usage of goto statement
+ */
+ if (sdw_mstr_rt == NULL)
+ goto sdw_rt_ops;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list,
+ slave_mstr_node) {
+
+ /*
+ * Do not perform enable/disable operation if stream is in
+ * ENABLE state.
+ */
+ if (slv_rt->sdw_rt->stream_state == SDW_STATE_STRM_ENABLE) {
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_slv, &slv_rt->port_rt_list,
+ port_node) {
+
+ /*
+ * Enable/Disable Slave port(s) channel(s)
+ */
+ ret = sdw_enable_disable_slv_ports(sdw_bus,
+ slv_rt, port_slv, chn_en);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ }
+
+ /*
+ * Do not perform enable/disable operation if stream is in ENABLE
+ * state.
+ */
+ if (sdw_mstr_rt->sdw_rt->stream_state == SDW_STATE_STRM_ENABLE) {
+
+
+ /* Iterate for all Master port(s) in port list */
+ list_for_each_entry(port_mstr,
+ &sdw_mstr_rt->port_rt_list, port_node) {
+
+ /* Enable/Disable Master port(s) channel(s) */
+ ret = sdw_enable_disable_mstr_ports(sdw_bus,
+ sdw_mstr_rt, port_mstr, chn_en);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+sdw_rt_ops:
+
+ /* Enable/Disable operation based on stream */
+ if (sdw_rt == NULL)
+ return ret;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &sdw_rt->slv_rt_list, slave_strm_node) {
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_slv, &slv_rt->port_rt_list,
+ port_node) {
+
+ /* Enable/Disable Slave port(s) channel(s) */
+ ret = sdw_enable_disable_slv_ports(sdw_bus, slv_rt,
+ port_slv, chn_en);
+ if (ret < 0)
+ return ret;
+
+ }
+ }
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(mstr_rt, &sdw_rt->mstr_rt_list, mstr_strm_node) {
+
+ /* Iterate for all Master port(s) in port list */
+ list_for_each_entry(port_mstr, &mstr_rt->port_rt_list,
+ port_node) {
+
+ /* Enable/Disable Master port(s) channel(s) */
+ ret = sdw_enable_disable_mstr_ports(sdw_bus, mstr_rt,
+ port_mstr, chn_en);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_check_slv_clock_cap: Slave capabilities are checked for each clock
+ * computed. If Slave support given clock, it returns true else false.
+ * This API is called by sdw_compute_bus_params.
+ *
+ * @mode_prop: Port properties.
+ * @clock_reqd: clock rate.
+ *
+ * Returns true if clock rate is OK else false.
+ */
+static bool sdw_check_slv_clock_cap(struct sdw_port_aud_mode_prop *mode_prop,
+ int clock_reqd)
+{
+
+ int value = 0, j;
+ bool clock_ok = false;
+
+ /*
+ * Slave(s) can provide supported clock rates or minimum/maximum
+ * range. First check for clock rates, if not available then check
+ * with minimum/maximum range.
+ */
+ if (mode_prop->num_bus_freq_cfgs) {
+ /* Run loop for all clock rates */
+ for (j = 0; j < mode_prop->num_bus_freq_cfgs; j++) {
+ value = mode_prop->clk_freq_buf[j];
+ if (clock_reqd == value) {
+ clock_ok = true;
+ break;
+ }
+ if (j == mode_prop->num_bus_freq_cfgs) {
+ clock_ok = false;
+ break;
+ }
+
+ }
+
+ } else {
+ if ((clock_reqd < mode_prop->min_bus_freq) ||
+ (clock_reqd > mode_prop->max_bus_freq))
+ clock_ok = false;
+ else
+ clock_ok = true;
+ }
+
+ return clock_ok;
+}
+
+/**
+ * sdw_compute_bus_params: Based on the bandwidth computed per bus, clock
+ * and frame shape required for bus is calculate. Each clock frequency
+ * selected is checked with all the Slave(s) capabilities in use on
+ * bus. Based on frame shape, frame interval is also computed. This API
+ * is called by sdw_compute_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @frame_int: Return frame interval computed.
+ * @sdw_mstr_rt: Runtime Master handle.
+ */
+static int sdw_compute_bus_params(struct sdw_bus *sdw_bus, int *frame_int,
+ struct sdw_mstr_runtime *sdw_mstr_rt)
+{
+ struct sdw_master_caps *sdw_mstr_cap = NULL;
+ struct sdw_dpn_caps *sdw_slv_dpn_cap = NULL;
+ struct sdw_port_aud_mode_prop *mode_prop = NULL;
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_port_runtime *port_slv_rt = NULL;
+ unsigned int double_rate_freq, clock_reqd;
+ int i, rc, num_clk_gears, gear;
+ int frame_interval = 0, frame_frequency = 0;
+ int sel_row = 0, sel_col = 0, pn = 0;
+ bool clock_ok = false;
+ struct sdw_slave_caps *caps;
+
+ /* Get Master capabilities handle */
+ sdw_mstr_cap = &sdw_bus->mstr->caps;
+
+ /*
+ * Note:
+ * Below loop is executed using number of clock gears supported.
+ * TODO: Need to add support further if clock to be computed using
+ * number of clock frequencies.
+ */
+ num_clk_gears = sdw_mstr_cap->num_clk_gears;
+ if (!num_clk_gears)
+ return -EINVAL;
+
+ /* Double clock rate */
+ double_rate_freq = sdw_mstr_cap->max_clk_freq * SDW_DOUBLE_RATE_FACTOR;
+
+ /*
+ * Find nearest clock frequency needed by bus for given bandwidth
+ */
+ for (i = 0; i < num_clk_gears; i++) {
+
+ gear = sdw_mstr_cap->clk_gears[i];
+
+ /* TODO: Explanation for SDW_FREQ_MOD_FACTOR */
+ if (((double_rate_freq / gear) <= sdw_bus->bandwidth) ||
+ (((double_rate_freq / gear) %
+ SDW_FREQ_MOD_FACTOR) != 0))
+ continue;
+
+ /* Selected clock frequency */
+ clock_reqd = sdw_mstr_cap->max_clk_freq / gear;
+
+ /*
+ * Check all the Slave(s) device capabilities here and find
+ * whether given clock rate is supported by all Slaves
+ */
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list,
+ slave_mstr_node) {
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list,
+ port_node) {
+
+ /* Get port number */
+ pn = port_slv_rt->port_num;
+ caps = &slv_rt->slv->priv.caps;
+
+ /* Get port capabilities */
+ sdw_slv_dpn_cap = sdw_get_slv_dpn_cap(caps,
+ slv_rt->direction, pn);
+ if (!sdw_slv_dpn_cap)
+ return -EINVAL;
+ mode_prop = sdw_slv_dpn_cap->mode_properties;
+
+ /*
+ * Perform slave capabilities check API call
+ * where current selected clock is checked
+ * against clock rates supported by Slave(s)
+ */
+ clock_ok = sdw_check_slv_clock_cap(mode_prop,
+ clock_reqd);
+ /*
+ * Don't check next Slave port capabilities,
+ * go for next clock frequency
+ */
+ if (!clock_ok)
+ break;
+ }
+
+ /* Go for next clock frequency */
+ if (!clock_ok)
+ break;
+ }
+
+ /* None of clock rate matches, return error */
+ if (i == num_clk_gears)
+ return -EINVAL;
+
+ /* check for next clock divider */
+ if (!clock_ok)
+ continue;
+
+ /*
+ * At this point clock rate and clock gear is computed, now
+ * find frame shape for bus.
+ */
+ for (rc = 0; rc < MAX_NUM_ROW_COLS; rc++) {
+
+ /* Compute frame interval and frame frequency */
+ frame_interval =
+ snd_sdw_core.row_col_pair[rc].row *
+ snd_sdw_core.row_col_pair[rc].col;
+ frame_frequency =
+ (double_rate_freq/gear)/frame_interval;
+
+ /* Run loop till frame shape is OK */
+ if (((double_rate_freq/gear) -
+ (frame_frequency *
+ snd_sdw_core.row_col_pair[rc].
+ control_bits)) <
+ sdw_bus->bandwidth)
+ continue;
+
+ break;
+ }
+
+ /* Valid frame shape not found, check for next clock freq */
+ if (rc == MAX_NUM_ROW_COLS)
+ continue;
+
+ /* Fill all the computed values in data structures */
+ sel_row = snd_sdw_core.row_col_pair[rc].row;
+ sel_col = snd_sdw_core.row_col_pair[rc].col;
+ sdw_bus->frame_freq = frame_frequency;
+ sdw_bus->curr_dr_clk_freq = double_rate_freq/gear;
+ sdw_bus->clk_div = gear;
+ clock_ok = false;
+ *frame_int = frame_interval;
+ sdw_bus->col = sel_col;
+ sdw_bus->row = sel_row;
+
+ break;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_compute_system_interval: This function computes system interval and
+ * stream interval per Master based on the current and active streams
+ * running on bus. This API is called by sdw_compute_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @frame_interval: Computed Frame interval.
+ */
+static int sdw_compute_system_interval(struct sdw_bus *sdw_bus,
+ int frame_interval)
+{
+ struct sdw_transport_params *t_params = NULL, *t_slv_params = NULL;
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_port_runtime *port_rt, *port_slv_rt;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_slv_runtime *slv_rt = NULL;
+ int lcmnum1 = 0, lcmnum2 = 0, lcmnum3 = 0, div = 0;
+ int sample_interval;
+
+ /*
+ * once bandwidth and frame shape for bus is found, run a loop for
+ * current and all the active streams on bus and compute stream
+ * interval & sample_interval.
+ */
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) {
+
+ /*
+ * Do not compute system and stream interval if stream state
+ * is in DEPREPARE
+ */
+ if (sdw_mstr_rt->sdw_rt->stream_state ==
+ SDW_STATE_STRM_DEPREPARE)
+ continue;
+
+ /* Calculate sample interval */
+ sample_interval = (sdw_bus->curr_dr_clk_freq/
+ sdw_mstr_rt->stream_params.rate);
+
+
+ /*
+ * Iterate for all Master port(s) in port list and assign
+ * sample interval per port.
+ */
+ list_for_each_entry(port_rt, &sdw_mstr_rt->port_rt_list,
+ port_node) {
+
+ /* Assign sample interval for each port */
+ t_params = &port_rt->transport_params;
+ t_params->sample_interval = sample_interval;
+ }
+
+ /* Calculate LCM */
+ lcmnum2 = sample_interval;
+
+ if (!lcmnum1)
+ lcmnum1 = lcm(lcmnum2, lcmnum2);
+ else
+ lcmnum1 = lcm(lcmnum1, lcmnum2);
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list,
+ slave_mstr_node) {
+
+ /*
+ * Iterate for all Slave port(s) in port list and
+ * assign sample interval per port.
+ */
+ list_for_each_entry(port_slv_rt, &slv_rt->port_rt_list,
+ port_node) {
+
+ /*
+ * Assign sample interval for each port.
+ * Assumption is that sample interval per
+ * port for given Slave will be same.
+ */
+ t_slv_params = &port_slv_rt->transport_params;
+ t_slv_params->sample_interval = sample_interval;
+ }
+ }
+ }
+
+ /* Assign stream interval */
+ sdw_bus->stream_interval = lcmnum1;
+
+ /*
+ * If system interval already calculated, return successful, it can
+ * be case in pause/resume, under-run scenario.
+ */
+ if (sdw_bus->system_interval)
+ return 0;
+
+ /* Compute system_interval */
+ if (sdw_bus->curr_dr_clk_freq) {
+
+ /* Get divide value */
+ div = (sdw_bus->max_dr_clk_freq / sdw_bus->curr_dr_clk_freq);
+
+ /* Get LCM of stream interval and frame interval */
+ if ((lcmnum1) && (frame_interval))
+
+ lcmnum3 = lcm(lcmnum1, frame_interval);
+ else
+ return -EINVAL;
+
+ /* Fill computed system interval value */
+ sdw_bus->system_interval = ((div * lcmnum3) / frame_interval);
+ }
+
+ /*
+ * Something went wrong while computing system interval may be lcm
+ * value may be 0, return error accordingly.
+ */
+ if (!sdw_bus->system_interval)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * sdw_compute_slv_xport_params: This function performs transport parameters
+ * computation of all Slave port(s) for all current and active streams.
+ * This API is called by sdw_compute_xport_params function.
+ *
+ * @sdw_bus: Bus handle.
+ */
+static int sdw_compute_slv_xport_params(struct sdw_bus *sdw_bus)
+{
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_mstr_runtime *sdw_mstr_rt;
+ struct sdw_slv_runtime *slv_rt = NULL;
+ struct sdw_port_runtime *port_rt;
+ int no_of_chan = 0;
+ int port_bo, bps;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_mstr->mstr_rt_list, mstr_node) {
+
+ /* Get block offset from Master runtime */
+ port_bo = sdw_mstr_rt->bus_rt.block_offset;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &sdw_mstr_rt->slv_rt_list,
+ slave_mstr_node) {
+
+ /*
+ * Do not compute any transport parameters if stream
+ * state is in DEPREPARE
+ */
+ if (slv_rt->sdw_rt->stream_state ==
+ SDW_STATE_STRM_DEPREPARE)
+ continue;
+
+ /* Get bps value */
+ bps = slv_rt->stream_params.bps;
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_rt, &slv_rt->port_rt_list,
+ port_node) {
+
+ /*
+ * Get no. of channels running on current
+ * port.
+ */
+ no_of_chan = sdw_chn_mask_to_chn(
+ port_rt->channel_mask);
+
+ /*
+ * Fill computed transport parameters for
+ * current port.
+ */
+ sdw_fill_xport_params(&port_rt->
+ transport_params,
+ port_rt->port_num,
+ true,
+ SDW_BLK_GRP_CNT_1,
+ port_bo,
+ port_bo >> 8,
+ sdw_mstr_rt->bus_rt.hstart,
+ sdw_mstr_rt->bus_rt.hstop,
+ (SDW_BLK_GRP_CNT_1 * no_of_chan), 0x0);
+
+ /*
+ * Increment block offset for next
+ * port/Slave
+ */
+ port_bo += bps * no_of_chan;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_compute_mstr_xport_params: This function performs transport
+ * parameters computation of all Master port(s) for all current and
+ * active streams. This API is called by sdw_compute_xport_params
+ * function.
+ *
+ * @sdw_bus: Bus handle.
+ * @grp_prms: Group Params.
+ * @group_count: Holds group count.
+ */
+static int sdw_compute_mstr_xport_params(struct sdw_bus *sdw_bus,
+ struct sdw_group_params *grp_prms,
+ int group_count)
+{
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_port_runtime *port_rt;
+ int hstop = 0, hstart = 0;
+ int i, block_offset, port_bo, no_of_chan;
+ int rate, bps, chn;
+
+ /* Compute hstop */
+ hstop = sdw_bus->col - 1;
+
+ /* Run loop for all groups to compute transport parameters */
+ for (i = 0; i < group_count; i++) {
+
+ /* Port block offset */
+ port_bo = block_offset = 1;
+
+ /*
+ * Iterate for all Master(s) in Master list Find all the
+ * streams associated with current group.
+ */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_mstr->mstr_rt_list, mstr_node) {
+
+ /*
+ * Do not compute any transport parameters if stream
+ * state is in DEPREPARE
+ */
+ if (sdw_mstr_rt->sdw_rt->stream_state ==
+ SDW_STATE_STRM_DEPREPARE)
+ continue;
+
+ /* Get rate, bps, channel values */
+ rate = sdw_mstr_rt->stream_params.rate;
+ bps = sdw_mstr_rt->stream_params.bps;
+ chn = sdw_mstr_rt->stream_params.channel_count;
+
+ /* Check whether stream belongs to this group */
+ if (rate != grp_prms[i].rate)
+ continue;
+
+ /* Compute hstart and hstop for given Master handle */
+ sdw_mstr_rt->bus_rt.hstart = hstart =
+ hstop - grp_prms[i].hwidth + 1;
+ sdw_mstr_rt->bus_rt.hstop = hstop;
+
+ /*
+ * Iterate for all Master port(s) in port list
+ * Compute hstart, hstop, block offset for each
+ * port(s) of current Master handle.
+ */
+ list_for_each_entry(port_rt,
+ &sdw_mstr_rt->port_rt_list, port_node) {
+
+ /*
+ * Get no. of channels running on current
+ * port.
+ */
+ no_of_chan = sdw_chn_mask_to_chn(
+ port_rt->channel_mask);
+
+ /*
+ * Fill computed transport parameters for
+ * current port.
+ */
+ sdw_fill_xport_params(
+ &port_rt->transport_params,
+ port_rt->port_num,
+ true,
+ SDW_BLK_GRP_CNT_1,
+ port_bo,
+ port_bo >> 8,
+ hstart,
+ hstop,
+ (SDW_BLK_GRP_CNT_1 * no_of_chan), 0x0);
+
+ /* Fill Master runtime params only once. */
+ if (port_rt == list_first_entry(
+ &sdw_mstr_rt->port_rt_list,
+ struct sdw_port_runtime,
+ port_node)) {
+
+ /*
+ * While processing first node, make
+ * a copy of hstart, hstop, block
+ * offset in Master runtime data
+ * structure.
+ */
+ sdw_mstr_rt->bus_rt.hstart = hstart;
+ sdw_mstr_rt->bus_rt.hstop = hstop;
+ sdw_mstr_rt->bus_rt.block_offset
+ = port_bo;
+ sdw_mstr_rt->bus_rt.sub_block_offset
+ = 0;
+ }
+
+ /* Compute block offset for next port */
+ port_bo += bps * chn;
+ }
+
+ /*
+ * Compute block offset for next stream of same group
+ */
+ block_offset += bps * chn;
+
+ /*
+ * Re-assign port_bo for next stream under same
+ * group
+ */
+ port_bo = block_offset;
+ }
+
+ /* Compute hstop for next group */
+ hstop = hstop - grp_prms[i].hwidth;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_compute_group_params: This function performs calculation of full
+ * bandwidth, payload bandwidth and hwidth per group. This API is
+ * called by sdw_compute_xport_params function.
+ *
+ * @sdw_bus: Bus handle.
+ * @grp_prms: Group Params
+ * @stream_rate: Stream rate array;
+ * @group_count: Holds group count.
+ */
+static int sdw_compute_group_params(struct sdw_bus *sdw_bus,
+ struct sdw_group_params *grp_prms,
+ int *stream_rates,
+ int group_count)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ int sel_col = sdw_bus->col; /* Computed columns */
+ int column_needed = 0;
+ int i, rate, bps, chn;
+
+ /* Calculate full bandwidth per group */
+ for (i = 0; i < group_count; i++) {
+ grp_prms[i].rate = stream_rates[i];
+ grp_prms[i].full_bw = sdw_bus->curr_dr_clk_freq/
+ grp_prms[i].rate;
+ }
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) {
+
+ /*
+ * Do not compute any transport parameters if stream state
+ * is in DEPREPARE
+ */
+ if (sdw_mstr_rt->sdw_rt->stream_state ==
+ SDW_STATE_STRM_DEPREPARE)
+ continue;
+
+ /* Get rate, bps, channel values */
+ rate = sdw_mstr_rt->stream_params.rate;
+ bps = sdw_mstr_rt->stream_params.bps;
+ chn = sdw_mstr_rt->stream_params.channel_count;
+
+ /* Calculate payload bandwidth per group */
+ for (i = 0; i < group_count; i++) {
+ if (rate == grp_prms[i].rate)
+ grp_prms[i].payload_bw += bps * chn;
+ }
+ }
+
+ /* Calculate hwidth per group and total column needed per Master */
+ for (i = 0; i < group_count; i++) {
+ grp_prms[i].hwidth =
+ (sel_col * grp_prms[i].payload_bw +
+ grp_prms[i].full_bw - 1)/grp_prms[i].full_bw;
+ column_needed += grp_prms[i].hwidth;
+ }
+
+ /* Check column required should not be greater than selected columns */
+ if (column_needed > sel_col - 1)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * sdw_add_element_group_count: This function checks grouping already exist
+ * for given rate. If not, it adds new group in array for given rate.
+ * This API is called by sdw_get_group_count.
+ *
+ * @grp_cnt: Struture holding stream rate array information.
+ * @rate: Sampling frequency.
+ */
+static int sdw_add_element_group_count(struct sdw_group_count *grp_cnt,
+ unsigned int rate)
+{
+
+ int num = grp_cnt->group_count;
+ int i;
+
+ /* Run loop for number of groups already computed */
+ for (i = 0; i < num; i++) {
+
+ if (rate == grp_cnt->stream_rates[i])
+ break;
+
+ if (i == num) {
+
+ if (grp_cnt->group_count >= grp_cnt->max_size) {
+
+ /* Inc. max size by 1 element */
+ grp_cnt->max_size += 1;
+ grp_cnt->stream_rates = krealloc(
+ grp_cnt->stream_rates,
+ (sizeof(int) *
+ grp_cnt->max_size),
+ GFP_KERNEL);
+ if (!grp_cnt->stream_rates)
+ return -ENOMEM;
+ }
+
+ /* Add new stream rate */
+ grp_cnt->stream_rates[grp_cnt->group_count++] = rate;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_get_group_count: This function performs grouping of streams having
+ * stream sample rate and computes group count. This API is called by
+ * sdw_compute_xport_params.
+ *
+ * @sdw_bus: Bus handle.
+ * @grp_cnt: Struture holding stream rate array information.
+ */
+static int sdw_get_group_count(struct sdw_bus *sdw_bus,
+ struct sdw_group_count *grp_cnt)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_mstr_runtime *sdw_mstr_rt;
+ unsigned int curr_rate = 0;
+ int ret = 0;
+
+ /* Initialize temporary group count data structure */
+ grp_cnt->group_count = 0;
+ grp_cnt->max_size = SDW_STRM_RATE_GROUPING;
+ grp_cnt->stream_rates = kcalloc(grp_cnt->max_size, sizeof(int),
+ GFP_KERNEL);
+ if (!grp_cnt->stream_rates)
+ return -ENOMEM;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_mstr->mstr_rt_list, mstr_node) {
+
+ /*
+ * Do not compute any transport parameters if stream state
+ * is in DEPREPARE
+ */
+ if (sdw_mstr_rt->sdw_rt->stream_state ==
+ SDW_STATE_STRM_DEPREPARE)
+ continue;
+
+ /* Perform grouping of streams based on stream rate */
+
+ /* check for first entry from Master node list */
+ if (sdw_mstr_rt == list_first_entry(&sdw_mstr->mstr_rt_list,
+ struct sdw_mstr_runtime, mstr_node))
+ /* Add first entry */
+ grp_cnt->stream_rates[grp_cnt->group_count++] =
+ sdw_mstr_rt->stream_params.rate;
+
+ else {
+ curr_rate = sdw_mstr_rt->stream_params.rate;
+
+ ret = sdw_add_element_group_count(grp_cnt, curr_rate);
+ if (ret < 0)
+ return ret;
+
+ }
+ }
+
+ return ret;
+
+}
+
+/** sdw_compute_xport_params: This function computes transport parameters
+ * for port(s) of all current and active stream(s) on given Master.The
+ * function also computes transport parameters of all Slave(s) port(s)
+ * associated with given Master. This API is called from
+ * sdw_compute_params.
+ *
+ * @sdw_bus: Bus handle.
+ */
+static int sdw_compute_xport_params(struct sdw_bus *sdw_bus)
+{
+ struct sdw_group_params *grp_prms = NULL;
+ struct sdw_group_count grp_cnt;
+ int ret;
+
+ /*
+ * Perform grouping of streams based on stream sampling rate and get
+ * number of group count.
+ */
+ ret = sdw_get_group_count(sdw_bus, &grp_cnt);
+ if (ret < 0)
+ goto out;
+
+ /* Check for number of streams and number of group count */
+ if (grp_cnt.group_count == 0)
+ goto out;
+
+ /* Allocate resources holding temporary variables */
+ grp_prms = kzalloc((sizeof(*grp_prms) *
+ grp_cnt.group_count), GFP_KERNEL);
+ if (!grp_prms) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /*
+ * Since the grouping of streams are performed based on stream rate,
+ * hwidth, full bandwidth and cumulative payload bandwidth is
+ * computed for each group which is required to compute transport
+ * parameters.
+ */
+ ret = sdw_compute_group_params(sdw_bus, grp_prms,
+ &grp_cnt.stream_rates[0], grp_cnt.group_count);
+ if (ret < 0)
+ goto out;
+
+ /*
+ * Once all the group related computation is performed, transport
+ * parameters of Master(s) port(s) are computed
+ */
+ ret = sdw_compute_mstr_xport_params(sdw_bus, grp_prms,
+ grp_cnt.group_count);
+ if (ret < 0)
+ goto out;
+
+ /*
+ * Once all the Master port(s) transport port(s) are computed,
+ * transport parameters for Slave port(s) are computed.
+ */
+ ret = sdw_compute_slv_xport_params(sdw_bus);
+ if (ret < 0)
+ goto out;
+
+out:
+ /* Free up temporary resources */
+ kfree(grp_prms);
+ kfree(grp_cnt.stream_rates);
+
+ return ret;
+}
+
+/**
+ * sdw_bank_switch: This function programs frame control register and
+ * broadcast message on Slave broadcast address based on bank to be
+ * used. In normal mode, it checks for bank bank switch completion, in
+ * aggregation mode, it comes out without checking.
+ *
+ * @sdw_bus: Bus handle.
+ */
+static int sdw_bank_switch(struct sdw_bus *sdw_bus)
+{
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_msg *wr_msg;
+ int bank_to_use, numcol, numrow;
+ int link_mask = sdw_bus->mstr->link_sync_mask;
+ int ret = 0;
+ u16 addr;
+ u8 *wbuf = NULL;
+
+ /* Allocate and assign resources for bank switch message */
+ wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL);
+ if (!wr_msg) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ sdw_bus->data.msg = wr_msg;
+
+ wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL);
+ if (!wbuf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Get row and column index to program register */
+ numcol = sdw_find_col_index(sdw_bus->col);
+ numrow = sdw_find_row_index(sdw_bus->row);
+
+ /* Fill frame_ctrl register values */
+ wbuf[0] = numcol | (numrow << 3);
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ /* Create write message to write frame_ctrl register */
+ if (bank_to_use)
+ addr = (SDW_SCP_FRAMECTRL + SDW_BANK1_REGISTER_OFFSET);
+ else
+ addr = SDW_SCP_FRAMECTRL;
+
+ sdw_create_wr_msg(wr_msg, true, addr, SDW_BUF_SIZE1, wbuf,
+ SDW_SLAVE_BDCAST_ADDR);
+
+ /* Return without waiting from message to get transferred */
+ if (link_mask) {
+
+ /* This lock will be released once transfer is complete */
+ mutex_lock(&sdw_mstr->msg_lock);
+ /* Transfer message to write frame_ctrl register */
+ sdw_bank_switch_deferred(sdw_mstr, wr_msg, &sdw_bus->data);
+ } else {
+
+ /* Transfer message to write frame_ctrl register */
+ ret = snd_sdw_slave_transfer(sdw_mstr, wr_msg,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_mstr->dev, "Slave frame_ctrl reg write failed\n");
+ goto error;
+ }
+ }
+
+ if (!link_mask) {
+
+ /* Free up resources in case of normal bank switch flow */
+ kfree(sdw_bus->data.msg->buf);
+ kfree(sdw_bus->data.msg);
+ sdw_bus->data.msg = NULL;
+
+ /* Update active bank local variable */
+ sdw_bus->active_bank = bank_to_use;
+ }
+
+ return ret;
+
+error:
+ kfree(wr_msg);
+ kfree(wbuf);
+ return ret;
+}
+
+/**
+ * sdw_wait_for_bank_switch: This function waits for reply for bank switch
+ * operation performed in aggregation mode where bank switch operation
+ * returns without checking whether switch is successful or not. After
+ * performing all the post enable operations, this API is called from
+ * sdw_update_bus_params_ops to wait till band switch operation is
+ * complete.
+ *
+ * @sdw_bus: Bus handle.
+ */
+static int sdw_wait_for_bank_switch(struct sdw_bus *sdw_bus)
+{
+ struct sdw_master *mstr = sdw_bus->mstr;
+ unsigned long time_left;
+ int bank_to_use;
+
+ /* Check whether to perform wait operation */
+ if (sdw_bus->data.msg != NULL) {
+
+ /* Wait on completion for transfer complete */
+ time_left = wait_for_completion_timeout(
+ &sdw_bus->data.xfer_complete,
+ mstr->caps.bank_switch_timeout);
+
+ if (!time_left) {
+ dev_err(&mstr->dev, "Controller Timed out\n");
+ mutex_unlock(&mstr->msg_lock);
+ return -ETIMEDOUT;
+ }
+
+ /* Update active bank local variable */
+ bank_to_use = sdw_bus->active_bank;
+ sdw_bus->active_bank = !bank_to_use;
+
+ /* Free up resources */
+ kfree(sdw_bus->data.msg->buf);
+ kfree(sdw_bus->data.msg);
+
+ /* Release master lock */
+ mutex_unlock(&mstr->msg_lock);
+ }
+
+ return 0;
+}
+
+
+/**
+ * sdw_update_bus_params_to_slv: This function updates the new bus
+ * parameters to the Slave driver. New parameters will be in effect on
+ * next bank switch. This is to inform Slave driver to program any
+ * implementation defined registers based on new bus params to the
+ * alternate bank of Slave registers.
+ *
+ * mstr_rt: Master runtime list.
+ * bus_params: Bus params
+ */
+static int sdw_update_bus_params_to_slv(struct sdw_mstr_runtime *mstr_rt,
+ struct sdw_bus_params *bus_params)
+{
+ struct sdw_slave *slave;
+ struct sdw_slv_runtime *slv_rt;
+ int ret = 0;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(slv_rt, &mstr_rt->slv_rt_list,
+ slave_mstr_node) {
+ slave = slv_rt->slv;
+ if (slave->priv.driver->pre_bus_config)
+ ret = slave->priv.driver->pre_bus_config(slave,
+ bus_params);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_program_params: This function performs Master/Slave transport
+ * parameters register programming and also perform bus parameters SSP,
+ * clock gear, register programming. This API is called sdw_prepare_op,
+ * sdw_enable_op, sdw_disable_op and sdw_deprepare_op.
+ *
+ * @sdw_bus: Bus handle.
+ */
+static int sdw_program_params(struct sdw_bus *sdw_bus)
+{
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ bool chn_en;
+ struct sdw_bus_params bus_params;
+ struct sdw_master_driver *ops;
+ int bank_to_use;
+ int ret = 0;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_mstr->mstr_rt_list, mstr_node) {
+ /*
+ * Program transport and port parameters for Master and Slave
+ * ports.
+ */
+ ret = sdw_program_xport_params(sdw_bus, sdw_mstr_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Program transport parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Get Master driver operation handle */
+ ops = sdw_bus->mstr->driver;
+
+ /* Get current bank in use from bus structure */
+ bank_to_use = !sdw_bus->active_bank;
+
+ /* Program SSP interval API call */
+ if (ops->ops->set_ssp_interval) {
+ ret = ops->ops->set_ssp_interval(sdw_bus->mstr,
+ sdw_bus->system_interval,
+ bank_to_use);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Program SSP interval failed ret = %d\n", ret);
+ return ret;
+ }
+ }
+
+ /* Program clock gear API call */
+ bus_params.clk_freq =
+ (sdw_bus->curr_dr_clk_freq/SDW_DOUBLE_RATE_FACTOR);
+ bus_params.num_rows = sdw_bus->row;
+ bus_params.num_cols = sdw_bus->col;
+ bus_params.bank = bank_to_use;
+ if (ops->ops->set_bus_params)
+ ops->ops->set_bus_params(sdw_bus->mstr, &bus_params);
+
+ /* Update new bus params to all the Slaves */
+ ret = sdw_update_bus_params_to_slv(sdw_mstr_rt, &bus_params);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Update of bus params to Slaves failed ret = %d\n", ret);
+ return ret;
+ }
+ /*
+ * Enable port(s) channel(s) on alternate bank for all active
+ * streams.
+ */
+ chn_en = true;
+ ret = sdw_enable_disable_ports(sdw_bus, NULL, sdw_mstr_rt,
+ chn_en);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Enable channel failed ret = %d\n", ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_prep_deprep_slv_ports: This function prepares/de-prepares all the
+ * Slave port(s). It calls all individual APIs to perform:
+ * - Pre-prepare port(s) operation.
+ * - Prepare port(s) operation.
+ * - Post-prepare port(s) operation.
+ * This API is called from sdw_prep_deprep_ports.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_slv_rt: Runtime Slave handle.
+ * @prt_slv_strm: Runtime port handle.
+ * @prep: prepare or de-prepare operation.
+ */
+static int sdw_prep_deprep_slv_ports(struct sdw_bus *sdw_bus,
+ struct sdw_slv_runtime *sdw_slv_rt,
+ struct sdw_port_runtime *port_slv_rt,
+ bool prep)
+{
+ struct sdw_slave_driver *slv_ops = sdw_slv_rt->slv->priv.driver;
+ struct sdw_slave_caps *caps = &sdw_slv_rt->slv->priv.caps;
+ struct sdw_dpn_caps *sdw_slv_dpn_cap;
+ struct sdw_msg wr_msg, rd_msg;
+ struct sdw_prepare_ch prep_ch;
+ int ret = 0;
+ int bank_to_use;
+ u16 addr;
+ u8 wbuf[SDW_BUF_SIZE1] = {0};
+ u8 rbuf[SDW_BUF_SIZE1] = {0};
+ unsigned int time_left;
+
+ /* Get current bank in use from bus structure*/
+ bank_to_use = !sdw_bus->active_bank;
+
+ sdw_slv_dpn_cap = sdw_get_slv_dpn_cap(caps, sdw_slv_rt->direction,
+ port_slv_rt->port_num);
+ if (!sdw_slv_dpn_cap)
+ return -EINVAL;
+
+ /*
+ * Pre-prepare port(s) API call. There can be case that some Slave
+ * needs to perform some operations before preparing port(s).
+ */
+ prep_ch.num = port_slv_rt->port_num;
+ prep_ch.ch_mask = port_slv_rt->channel_mask;
+
+ if (prep)
+ prep_ch.prepare = true;
+ else
+ prep_ch.prepare = false;
+
+ prep_ch.bank = bank_to_use;
+ if (slv_ops->port_prep) {
+
+ ret = slv_ops->port_prep(sdw_slv_rt->slv, &prep_ch,
+ SDW_OPS_PORT_PRE_PREP);
+ if (ret < 0) {
+ dev_err(&sdw_bus->mstr->dev, "Slave Port Pre-Prepare failed ret = %d\n", ret);
+ goto out;
+ }
+ }
+
+ /* Prepare Slave port(s) operation */
+ if (sdw_slv_dpn_cap->prepare_ch == SDW_CP_MODE_NORMAL) {
+
+ addr = SDW_DPN_PREPARECTRL +
+ (SDW_NUM_DATA_PORT_REGISTERS * port_slv_rt->port_num);
+ /* Transfer message to read prepare_ctrl Slave register */
+ ret = sdw_rd_msg(&rd_msg, false, addr, SDW_BUF_SIZE1, rbuf,
+ sdw_slv_rt->slv->dev_num,
+ sdw_bus->mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_bus->mstr->dev,
+ "Slave prep_ctrl reg read failed\n");
+ goto out;
+ }
+
+ if (prep)
+ wbuf[0] = (rbuf[0] | port_slv_rt->channel_mask);
+ else
+ wbuf[0] = (rbuf[0] & ~(port_slv_rt->channel_mask));
+
+ /* Transfer message to write prepare_ctrl Slave register */
+ ret = sdw_wr_msg(&wr_msg, false, addr, SDW_BUF_SIZE1, wbuf,
+ sdw_slv_rt->slv->dev_num,
+ sdw_bus->mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&sdw_bus->mstr->dev,
+ "Slave prep_ctrl reg write failed\n");
+ goto out;
+ }
+
+ /* Wait for completion on port ready */
+ time_left = wait_for_completion_timeout(
+ &sdw_slv_rt->slv->priv.port_ready
+ [port_slv_rt->port_num],
+ sdw_slv_dpn_cap->ch_prep_timeout);
+
+ if (!time_left) {
+ dev_err(&sdw_bus->mstr->dev, "Timeout:Chn prep failed\n");
+ ret = -ETIMEDOUT;
+ goto out;
+ }
+ }
+
+ /*
+ * Post-prepare port(s) API call. There can be case that some Slave
+ * needs to perform some operations after preparing port(s).
+ */
+ if (slv_ops->port_prep) {
+ ret = slv_ops->port_prep(sdw_slv_rt->slv, &prep_ch,
+ SDW_OPS_PORT_POST_PREP);
+ if (ret < 0)
+ dev_err(&sdw_bus->mstr->dev, "Slave Port Post-Prepare failed ret = %d\n", ret);
+ }
+
+out:
+ return ret;
+}
+
+/**
+ * sdw_prep_deprep_mstr_ports: This function prepares/de-prepares all the
+ * Master port(s). It calls all individual APIs to perform:
+ * - Pre-prepare port(s) operation.
+ * - Prepare port(s) operation.
+ * - Post-prepare port(s) operation.
+ * This API is called from sdw_prep_deprep_ports.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @prt_slv_strm: Runtime port handle.
+ * @prep: prepare or de-prepare operation.
+ */
+static int sdw_prep_deprep_mstr_ports(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_port_runtime *port_rt,
+ bool prep)
+{
+ struct sdw_master_driver *ops = sdw_bus->mstr->driver;
+ struct sdw_prepare_ch prep_ch;
+ int ret = 0;
+
+ /*
+ * Fill prep_ch structure values with port number, channel mask and
+ * prepare/de-prepare flag value
+ */
+ prep_ch.num = port_rt->port_num;
+ prep_ch.ch_mask = port_rt->channel_mask;
+ prep_ch.prepare = prep; /* Prepare/De-prepare */
+ prep_ch.bank = !sdw_bus->active_bank;
+
+ /*
+ * Pre-prepare/Pre-deprepare port(s) API call. There can be case
+ * that some Master(s) needs to perform some operations before
+ * preparing/de-preparing port(s).
+ */
+ if (ops->port_ops->dpn_port_prep) {
+ ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch,
+ SDW_OPS_PORT_PRE_PREP);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Prepare/De-prepare API call */
+ if (ops->port_ops->dpn_port_prep) {
+ ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch,
+ SDW_OPS_PORT_PREP);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * Post-prepare/Post-deprepare port(s) API call. There can be case
+ * that some Master(s) needs to perform some operations after
+ * preparing/de-preparing port(s).
+ */
+ if (ops->port_ops->dpn_port_prep) {
+ ret = ops->port_ops->dpn_port_prep(sdw_bus->mstr, &prep_ch,
+ SDW_OPS_PORT_POST_PREP);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_prep_deprep_ports: This function calls individual APIs for
+ * prepare/de-prepare for all the Ports of all the Master(s) and
+ * Slave(s) associated with current stream. This API is called from
+ * sdw_prepare_op and sdw_deprepare_op.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_rt: Runtime stream handle.
+ */
+static int sdw_prep_deprep_ports(struct sdw_bus *sdw_bus,
+ struct sdw_runtime *sdw_rt,
+ bool is_prep)
+{
+ struct sdw_port_runtime *port_slv_rt, *port_rt;
+ struct sdw_slv_runtime *sdw_slv_rt = NULL;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ int ret = 0;
+
+ /* Iterate for all Slave(s) in Slave list */
+ list_for_each_entry(sdw_slv_rt,
+ &sdw_rt->slv_rt_list, slave_strm_node) {
+
+ /* Iterate for all Slave port(s) in port list */
+ list_for_each_entry(port_slv_rt,
+ &sdw_slv_rt->port_rt_list, port_node) {
+
+
+ /* Enable interrupt before Port prepare */
+ if (is_prep) {
+ ret = sdw_enable_disable_dpn_intr(
+ sdw_slv_rt->slv,
+ port_slv_rt->port_num,
+ sdw_slv_rt->direction,
+ is_prep);
+ if (ret < 0)
+ return ret;
+ }
+
+ /*
+ * Prepare/De-prepare API call for all Slave port(s)
+ */
+ ret = sdw_prep_deprep_slv_ports(sdw_bus,
+ sdw_slv_rt, port_slv_rt,
+ is_prep);
+ if (ret < 0)
+ return ret;
+
+ /* Disable interrupt after Port de-prepare */
+ if (!is_prep) {
+ ret = sdw_enable_disable_dpn_intr(
+ sdw_slv_rt->slv,
+ port_slv_rt->port_num,
+ sdw_slv_rt->direction,
+ is_prep);
+ if (ret < 0)
+ return ret;
+ }
+
+ }
+ }
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_rt->mstr_rt_list, mstr_strm_node) {
+
+ /* Iterate for all Master port(s) in port list */
+ list_for_each_entry(port_rt,
+ &sdw_mstr_rt->port_rt_list, port_node) {
+
+ /*
+ * Prepare/De-prepare API call for all Master
+ * port(s)
+ */
+ ret = sdw_prep_deprep_mstr_ports(sdw_bus,
+ sdw_mstr_rt, port_rt,
+ is_prep);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_check_strm_params: Validates all the received stream parameters with
+ * the Master capabilities on which current stream will be running.
+ * This API is called from sdw_prepare_op.
+ *
+ * @sdw_mstr_cap: Master capabilities.
+ * @mstr_params: Master PCM parameters.
+ * @stream_params: Stream PCM parameters.
+ *
+ * Returns 0 on success else appropriate error code.
+ */
+static int sdw_check_strm_params(struct sdw_master_caps *sdw_mstr_cap,
+ struct sdw_stream_params *mstr_params,
+ struct sdw_stream_params *stream_params)
+{
+ /*
+ * Note: Asynchronous mode not supported, return error in case
+ * stream is not operating in isochronous mode.
+ */
+ if ((sdw_mstr_cap->max_clk_freq % mstr_params->rate) != 0)
+ return -EINVAL;
+
+ /* Check for sampling frequency */
+ if (stream_params->rate != mstr_params->rate)
+ return -EINVAL;
+
+ return 0;
+}
+
+/**
+ * sdw_compute_params: This function calls individual API's for computing
+ * clock frequency, frame shape, frame frequency, SSP for bus and all
+ * transport/port port parameters for Master and Slave port(s). This
+ * API is called from sdw_prepare_op and sdw_deprepare_op.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ */
+static int sdw_compute_params(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_master_caps *sdw_mstr_cap = NULL;
+ int ret, frame_interval = 0;
+
+ /* Retrieve Master capabilities */
+ sdw_mstr_cap = &sdw_mstr->caps;
+
+ /*
+ * Compute bus parameters API Call. It computes clock frequency
+ * based on current bandwidth required for bus. It also computes
+ * frame shape and SoundWire frame frequency.
+ */
+ ret = sdw_compute_bus_params(sdw_bus, &frame_interval, sdw_mstr_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Compute bus parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Compute system interval API call. It computes system interval and
+ * stream interval for bus which is required for computing SSP.
+ */
+ ret = sdw_compute_system_interval(sdw_bus, frame_interval);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Compute system interval failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Compute transport parameters API call. It computes all the
+ * transport parameters for all Master(s) associated with current
+ * stream and all Slave(s) associated with Master(s).
+ */
+ ret = sdw_compute_xport_params(sdw_bus);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Compute transport parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+/**
+ * sdw_update_bus_params_ops: This API performs one of the following
+ * operation based on bus state. Called from sdw_update_bus_params API.
+ * - pre-enable port(s) channel(s).
+ * - bank switch operation.
+ * - post-enable port channel.
+ * - bank-switch wait operation.
+ * - disable port(s) channel(s) operation.
+ *
+ * @sdw_rt: Runtime stream handle.
+ * @bus_state: Operation to be performed.
+ */
+static int sdw_update_bus_params_ops(struct sdw_runtime *sdw_rt,
+ enum sdw_update_bus_ops bus_state)
+{
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_bus *sdw_bus = NULL;
+ struct sdw_master_ops *ops;
+ bool chn_en;
+ int ret = 0;
+
+ /* Iterate for all Master(s) in Master list */
+ list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ /* Get bus structure for Master */
+ sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr);
+ ops = sdw_bus->mstr->driver->ops;
+
+ /*
+ * Note that currently all the operations of aggregation
+ * mode are performed sequentially. The switch case is kept
+ * in order for code to scale where below operations can be
+ * performed or called in different order.
+ */
+ switch (bus_state) {
+
+ case SDW_BUS_PORT_PRE:
+
+ /* Pre-enable port(s) channel(s) API call */
+ if (ops->pre_bank_switch) {
+ ret = ops->pre_bank_switch(sdw_bus->mstr);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+
+ case SDW_BUS_BANK_SWITCH:
+
+ /* Bank-switch operation API call */
+ ret = sdw_bank_switch(sdw_bus);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case SDW_BUS_PORT_POST:
+
+ /* Post-enable port(s) channel(s) API call */
+ if (ops->post_bank_switch) {
+ ret = ops->post_bank_switch(sdw_bus->mstr);
+ if (ret < 0)
+ return ret;
+ }
+ break;
+
+ case SDW_BUS_BANK_SWITCH_WAIT:
+
+ /* Bank-switch wait operation API call */
+ ret = sdw_wait_for_bank_switch(sdw_bus);
+ if (ret < 0)
+ return ret;
+ break;
+
+ case SDW_BUS_PORT_DIS_CHN:
+
+ /*
+ * Disable channel on previous bank for all active
+ * stream(s)
+ */
+ chn_en = false;
+ ret = sdw_enable_disable_ports(sdw_bus, NULL,
+ sdw_mstr_rt, chn_en);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_update_bus_params: Once all the bus and port parameters are
+ * programmed, this function performs bank-switch where all the new
+ * configured parameters gets in effect on alternate.The bank-switch
+ * operation are different for normal and aggregation mode. In normal
+ * mode where only one Master is used for stream, bank-switch is
+ * performed directly followed by disabling channel on previous bank
+ * for active stream. In aggregation mode below flow is used.
+ * - pre-enable port(s) channel(s) operation.
+ * - bank switch operation.
+ * - post-enable port(s) channel(s) operation.
+ * - bank-switch wait operation.
+ * - disable port(s) channel(s) operation.
+ *
+ * @sdw_bus: Bus Handle.
+ * @sdw_rt: Runtime stream handle.
+ * @last_node: Boolean used in case of aggregation mode operation.
+ */
+static int sdw_update_bus_params(struct sdw_bus *sdw_bus,
+ struct sdw_runtime *sdw_rt,
+ bool last_node)
+{
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ int ret = 0;
+
+ /*
+ * The flow in this API is common for both aggregation and
+ * non-aggregation mode. Bus driver doesn't differentiate in both of
+ * the flows, however controller driver should take actions based
+ * normal or aggregation mode.
+ */
+
+ /* Check for last node */
+ if (!last_node)
+ return ret;
+
+ /*
+ * Perform pre-enable ports. There can be case that some of
+ * controller or Slave port(s) Channel(s) needs to perform some
+ * operation before enabling port(s) channel(s).
+ */
+ ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_PRE);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Pre-enable port(s) channel(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Bank-switch operation. Write is broadcast with new rows and
+ * columns programmed in frame_ctrl register
+ */
+ ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_BANK_SWITCH);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Bank switch operation failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Perform post-enable ports. There can be case that some of
+ * controller or Slave port(s) Channel(s) needs to perform some
+ * operation after enabling port(s) channel(s).
+ */
+ ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_POST);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Post-enable port failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Bank-switch post wait operation. In aggregation mode, bank-switch
+ * is performed in sync for all Master(s) in post_enable operation.
+ * This API call check whether bank switch is successful or not.
+ */
+ ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_BANK_SWITCH_WAIT);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Bank switch wait operation failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Disable port(s) channel(s) on previous bank for all active streams.
+ */
+ ret = sdw_update_bus_params_ops(sdw_rt, SDW_BUS_PORT_DIS_CHN);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Disable port(s) channel(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_check_last_node: Check for last node in the given list.
+ *
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @sdw_rt: Runtime stream handle.
+ *
+ * Returns true if given node is last node else false.
+ */
+static bool sdw_check_last_node(struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_mstr_runtime *last_rt = NULL;
+
+ /* Get last entry from given list */
+ last_rt = list_last_entry(&sdw_rt->mstr_rt_list,
+ struct sdw_mstr_runtime, mstr_strm_node);
+ if (sdw_mstr_rt == last_rt)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * sdw_deprepare_op: perform all operations required to de-prepare port(s).
+ * Below is the sequence.
+ * - De-prepare port(s) for current stream.
+ * - Compute bus parameters.
+ * - Program bus parameters.
+ * - Update bus parameters. Switch to alternate bank.
+ * - Change stream state.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @sdw_rt: Runtime stream handle.
+ */
+static int sdw_deprepare_op(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_runtime *sdw_rt)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_stream_params *mstr_params;
+ bool last_node = false;
+ int ret;
+
+ /*
+ * Check whether current Master handle is last in the list This is
+ * needed because some operations in sdw_update_bus_params are only
+ * performed when all Master(s)/Slave(s) processing is done. Also
+ * the stream state is changed after all Master(s)/Slave(s)
+ * processing is done.
+ */
+ last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt);
+ mstr_params = &sdw_mstr_rt->stream_params;
+
+ /*
+ * De-prepare port(s) for Master and Slave associated with current
+ * stream
+ */
+ ret = sdw_prep_deprep_ports(sdw_bus, sdw_rt, false);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "De-prepare port(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Calculate cumulative bus bandwidth */
+ sdw_mstr_rt->bus_rt.stream_bw = mstr_params->rate *
+ mstr_params->channel_count * mstr_params->bps;
+ sdw_bus->bandwidth -= sdw_mstr_rt->bus_rt.stream_bw;
+
+ /* Perform error check on bus cumulative bandwidth */
+ if (sdw_bus->bandwidth < 0) {
+ dev_err(&sdw_mstr->dev, "Bandwidth calculation failed\n");
+ return -EINVAL;
+ }
+
+ /* Check for any active stream on current bus handle */
+ if (sdw_bus->bandwidth == 0) {
+
+ sdw_bus->system_interval = 0;
+ sdw_bus->stream_interval = 0;
+ sdw_bus->frame_freq = 0;
+
+ /* Change stream state to DEPREPARE */
+ if (last_node)
+ sdw_rt->stream_state = SDW_STATE_STRM_DEPREPARE;
+
+ /*
+ * No active stream on current bus handle, return
+ * successfully
+ */
+ return 0;
+ }
+
+ /*
+ * Compute bus parameters and transport parameters for current
+ * Master handle and the Slave(s) associated with it. Bus parameters
+ * includes computation of clock, frame shape, frame frequency, SSP
+ * and transport parameters includes computation of hstart, hstop,
+ * blockoffset, subblockoffset, blockpackingmode, lanecontrol etc.
+ */
+ ret = sdw_compute_params(sdw_bus, sdw_mstr_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Parameter computation failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Program bus parameters and transport parameters for current
+ * Master handle and Slave(s) associated with it. Bus parameters
+ * includes programming clock gear, SSP registers. Transport
+ * parameters includes programming registers for hstart, hstop,
+ * blockoffset, subblockoffset, blockpackingmode, lanecontrol,
+ * SampleInterval, etc.
+ */
+ ret = sdw_program_params(sdw_bus);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Transport parameters configuration failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Update bus parameters which is basically bank switch operation
+ * where bus switches from current bank to alternate bank where new
+ * programmed values take effect. Bank switch operation can be
+ * performed for individual bus or for multiple bus in sync based on
+ * stream configuration.
+ */
+ ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Change stream state to DEPREPARE */
+ if (last_node)
+ sdw_rt->stream_state = SDW_STATE_STRM_DEPREPARE;
+
+ return ret;
+}
+
+/**
+ * sdw_disable_op: perform all operations required to disable port(s)
+ * channel(s). Below is sequence.
+ * - Disable Master/Slave port(s) channel(s) for current stream.
+ * - Program bus parameters.
+ * - Update bus parameters. Switch to alternate bank.
+ * - Change stream state.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @sdw_rt: Runtime stream handle.
+ */
+static int sdw_disable_op(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_runtime *sdw_rt)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_master_caps *sdw_mstr_cap = NULL;
+ struct sdw_stream_params *mstr_params;
+ bool chn_en;
+ bool last_node = false;
+ int ret;
+
+ /*
+ * Check whether current Master handle is last in the list This is
+ * needed because some operations in sdw_update_bus_params are only
+ * performed when all Master(s)/Slave(s) processing is done. Also
+ * the stream state is changed after all Master(s)/Slave(s)
+ * processing is done.
+ */
+ last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt);
+
+ /* Retrieve Master capabilities and stream parameters */
+ sdw_mstr_cap = &sdw_bus->mstr->caps;
+ mstr_params = &sdw_mstr_rt->stream_params;
+
+ /*
+ * Disable port(s) channel(s) for Master(s) and Slave(s) associated
+ * with current stream
+ */
+ chn_en = false;
+ ret = sdw_enable_disable_ports(sdw_bus, sdw_rt, NULL, chn_en);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Disable port(s) channel(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Program bus parameters and transport parameters for current
+ * Master handle and Slave(s) associated with it. Bus parameters
+ * includes programming clock gear, SSP registers. Transport
+ * parameters includes programming registers for hstart, hstop,
+ * blockoffset, subblockoffset, blockpackingmode, lanecontrol, etc.
+ */
+ ret = sdw_program_params(sdw_bus);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Update bus parameters which is basically bank switch operation
+ * where bus switches from current bank to alternate bank where new
+ * programmed values take effect. Bank switch operation can be
+ * performed for individual bus or for multiple bus in sync based on
+ * stream configuration.
+ */
+ ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Change stream state to DISABLE */
+ if (last_node)
+ sdw_rt->stream_state = SDW_STATE_STRM_DISABLE;
+
+ return ret;
+}
+
+/**
+ * sdw_enable_op: perform all operations required to enable port(s) channel(s).
+ * Below is sequence.
+ * - Program bus parameters.
+ * - Enable Master/Slave port(s) channel(s) for current stream.
+ * - Update bus parameters. Switch to alternate bank.
+ * - Change stream state.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @sdw_rt: Runtime stream handle.
+ */
+static int sdw_enable_op(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_runtime *sdw_rt)
+{
+
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ bool chn_en;
+ bool last_node = false;
+ int ret;
+
+ /*
+ * Check whether current Master handle is last in the list This is
+ * needed because some operations in sdw_update_bus_params are only
+ * performed when all Master(s)/Slave(s) processing is done. Also
+ * the stream state is changed after all Master(s)/Slave(s)
+ * processing is done.
+ */
+ last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt);
+
+ /*
+ * Program bus parameters and transport parameters for current
+ * Master handle and Slave(s) associated with it. Bus parameters
+ * includes programming clock gear, SSP registers. Transport
+ * parameters includes programming registers for hstart, hstop,
+ * blockoffset, subblockoffset, blockpackingmode, lanecontrol, etc.
+ */
+ ret = sdw_program_params(sdw_bus);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Enable port(s) channel(s) for Master(s) and Slave(s) associated
+ * with current stream
+ */
+ chn_en = true;
+ ret = sdw_enable_disable_ports(sdw_bus, sdw_rt, NULL, chn_en);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Enable port(s) channel(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Update bus parameters which is basically bank switch operation
+ * where bus switches from current bank to alternate bank where new
+ * programmed values take effect. Bank switch operation can be
+ * performed for individual bus or for multiple bus in sync based on
+ * stream configuration.
+ */
+ ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Change stream state to ENABLE */
+ if (last_node)
+ sdw_rt->stream_state = SDW_STATE_STRM_ENABLE;
+
+ return ret;
+}
+
+/**
+ * sdw_prepare_op: Perform all operations required to prepare ports. Below is
+ * sequence.
+ * - Cross check stream parameters with Master parameters.
+ * - Compute bus and transport parameters.
+ * - Program bus and transport parameters.
+ * - Update bus parameters. Switch to alternate bank.
+ * - Prepare port(s) for current stream.
+ * - Change stream state.
+ *
+ * @sdw_bus: Bus handle.
+ * @sdw_mstr_rt: Runtime Master handle.
+ * @sdw_rt: Runtime stream handle.
+ */
+static int sdw_prepare_op(struct sdw_bus *sdw_bus,
+ struct sdw_mstr_runtime *sdw_mstr_rt,
+ struct sdw_runtime *sdw_rt)
+{
+ struct sdw_stream_params *stream_params = &sdw_rt->stream_params;
+ struct sdw_master *sdw_mstr = sdw_bus->mstr;
+ struct sdw_master_caps *sdw_mstr_cap = NULL;
+ struct sdw_stream_params *mstr_params;
+ bool last_node = false;
+ int ret;
+
+ /*
+ * Check whether current Master handle is last in the list This is
+ * needed because some operations in sdw_update_bus_params are only
+ * performed when all Master(s)/Slave(s) processing is done. Also
+ * the stream state is changed after all Master(s)/Slave(s)
+ * processing is done.
+ */
+ last_node = sdw_check_last_node(sdw_mstr_rt, sdw_rt);
+
+ /* Retrieve Master capabilities and stream parameters */
+ sdw_mstr_cap = &sdw_bus->mstr->caps;
+ mstr_params = &sdw_mstr_rt->stream_params;
+
+ /* Check for isochronous mode, sample rate support etc. */
+ ret = sdw_check_strm_params(sdw_mstr_cap, mstr_params, stream_params);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Check for stream parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Calculate stream bandwidth and cumulative bus bandwidth */
+ sdw_mstr_rt->bus_rt.stream_bw = mstr_params->rate *
+ mstr_params->channel_count * mstr_params->bps;
+ sdw_bus->bandwidth += sdw_mstr_rt->bus_rt.stream_bw;
+
+ /*
+ * Compute bus parameters and transport parameters for current
+ * Master and the Slave(s) associated with it. Bus parameters
+ * includes computation of clock, frame shape, frame frequency, SSP
+ * and transport parameters includes computation of hstart, hstop,
+ * blockoffset, subblockoffset, blockpackingmode etc for all the
+ * Ports of this Master and Slave(s) associated with current stream
+ * and already active streams.
+ */
+ ret = sdw_compute_params(sdw_bus, sdw_mstr_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Compute parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Program bus parameters and transport parameters for current
+ * Master and Slave(s) associated with it. Bus parameters includes
+ * programming clock gear, SSP registers. Transport parameters
+ * includes programming registers for hstart, hstop, blockoffset,
+ * subblockoffset, blockpackingmode, lanecontrol, etc.
+ */
+ ret = sdw_program_params(sdw_bus);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Program parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Update bus parameters which is basically bank switch operation
+ * where bus switches from current bank to alternate bank where new
+ * programmed values take effect. Bank switch operation can be
+ * performed for individual bus or for multiple bus in sync based on
+ * stream configuration.
+ */
+ ret = sdw_update_bus_params(sdw_bus, sdw_rt, last_node);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Update parameters failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Prepare port(s) for Master and Slave associated with current
+ * stream
+ */
+ ret = sdw_prep_deprep_ports(sdw_bus, sdw_rt, true);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Prepare port(s) failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /* Change stream state to PREPARE */
+ if (last_node)
+ sdw_rt->stream_state = SDW_STATE_STRM_PREPARE;
+
+ return ret;
+}
+
+/**
+ * sdw_prepare_and_enable_ops: This is called by the bus driver for doing
+ * operations related to stream prepare and enable. sdw_bus_ops are
+ * performed on bus for preparing and enabling of the streams.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ */
+int sdw_prepare_and_enable_ops(struct sdw_stream_tag *stream_tag)
+{
+
+ struct sdw_runtime *sdw_rt = stream_tag->sdw_rt;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_bus *sdw_bus = NULL;
+ struct sdw_master *sdw_mstr = NULL;
+ int ret = 0;
+
+ /*
+ * Perform prepare and enable operation on all the Masters and
+ * Slaves associated with stream. Note that the loop is iterated for
+ * all Master. Stream with only Slave to Slave communication is not
+ * supported.
+ */
+
+ /* Check stream state whether to perform prepare & enable operation */
+ if (sdw_rt->stream_state != SDW_STATE_STRM_CONFIG)
+ return ret;
+
+ /* Iterate for all Master(s) in Master list for Prepare operation */
+ list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ /* Get bus structure for Master */
+ sdw_mstr = sdw_mstr_rt->mstr;
+ sdw_bus = sdw_master_to_bus(sdw_mstr);
+
+ /* Prepare operation of Master/Slave port(s) */
+ ret = sdw_prepare_op(sdw_bus, sdw_mstr_rt,
+ sdw_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Prepare Operation failed ret = %d\n", ret);
+ return -EINVAL;
+ }
+ }
+
+ /* Iterate for all Master(s) in Master list for Enable operation */
+ list_for_each_entry(sdw_mstr_rt, &sdw_rt->mstr_rt_list,
+ mstr_strm_node) {
+
+ /* Get bus structure for Master */
+ sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr);
+ sdw_mstr = sdw_bus->mstr;
+
+ /* Enable operation of Master/Slave port(s) channel(s) */
+ ret = sdw_enable_op(sdw_bus, sdw_mstr_rt,
+ sdw_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Enable Operation failed ret = %d\n", ret);
+ return -EINVAL;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * sdw_disable_and_deprepare_ops: This is called by the bus driver for doing
+ * operations related to stream disable and de-prepare.sdw_bus_ops are
+ * performed on bus for disabling and de-preparing of the streams.
+ *
+ * @stream_tag: Stream tag on which operations needs to be performed.
+ */
+int sdw_disable_and_deprepare_ops(struct sdw_stream_tag *stream_tag)
+{
+ struct sdw_runtime *sdw_rt = stream_tag->sdw_rt;
+ struct sdw_mstr_runtime *sdw_mstr_rt = NULL;
+ struct sdw_bus *sdw_bus = NULL;
+ struct sdw_master *sdw_mstr = NULL;
+ int ret = 0;
+
+ /*
+ * Perform disable and de-prepare operation on all the Masters and
+ * Slaves associated with stream. Note that the loop is iterated for
+ * all Master. Stream with only Slave to Slave communication is not
+ * supported.
+ */
+
+ /*
+ * Check stream state whether to perform disable &
+ * deprepare operation
+ */
+ if (sdw_rt->stream_state != SDW_STATE_STRM_ENABLE)
+ return ret;
+
+ /* Iterate for all Master(s) in Master list for Disable operation */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_rt->mstr_rt_list, mstr_strm_node) {
+
+ /* Get bus structure for Master */
+ sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr);
+ sdw_mstr = sdw_bus->mstr;
+
+ /* Disable operation of Master/Slave port(s) channel(s) */
+ ret = sdw_disable_op(sdw_bus, sdw_mstr_rt,
+ sdw_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "Disable Operation failed ret = %d\n", ret);
+ return -EINVAL;
+ }
+ }
+
+ /* Iterate for all Master(s) in Master list for De-prepare operation */
+ list_for_each_entry(sdw_mstr_rt,
+ &sdw_rt->mstr_rt_list, mstr_strm_node) {
+
+ /* Get bus structure for Master */
+ sdw_bus = sdw_master_to_bus(sdw_mstr_rt->mstr);
+ sdw_mstr = sdw_bus->mstr;
+
+ /* Disable operation of Master/Slave port(s) */
+ ret = sdw_deprepare_op(sdw_bus, sdw_mstr_rt,
+ sdw_rt);
+ if (ret < 0) {
+ dev_err(&sdw_mstr->dev, "De-prepare Operation failed ret = %d\n", ret);
+ return -EINVAL;
+ }
+ }
+
+ return ret;
+}
--
1.7.9.5
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
drivers/base/regmap/Kconfig | 3 +
drivers/base/regmap/Makefile | 1 +
drivers/base/regmap/regmap-sdw.c | 240 ++++++++++++++++++++++++++++++++++++++
include/linux/regmap.h | 37 ++++++
4 files changed, 281 insertions(+)
create mode 100644 drivers/base/regmap/regmap-sdw.c
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig
index db9d00c3..1d650c1 100644
--- a/drivers/base/regmap/Kconfig
+++ b/drivers/base/regmap/Kconfig
@@ -29,3 +29,6 @@ config REGMAP_MMIO
config REGMAP_IRQ
bool
+
+config REGMAP_SDW
+ depends on SOUND_SDW
diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile
index 609e4c8..ee041e0 100644
--- a/drivers/base/regmap/Makefile
+++ b/drivers/base/regmap/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_REGMAP_SPI) += regmap-spi.o
obj-$(CONFIG_REGMAP_SPMI) += regmap-spmi.o
obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o
obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o
+obj-$(CONFIG_REGMAP_SDW) += regmap-sdw.o
diff --git a/drivers/base/regmap/regmap-sdw.c b/drivers/base/regmap/regmap-sdw.c
new file mode 100644
index 0000000..d76ee12
--- /dev/null
+++ b/drivers/base/regmap/regmap-sdw.c
@@ -0,0 +1,240 @@
+/*
+ * regmap-sdw.c - Register map access API - SoundWire support.
+ *
+ * Copyright (C) 2015-2016 Intel Corp
+ * Author: Hardik Shah <[email protected]>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#include <linux/regmap.h>
+#include <sound/sdw_bus.h>
+#include <sound/sdw_master.h>
+#include <sound/sdw_slave.h>
+#include <linux/module.h>
+#include <sound/sdw/sdw_registers.h>
+
+#include "internal.h"
+
+
+static inline void get_t_size(size_t *t_val_size, size_t *t_size,
+ int *reg_addr,
+ int *offset,
+ size_t *val_size)
+{
+
+ *t_val_size += *t_size;
+ *offset += *t_size;
+
+ *t_size = *val_size - *t_val_size;
+ *t_size = min_t(size_t, *t_size, 65535);
+
+ *reg_addr += *t_size;
+
+
+}
+
+static int regmap_sdw_read(void *context,
+ const void *reg, size_t reg_size,
+ void *val, size_t val_size)
+{
+ struct device *dev = context;
+ struct sdw_slave *sdw = to_sdw_slave(dev);
+ struct sdw_msg xfer;
+ int ret, scp_addr1, scp_addr2;
+ int reg_command;
+ int reg_addr = *(u32 *)reg;
+ size_t t_val_size = 0, t_size;
+ int offset;
+ u8 *t_val;
+
+ /* SoundWire registers are 32-bit addressed */
+ if (reg_size != 4)
+ return -ENOTSUPP;
+
+ xfer.dev_num = sdw->dev_num;
+ xfer.xmit_on_ssp = 0;
+ xfer.r_w_flag = SDW_MSG_FLAG_READ;
+ xfer.len = 0;
+ t_val = val;
+
+ offset = 0;
+ reg_command = (reg_addr >> SDW_REGADDR_SHIFT) &
+ SDW_REGADDR_MASK;
+ if (val_size > SDW_MAX_REG_ADDR)
+ t_size = SDW_MAX_REG_ADDR - reg_command;
+ else
+ t_size = val_size;
+
+ while (t_val_size < val_size) {
+
+ scp_addr1 = (reg_addr >> SDW_SCP_ADDRPAGE1_SHIFT) &
+ SDW_SCP_ADDRPAGE1_MASK;
+ scp_addr2 = (reg_addr >> SDW_SCP_ADDRPAGE2_SHIFT) &
+ SDW_SCP_ADDRPAGE2_MASK;
+ xfer.addr_page1 = scp_addr1;
+ xfer.addr_page2 = scp_addr2;
+ xfer.addr = reg_command;
+ xfer.len += t_size;
+ xfer.buf = &t_val[offset];
+ ret = snd_sdw_slave_transfer(sdw->mstr, &xfer, 1);
+ if (ret < 0)
+ return ret;
+ else if (ret != 1)
+ return -EIO;
+
+ get_t_size(&t_val_size, &t_size, ®_addr, &offset,
+ &val_size);
+
+ reg_command = (reg_addr >> SDW_REGADDR_SHIFT) &
+ SDW_REGADDR_MASK;
+ }
+ return 0;
+}
+
+static int regmap_sdw_gather_write(void *context,
+ const void *reg, size_t reg_size,
+ const void *val, size_t val_size)
+{
+ struct device *dev = context;
+ struct sdw_slave *sdw = to_sdw_slave(dev);
+ struct sdw_msg xfer;
+ int ret, scp_addr1, scp_addr2;
+ int reg_command;
+ int reg_addr = *(u32 *)reg;
+ size_t t_val_size = 0, t_size;
+ int offset;
+ u8 *t_val;
+
+ /* All registers are 4 byte on SoundWire bus */
+ if (reg_size != 4)
+ return -ENOTSUPP;
+
+ if (!sdw)
+ return 0;
+
+ xfer.dev_num = sdw->dev_num;
+ xfer.xmit_on_ssp = 0;
+ xfer.r_w_flag = SDW_MSG_FLAG_WRITE;
+ xfer.len = 0;
+ t_val = (u8 *)val;
+
+ offset = 0;
+ reg_command = (reg_addr >> SDW_REGADDR_SHIFT) &
+ SDW_REGADDR_MASK;
+ if (val_size > SDW_MAX_REG_ADDR)
+ t_size = SDW_MAX_REG_ADDR - reg_command;
+ else
+ t_size = val_size;
+ while (t_val_size < val_size) {
+
+ scp_addr1 = (reg_addr >> SDW_SCP_ADDRPAGE1_SHIFT) &
+ SDW_SCP_ADDRPAGE1_MASK;
+ scp_addr2 = (reg_addr >> SDW_SCP_ADDRPAGE2_SHIFT) &
+ SDW_SCP_ADDRPAGE2_MASK;
+ xfer.addr_page1 = scp_addr1;
+ xfer.addr_page2 = scp_addr2;
+ xfer.addr = reg_command;
+ xfer.len += t_size;
+ xfer.buf = &t_val[offset];
+ ret = snd_sdw_slave_transfer(sdw->mstr, &xfer, 1);
+ if (ret < 0)
+ return ret;
+ else if (ret != 1)
+ return -EIO;
+
+ get_t_size(&t_val_size, &t_size, ®_addr, &offset,
+ &val_size);
+
+ reg_command = (reg_addr >> SDW_REGADDR_SHIFT) &
+ SDW_REGADDR_MASK;
+ }
+ return 0;
+}
+
+static int regmap_sdw_write(void *context, const void *data, size_t count)
+{
+ /* 4-byte register address for the soundwire */
+ unsigned int offset = 4;
+
+ if (count <= offset)
+ return -EINVAL;
+
+ return regmap_sdw_gather_write(context, data, 4,
+ data + offset, count - offset);
+}
+
+static struct regmap_bus regmap_sdw = {
+ .write = regmap_sdw_write,
+ .gather_write = regmap_sdw_gather_write,
+ .read = regmap_sdw_read,
+ .reg_format_endian_default = REGMAP_ENDIAN_LITTLE,
+ .val_format_endian_default = REGMAP_ENDIAN_LITTLE,
+};
+
+static int regmap_sdw_config_check(const struct regmap_config *config)
+{
+ /* All register are 8-bits wide as per MIPI Soundwire 1.0 Spec */
+ if (config->val_bits != 8)
+ return -ENOTSUPP;
+ /* Registers are 32 bit in size, based on SCP_ADDR1 and SCP_ADDR2
+ * implementation address range may vary in slave.
+ */
+ if (config->reg_bits != 32)
+ return -ENOTSUPP;
+ /* SoundWire register address are contiguous. */
+ if (config->reg_stride != 0)
+ return -ENOTSUPP;
+ if (config->pad_bits != 0)
+ return -ENOTSUPP;
+
+
+ return 0;
+}
+
+struct regmap *__regmap_init_sdw(struct sdw_slave *sdw,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name)
+{
+ int ret;
+
+ ret = regmap_sdw_config_check(config);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return __regmap_init(&sdw->dev, ®map_sdw, &sdw->dev, config,
+ lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__regmap_init_sdw);
+
+
+struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name)
+{
+ int ret;
+
+ ret = regmap_sdw_config_check(config);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return __devm_regmap_init(&sdw->dev, ®map_sdw, &sdw->dev, config,
+ lock_key, lock_name);
+}
+EXPORT_SYMBOL_GPL(__devm_regmap_init_sdw);
+
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/regmap.h b/include/linux/regmap.h
index 9adc7b2..5c192f7 100644
--- a/include/linux/regmap.h
+++ b/include/linux/regmap.h
@@ -29,6 +29,7 @@
struct regmap_range_cfg;
struct regmap_field;
struct snd_ac97;
+struct sdw_slave;
/* An enum of all the supported cache types */
enum regcache_type {
@@ -456,6 +457,10 @@ struct regmap *__regmap_init_ac97(struct snd_ac97 *ac97,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name);
+struct regmap *__regmap_init_sdw(struct sdw_slave *sdw,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name);
struct regmap *__devm_regmap_init(struct device *dev,
const struct regmap_bus *bus,
@@ -489,6 +494,10 @@ struct regmap *__devm_regmap_init_ac97(struct snd_ac97 *ac97,
const struct regmap_config *config,
struct lock_class_key *lock_key,
const char *lock_name);
+struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw,
+ const struct regmap_config *config,
+ struct lock_class_key *lock_key,
+ const char *lock_name);
/*
* Wrapper for regmap_init macros to include a unique lockdep key and name
@@ -623,6 +632,20 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg);
/**
+ * regmap_init_sdw(): Initialise register map
+ *
+ * @sdw: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer to
+ * a struct regmap.
+ */
+#define regmap_init_sdw(sdw, config) \
+ __regmap_lockdep_wrapper(__regmap_init_sdw, #config, \
+ sdw, config)
+
+
+/**
* devm_regmap_init(): Initialise managed register map
*
* @dev: Device that will be interacted with
@@ -737,6 +760,20 @@ int regmap_attach_dev(struct device *dev, struct regmap *map,
__regmap_lockdep_wrapper(__devm_regmap_init_ac97, #config, \
ac97, config)
+/**
+ * devm_regmap_init_sdw(): Initialise managed register map
+ *
+ * @sdw: Device that will be interacted with
+ * @config: Configuration for register map
+ *
+ * The return value will be an ERR_PTR() on error or a valid pointer
+ * to a struct regmap. The regmap will be automatically freed by the
+ * device management code.
+ */
+#define devm_regmap_init_sdw(sdw, config) \
+ __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config, \
+ sdw, config)
+
void regmap_exit(struct regmap *map);
int regmap_reinit_cache(struct regmap *map,
const struct regmap_config *config);
--
1.7.9.5
This patch adds support for clock stop prepare, clock stop and clock
resume.
As a part of SoundWire clock stop sequence, Master driver needs to call
APIs for.
1. Prepare Slaves for Clock Stop.
2. Broadcast ClockStopNow to all Slaves.
As part of SoundWire clock stop resume sequence, Master driver needs to
call APIs for.
1. De-prepare Slaves exiting from ClockStop mode 0
2. Optionally de-prepare Slaves exiting from ClockStop mode 1
based on Slave capabilities.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
sound/sdw/sdw.c | 547 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 547 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index 801200f..449060e 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -671,6 +671,37 @@ static int sdw_transfer(struct sdw_master *mstr, struct sdw_msg *msg, int num,
return ret;
}
+/*
+ * NO PM version of Slave transfer. Called from power management APIs
+ * to avoid dead locks. This is called by bus driver only.
+ */
+static int sdw_slv_transfer_nopm(struct sdw_master *mstr,
+ struct sdw_msg *msg, int num)
+{
+ int ret;
+
+ /*
+ * If calling from atomic context, return immediately if previous
+ * message has not completed executing
+ */
+ if (in_atomic() || irqs_disabled()) {
+ ret = mutex_trylock(&mstr->msg_lock);
+ if (!ret) {
+ /* SDW activity is ongoing. */
+ ret = -EAGAIN;
+ goto out;
+ }
+ } else {
+ mutex_lock(&mstr->msg_lock);
+ }
+
+ ret = sdw_transfer(mstr, msg, num, NULL);
+
+ mutex_unlock(&mstr->lock);
+out:
+ 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
@@ -1393,6 +1424,189 @@ static void sdw_update_slv_status_event(struct sdw_slave *slave,
slv_drv->status_change_event(slave, status);
}
+static int sdw_wait_for_clk_stp_deprep(struct sdw_slave *slave,
+ unsigned int prep_timeout)
+{
+ int ret;
+ struct sdw_msg msg;
+ u8 buf = 0;
+ int count = 0;
+ struct sdw_master *mstr = slave->mstr;
+
+ sdw_create_rd_msg(&msg, 0x0, SDW_SCP_STAT, 1, &buf, slave->dev_num);
+
+ /*
+ * Read the ClockStopNotFinished bit from the SCP_Stat register of
+ * particular Slave to make sure that clock stop prepare is done
+ */
+ do {
+ ret = sdw_slv_transfer_nopm(mstr, &msg, SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ WARN_ONCE(1, "Clock stop status read failed\n");
+ break;
+ }
+
+ if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK)) {
+ ret = 0;
+ break;
+ }
+
+ usleep_range(1000, 1200);
+ count++;
+
+ } while (count != prep_timeout);
+
+ if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK))
+
+ dev_info(&mstr->dev, "Clock stop prepare done\n");
+ else
+ WARN_ONCE(1, "Clk stp deprepare failed for slave %d\n",
+ slave->dev_num);
+
+ return ret;
+}
+
+/*
+ * This function does one of two things based on "prep" flag.
+ * 1. Prepare Slave for clock stop, if "prep" flag is true.
+ * 2. De-prepare Slave after clock resume, if "prep" flag is false.
+ */
+static void sdw_prepare_slv_for_clk_stp(struct sdw_master *mstr,
+ struct sdw_slave *slave,
+ enum sdw_clk_stop_mode clock_stop_mode,
+ bool prep)
+{
+ bool wake_en;
+ struct sdw_slave_caps *cap;
+ u8 buf = 0;
+ struct sdw_msg msg;
+ int ret;
+
+ cap = &slave->priv.caps;
+
+ wake_en = !cap->wake_up_unavailable;
+
+ if (prep) {
+ /*
+ * Even if its simplified clock stop prepare, setting
+ * prepare bit wont harm Here we are not doing write modify
+ * write since we are updating all fields of SystemCtrl
+ * registers. Currently highphy is not supported, so
+ * setting that bit to always 0
+ */
+ buf |= (1 << SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_SHIFT);
+ buf |= clock_stop_mode <<
+ SDW_SCP_SYSTEMCTRL_CLK_STP_MODE_SHIFT;
+ buf |= wake_en << SDW_SCP_SYSTEMCTRL_WAKE_UP_EN_SHIFT;
+ } else
+ buf = 0;
+
+ /*
+ * We are calling NOPM version of the transfer API, because Master
+ * controllers calls this from the suspend handler, so if we call
+ * the normal transfer API, it tries to resume controller, which
+ * results in deadlock
+ */
+ ret = sdw_wr_msg_nopm(&msg, 0x0, SDW_SCP_SYSTEMCTRL, 1, &buf,
+ slave->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+
+ /* We should continue even if it fails for some Slave */
+ if (ret != SDW_NUM_OF_MSG1_XFRD)
+ WARN_ONCE(1, "Clock Stop prepare failed for slave %d\n",
+ slave->dev_num);
+}
+
+/*
+ * This function checks if the Slave is in "prepared" or "de-prepared" state
+ * This is used to de-prepare Slaves which are in "prepared" state after
+ * resuming from ClockStop Mode 1
+ */
+static int sdw_check_for_prep_bit(struct sdw_slave *slave)
+{
+ u8 buf = 0;
+ struct sdw_msg msg;
+ int ret;
+ struct sdw_master *mstr = slave->mstr;
+
+ ret = sdw_rd_msg_nopm(&msg, 0x0, SDW_SCP_SYSTEMCTRL, 1, &buf,
+ slave->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "SCP_SystemCtrl read failed for Slave %d\n",
+ slave->dev_num);
+ return -EINVAL;
+ }
+
+ return !(buf & SDW_SCP_SYSTEMCTRL_CLK_STP_PREP_MASK);
+}
+
+/*
+ * This function De-prepares particular Slave which is resuming from
+ * ClockStop mode1. It does following things.
+ * 1. Check if Slave requires de-prepare based on Slave capabilities.
+ * 2. Check for the "Prepare" bit in SystemCtrl register.
+ * 3. If prepare bit is set Deprepare the Slave.
+ * 4. Wait till Slave is deprepared
+ */
+static int sdw_deprepare_slv_clk_stp1(struct sdw_slave *slave)
+{
+ struct sdw_slave_caps *cap;
+ int ret;
+ struct sdw_master *mstr = slave->mstr;
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+ int prep_timeout = 0;
+
+ cap = &slv_priv->caps;
+
+ /*
+ * Slave might have enumerated 1st time or from clock stop mode 1
+ * return if Slave doesn't require deprepare
+ */
+ if (!cap->clk_stp_prep_hard_reset_behavior)
+ return 0;
+
+ /*
+ * If Slave requires de-prepare after exiting from Clock Stop mode
+ * 1, then check for ClockStopPrepare bit in SystemCtrl register if
+ * its 1, de-prepare Slave from clock stop prepare, else return
+ */
+ ret = sdw_check_for_prep_bit(slave);
+
+ if (ret < 0)
+ return ret;
+
+ if (slv_priv->driver->pre_clk_stop_prep) {
+ ret = slv_priv->driver->pre_clk_stop_prep(slave,
+ cap->clk_stp1_mode, false);
+ if (ret < 0) {
+ dev_warn(&mstr->dev, "Pre de-prepare failed for Slave %d\n",
+ slave->dev_num);
+ return ret;
+ }
+ }
+
+ prep_timeout = cap->clk_stp_prep_timeout;
+ sdw_prepare_slv_for_clk_stp(slave->mstr, slave, cap->clk_stp1_mode,
+ false);
+
+ /* Make sure de-prepare is complete */
+ ret = sdw_wait_for_clk_stp_deprep(slave, prep_timeout);
+
+ if (ret < 0)
+ return ret;
+
+ if (slv_priv->driver->post_clk_stop_prep) {
+ ret = slv_priv->driver->post_clk_stop_prep(slave,
+ cap->clk_stp1_mode, false);
+
+ if (ret < 0)
+ dev_err(&mstr->dev, "Post de-prepare failed for Slave %d ret = %d\n",
+ slave->dev_num, ret);
+ }
+
+ return ret;
+}
/*
* Following thing are done in below loop for each of the registered Slaves.
@@ -1470,6 +1684,13 @@ static void sdw_process_slv_status(struct sdw_master *mstr,
if (ret < 0)
continue;
+ ret = sdw_deprepare_slv_clk_stp1(
+ mstr->sdw_addr[i].slave);
+
+ if (ret < 0)
+ continue;
+
+
mstr->sdw_addr[i].status =
SDW_SLAVE_STAT_ATTACHED_OK;
}
@@ -2076,6 +2297,332 @@ void snd_sdw_master_del(struct sdw_master *master)
}
EXPORT_SYMBOL_GPL(snd_sdw_master_del);
+static enum sdw_clk_stop_mode sdw_slv_get_clk_stp_mode(struct sdw_slave *slave)
+{
+ enum sdw_clk_stop_mode clock_stop_mode;
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+ struct sdw_slave_caps *cap = &slv_priv->caps;
+
+ /*
+ * Get the dynamic value of clock stop from Slave driver if
+ * supported, else use the static value from capabilities register.
+ * Update the capabilities also if we have new dynamic value.
+ */
+ if (slv_priv->driver->get_dyn_clk_stp_mod) {
+ clock_stop_mode = slv_priv->driver->get_dyn_clk_stp_mod(slave);
+ if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1)
+ cap->clk_stp1_mode = true;
+ else
+ cap->clk_stp1_mode = false;
+ } else
+ clock_stop_mode = cap->clk_stp1_mode;
+
+ return clock_stop_mode;
+}
+
+/**
+ * snd_sdw_master_stop_clock: Stop the clock. This function broadcasts the
+ * SCP_CTRL register with clock_stop_now bit set.
+ *
+ * @master: Master handle for which clock has to be stopped.
+ */
+int snd_sdw_master_stop_clock(struct sdw_master *master)
+{
+ int ret, i;
+ struct sdw_msg msg;
+ u8 buf = 0;
+ enum sdw_clk_stop_mode mode;
+
+ /*
+ * Send Broadcast message to the SCP_ctrl register with clock stop
+ * now. If none of the Slaves are attached, then there may not be
+ * ACK, flag the error about ACK not received but clock will be
+ * still stopped.
+ */
+
+ buf |= 0x1 << SDW_SCP_CTRL_CLK_STP_NOW_SHIFT;
+ ret = sdw_wr_msg_nopm(&msg, 0x0, SDW_SCP_CTRL, 1, &buf,
+ SDW_SLAVE_BDCAST_ADDR, master,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD)
+ dev_err(&master->dev, "ClockStopNow Broadcast message failed\n");
+
+ /*
+ * Mark all Slaves as un-attached which are entering clock stop
+ * mode1
+ */
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+
+ if (!master->sdw_addr[i].assigned)
+ continue;
+
+ /* Get clock stop mode for all Slaves */
+ mode = sdw_slv_get_clk_stp_mode(master->sdw_addr[i].slave);
+ if (mode == SDW_CLOCK_STOP_MODE_0)
+ continue;
+
+ /* If clock stop mode 1, mark Slave as not present */
+ master->sdw_addr[i].status = SDW_SLAVE_STAT_NOT_PRESENT;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_stop_clock);
+
+static struct sdw_slave *sdw_get_slv_status(struct sdw_master *mstr,
+ int *slave_index)
+{
+ int i;
+
+ for (i = *slave_index; i <= SDW_MAX_DEVICES; i++) {
+ if (mstr->sdw_addr[i].assigned != true)
+ continue;
+
+ if (mstr->sdw_addr[i].status == SDW_SLAVE_STAT_NOT_PRESENT)
+ continue;
+
+ *slave_index = i + 1;
+ return mstr->sdw_addr[i].slave;
+ }
+ return NULL;
+}
+
+/*
+ * Wait till ClockStop prepared/De-prepared is finished, Broadcasts the read
+ * message to read the SCP_STAT register. Wait till ClockStop_NotFinished bit
+ * is set. Break loop after timeout.
+ */
+static void sdw_wait_for_clk_stp_prep(struct sdw_master *mstr, unsigned int
+ prep_timeout)
+{
+ int ret;
+ struct sdw_msg msg;
+ u8 buf = 0;
+ int count = 0;
+
+ /* Create message to read clock stop status, its broadcast message. */
+ sdw_create_rd_msg(&msg, 0x0, SDW_SCP_STAT, 1, &buf,
+ SDW_SLAVE_BDCAST_ADDR);
+ /*
+ * Once all the Slaves are written with prepare bit, broadcast the
+ * read message for the SCP_STAT register to read the
+ * ClockStopNotFinished bit. Read till we get this a 0. Currently
+ * we have timeout of 1sec before giving up. Even if its not read as
+ * 0 after timeout, controller can stop the clock after warning.
+ */
+ do {
+ ret = sdw_slv_transfer_nopm(mstr, &msg, SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ WARN_ONCE(1, "Clock stop status read failed\n");
+ break;
+ }
+
+ if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK))
+ break;
+
+ /*
+ * Sleep in range of 1ms for the max number of millisecond
+ * of timeout
+ */
+ usleep_range(1000, 1200);
+ count++;
+
+ } while (count != prep_timeout);
+
+ if (!(buf & SDW_SCP_STAT_CLK_STP_NF_MASK))
+ dev_info(&mstr->dev, "Clock stop prepare done\n");
+ else
+ WARN_ONCE(1, "Some Slaves prepare un-successful\n");
+}
+
+/**
+ * snd_sdw_master_prepare_for_clk_stop: Prepare all the Slaves for clock
+ * stop. Iterate through each of the enumerated Slaves. Prepare each
+ * Slave according to the clock stop mode supported by Slave. Use
+ * dynamic value from Slave callback if registered, else use static
+ * values from Slave capabilities registered.
+ * 1. Get clock stop mode for each Slave.
+ * 2. Call pre_prepare callback of each Slave if registered.
+ * 3. Write ClockStopPrepare bit in SCP_SystemCtrl register for each of
+ * the enumerated Slaves.
+ * 4. Broadcast the read message to read the SCP_Stat register to make
+ * sure ClockStop Prepare is finished for all Slaves.
+ * 5. Call post_prepare callback of each Slave if registered after
+ * Slaves are in ClockStopPrepare state.
+ *
+ * @master: Master handle for which clock state has to be changed.
+ */
+int snd_sdw_master_prepare_for_clk_stop(struct sdw_master *master)
+{
+ struct sdw_slave_caps *cap;
+ enum sdw_clk_stop_mode clock_stop_mode;
+ int ret;
+ struct sdw_slave *slave = NULL;
+ int slv_index = 1;
+ unsigned int prep_timeout = 0;
+
+ /*
+ * Get all the Slaves registered to the Master driver for preparing
+ * for clock stop. Start from Slave with logical address as 1.
+ */
+ while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) {
+
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+
+ cap = &slv_priv->caps;
+
+ clock_stop_mode = sdw_slv_get_clk_stp_mode(slave);
+
+ /*
+ * Call the pre clock stop prepare, if Slave requires.
+ */
+ if (slv_priv->driver->pre_clk_stop_prep) {
+ ret = slv_priv->driver->pre_clk_stop_prep(slave,
+ clock_stop_mode, true);
+
+ /* If it fails we still continue */
+ if (ret < 0)
+ dev_warn(&master->dev, "Pre prepare failed for Slave %d\n",
+ slave->dev_num);
+ }
+
+ sdw_prepare_slv_for_clk_stp(master, slave, clock_stop_mode,
+ true);
+
+ if (prep_timeout > cap->clk_stp_prep_timeout)
+ prep_timeout = cap->clk_stp_prep_timeout;
+ }
+
+ /* Wait till prepare for all Slaves is finished */
+ sdw_wait_for_clk_stp_prep(master, prep_timeout);
+
+ slv_index = 1;
+ while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) {
+
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+
+ cap = &slv_priv->caps;
+
+ clock_stop_mode = sdw_slv_get_clk_stp_mode(slave);
+
+ if (slv_priv->driver->post_clk_stop_prep) {
+ ret = slv_priv->driver->post_clk_stop_prep(slave,
+ clock_stop_mode,
+ true);
+ /*
+ * Even if Slave fails we continue with other
+ * Slaves. This should never happen ideally.
+ */
+ if (ret < 0)
+ dev_err(&master->dev, "Post prepare failed for Slave %d ret = %d\n",
+ slave->dev_num, ret);
+
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_prepare_for_clk_stop);
+
+/**
+ * snd_sdw_master_deprepare_after_clk_start: De-prepare all the Slaves
+ * exiting clock stop mode 0 after clock resumes. Clock is already
+ * resumed before this. De-prepare for the Slaves which were there in
+ * clock stop mode 1 is done after they enumerated back. This is because
+ * Slave specific callbacks needs to be invoked as part of de-prepare,
+ * which can be invoked only after Slave enumerates.
+ * 1. Get clock stop mode for each Slave.
+ * 2. Call pre_prepare callback of each Slave exiting from clock stop
+ * mode 0.
+ * 3. De-Prepare each Slave exiting from clock stop mode 0
+ * 4. Broadcast the Read message to make sure all Slaves are
+ * de-prepared for clock stop.
+ * 5. Call post_prepare callback of each Slave exiting from clock stop
+ * mode0
+ *
+ * @master: Master handle
+ */
+int snd_sdw_master_deprepare_after_clk_start(struct sdw_master *master)
+{
+ struct sdw_slave_caps *cap;
+ enum sdw_clk_stop_mode clock_stop_mode;
+ int ret = 0;
+ struct sdw_slave *slave = NULL;
+ bool stop = false;
+ int slv_index = 1;
+ unsigned int prep_timeout = 0;
+
+ while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) {
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+
+ cap = &slv_priv->caps;
+
+ /* Get the clock stop mode from which Slave is exiting */
+ clock_stop_mode = sdw_slv_get_clk_stp_mode(slave);
+
+ /*
+ * Slave is exiting from Clock stop mode 1, De-prepare is
+ * optional based on capability, and it has to be done after
+ * Slave is enumerated. So nothing to be done here.
+ */
+ if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1)
+ continue;
+ /*
+ * Call the pre clock stop prepare, if Slave requires.
+ */
+ if (slv_priv->driver->pre_clk_stop_prep)
+ ret = slv_priv->driver->pre_clk_stop_prep(slave,
+ clock_stop_mode, false);
+
+ /* If it fails we still continue */
+ if (ret < 0)
+ dev_warn(&master->dev, "Pre de-prepare failed for Slave %d ret = %d\n",
+ slave->dev_num, ret);
+
+ sdw_prepare_slv_for_clk_stp(master, slave, clock_stop_mode,
+ false);
+ if (prep_timeout > cap->clk_stp_prep_timeout)
+ prep_timeout = cap->clk_stp_prep_timeout;
+ }
+
+ /*
+ * Wait till de-prepare is finished for all the Slaves.
+ */
+ sdw_wait_for_clk_stp_prep(master, prep_timeout);
+
+ slv_index = 1;
+ while ((slave = sdw_get_slv_status(master, &slv_index)) != NULL) {
+
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+
+ cap = &slv_priv->caps;
+
+ clock_stop_mode = sdw_slv_get_clk_stp_mode(slave);
+
+ /*
+ * Slave is exiting from Clock stop mode 1, De-prepare is
+ * optional based on capability, and it has to be done after
+ * Slave is enumerated.
+ */
+ if (clock_stop_mode == SDW_CLOCK_STOP_MODE_1)
+ continue;
+
+ if (slv_priv->driver->post_clk_stop_prep)
+ ret = slv_priv->driver->post_clk_stop_prep(slave,
+ clock_stop_mode,
+ stop);
+ /*
+ * Even if Slave fails we continue with other
+ * Slaves. This should never happen ideally.
+ */
+ if (ret < 0)
+ dev_err(&master->dev, "Post de-prepare failed for Slave %d ret = %d\n",
+ slave->dev_num, ret);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_deprepare_after_clk_start);
+
/**
* snd_sdw_master_get: Return the Master handle from Master number.
* Increments the reference count of the module. Similar to
--
1.7.9.5
This patch adds the support for updating the Slave status to bus driver.
Master driver updates Slave status change to the bus driver. Bus driver
takes appropriate action on Slave status change like.
1. Registering new device if new Slave got enumerated on bus.
2. Assigning the device number to the Slave device
3. Marking Slave as un-attached if Slave got detached from bus.
4. Handling Slave alerts.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
sound/sdw/sdw.c | 1074 ++++++++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw_priv.h | 66 ++++
2 files changed, 1140 insertions(+)
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
index c98e4d7..801200f 100644
--- a/sound/sdw/sdw.c
+++ b/sound/sdw/sdw.c
@@ -60,11 +60,13 @@
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
+#include <linux/delay.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <sound/sdw_bus.h>
#include <sound/sdw_master.h>
#include <sound/sdw_slave.h>
+#include <sound/sdw/sdw_registers.h>
#include "sdw_priv.h"
@@ -334,6 +336,251 @@ static int sdw_match(struct device *dev, struct device_driver *driver)
.pm = &soundwire_pm,
};
+static int sdw_find_free_dev_num(struct sdw_master *mstr,
+ struct sdw_msg *msg)
+{
+ int i, ret = -EINVAL;
+
+ mutex_lock(&mstr->lock);
+
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ if (mstr->sdw_addr[i].assigned == true)
+ continue;
+
+ mstr->sdw_addr[i].assigned = true;
+
+ memcpy(mstr->sdw_addr[i].dev_id, msg->buf,
+ SDW_NUM_DEV_ID_REGISTERS);
+
+ ret = i;
+ break;
+ }
+
+ mutex_unlock(&mstr->lock);
+ return ret;
+}
+
+static int sdw_program_dev_num(struct sdw_master *mstr, u8 dev_num)
+{
+ struct sdw_msg msg;
+ u8 buf;
+ int ret;
+
+ buf = dev_num;
+ ret = sdw_wr_msg(&msg, 0, SDW_SCP_DEVNUMBER, 1, &buf, 0x0, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Program Slave address failed ret = %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static bool sdw_find_slv(struct sdw_master *mstr, struct sdw_msg *msg,
+ unsigned int *dev_num)
+{
+ struct sdw_slave_addr *sdw_addr;
+ int i, comparison;
+ bool found = false;
+
+ mutex_lock(&mstr->lock);
+
+ /*
+ * Device number resets to 0, when Slave gets unattached. Find the
+ * already registered Slave, mark it as present and program the
+ * Slave address again with same value.
+ */
+ sdw_addr = mstr->sdw_addr;
+
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+ comparison = memcmp(sdw_addr[i].dev_id, msg->buf,
+ SDW_NUM_DEV_ID_REGISTERS);
+
+ if ((!comparison) && (sdw_addr[i].assigned == true)) {
+ found = true;
+ *dev_num = i;
+ break;
+ }
+ }
+
+ mutex_unlock(&mstr->lock);
+
+ return found;
+}
+
+static void sdw_free_dev_num(struct sdw_master *mstr, int dev_num)
+{
+ int i;
+
+ mutex_lock(&mstr->lock);
+
+ for (i = 0; i <= SDW_MAX_DEVICES; i++) {
+
+ if (dev_num == mstr->sdw_addr[i].dev_num) {
+
+ mstr->sdw_addr[dev_num].assigned = false;
+ memset(&mstr->sdw_addr[dev_num].dev_id[0], 0x0,
+ SDW_NUM_DEV_ID_REGISTERS);
+ break;
+ }
+ }
+
+ mutex_unlock(&mstr->lock);
+}
+
+static int sdw_slv_register(struct sdw_master *mstr)
+{
+ int ret, i;
+ struct sdw_msg msg;
+ u8 buf[SDW_NUM_DEV_ID_REGISTERS];
+ struct sdw_slave *sdw_slave;
+ int dev_num = -1;
+ bool found = false;
+
+ /* Create message to read the 6 dev_id registers */
+ sdw_create_rd_msg(&msg, 0, SDW_SCP_DEVID_0, SDW_NUM_DEV_ID_REGISTERS,
+ buf, 0x0);
+
+ /*
+ * Multiple Slaves may report an Attached_OK status as Device0.
+ * Since the enumeration relies on a hardware arbitration and is
+ * done one Slave at a time, a loop needs to run until all Slaves
+ * have been assigned a non-zero DeviceNumber. The loop exits when
+ * the reads from Device0 devID registers are no longer successful,
+ * i.e. there is no Slave left to enumerate
+ */
+ while ((ret = (snd_sdw_slave_transfer(mstr, &msg, SDW_NUM_OF_MSG1_XFRD))
+ == SDW_NUM_OF_MSG1_XFRD)) {
+
+ /*
+ * Find is Slave is re-enumerating, and was already
+ * registered earlier.
+ */
+ found = sdw_find_slv(mstr, &msg, &dev_num);
+
+ /*
+ * Reprogram the Slave device number if its getting
+ * re-enumerated. If that fails we continue finding new
+ * slaves, we flag error but don't stop since there may be
+ * new Slaves trying to get enumerated.
+ */
+ if (found) {
+ ret = sdw_program_dev_num(mstr, dev_num);
+ if (ret < 0)
+ dev_err(&mstr->dev, "Re-registering slave failed ret = %d", ret);
+
+ continue;
+
+ }
+
+ /*
+ * Find the free device_number for the new Slave getting
+ * enumerated 1st time.
+ */
+ dev_num = sdw_find_free_dev_num(mstr, &msg);
+ if (dev_num < 0) {
+ dev_err(&mstr->dev, "Failed to find free dev_num ret = %d\n", ret);
+ goto dev_num_assign_fail;
+ }
+
+ /*
+ * Allocate and initialize the Slave device on first
+ * enumeration
+ */
+ sdw_slave = kzalloc(sizeof(*sdw_slave), GFP_KERNEL);
+ if (!sdw_slave) {
+ ret = -ENOMEM;
+ goto mem_alloc_failed;
+ }
+
+ /*
+ * Initialize the allocated Slave device, set bus type and
+ * device type to SoundWire.
+ */
+ sdw_slave->mstr = mstr;
+ sdw_slave->dev.parent = &sdw_slave->mstr->dev;
+ sdw_slave->dev.bus = &sdw_bus_type;
+ sdw_slave->dev.type = &sdw_slv_type;
+ sdw_slave->priv.addr = &mstr->sdw_addr[dev_num];
+ sdw_slave->priv.addr->slave = sdw_slave;
+
+ for (i = 0; i < SDW_NUM_DEV_ID_REGISTERS; i++)
+ sdw_slave->priv.dev_id[i] = msg.buf[i];
+
+ dev_dbg(&mstr->dev, "SDW slave slave id found with values\n");
+ dev_dbg(&mstr->dev, "dev_id0 to dev_id5: %x:%x:%x:%x:%x:%x\n",
+ msg.buf[0], msg.buf[1], msg.buf[2],
+ msg.buf[3], msg.buf[4], msg.buf[5]);
+ dev_dbg(&mstr->dev, "Dev number assigned is %x\n", dev_num);
+
+ /*
+ * Set the Slave device name, its based on the dev_id and
+ * to bus which it is attached.
+ */
+ dev_set_name(&sdw_slave->dev, "sdw-slave%d-%02x:%02x:%02x:%02x:%02x:%02x",
+ sdw_master_get_id(mstr),
+ sdw_slave->priv.dev_id[0],
+ sdw_slave->priv.dev_id[1],
+ sdw_slave->priv.dev_id[2],
+ sdw_slave->priv.dev_id[3],
+ sdw_slave->priv.dev_id[4],
+ sdw_slave->priv.dev_id[5]);
+
+ /*
+ * Set name based on dev_id. This will be used in match
+ * function to bind the device and driver.
+ */
+ sprintf(sdw_slave->priv.name, "%02x:%02x:%02x:%02x:%02x:%02x",
+ sdw_slave->priv.dev_id[0],
+ sdw_slave->priv.dev_id[1],
+ sdw_slave->priv.dev_id[2],
+ sdw_slave->priv.dev_id[3],
+ sdw_slave->priv.dev_id[4],
+ sdw_slave->priv.dev_id[5]);
+ ret = device_register(&sdw_slave->dev);
+ if (ret) {
+ dev_err(&mstr->dev, "Register slave failed ret = %d\n", ret);
+ goto reg_slv_failed;
+ }
+
+ ret = sdw_program_dev_num(mstr, dev_num);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "Programming slave address failed ret = %d\n", ret);
+ goto program_slv_failed;
+ }
+
+ dev_dbg(&mstr->dev, "Slave registered with bus id %s\n",
+ dev_name(&sdw_slave->dev));
+
+ sdw_slave->dev_num = dev_num;
+
+ /*
+ * Max number of Slaves that can be attached is 11. This
+ * check is performed in sdw_find_free_dev_num function.
+ */
+ mstr->num_slv++;
+
+ mutex_lock(&mstr->lock);
+ list_add_tail(&sdw_slave->priv.node, &mstr->slv_list);
+ mutex_unlock(&mstr->lock);
+
+ }
+
+ return ret;
+
+program_slv_failed:
+ device_unregister(&sdw_slave->dev);
+reg_slv_failed:
+ kfree(sdw_slave);
+mem_alloc_failed:
+ sdw_free_dev_num(mstr, dev_num);
+dev_num_assign_fail:
+ return ret;
+
+}
+
/**
* sdw_transfer: Local function where logic is placed to handle NOPM and PM
* variants of the Slave transfer functions.
@@ -516,6 +763,816 @@ int snd_sdw_slave_transfer(struct sdw_master *master, struct sdw_msg *msg,
}
EXPORT_SYMBOL_GPL(snd_sdw_slave_transfer);
+static int sdw_handle_dp0_interrupts(struct sdw_master *mstr,
+ struct sdw_slave *sdw_slv, unsigned int *status)
+{
+ int ret;
+ struct sdw_msg rd_msg, wr_msg;
+ int impl_def_mask = 0;
+ u8 rbuf, wbuf;
+ struct sdw_slave_dp0_caps *dp0_cap;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ dp0_cap = slv_priv->caps.dp0_caps;
+
+ /* Read the DP0 interrupt status register and parse the bits */
+ ret = sdw_rd_msg(&rd_msg, 0x0, SDW_DP0_INTSTAT, 1, &rbuf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Intr status read failed for slave %x\n",
+ sdw_slv->dev_num);
+ goto out;
+ }
+
+ if (rd_msg.buf[0] & SDW_DP0_INTSTAT_TEST_FAIL_MASK) {
+ dev_err(&mstr->dev, "Test fail for slave %d port 0\n",
+ sdw_slv->dev_num);
+ wr_msg.buf[0] |= SDW_DP0_INTCLEAR_TEST_FAIL_MASK;
+ }
+
+ if ((dp0_cap->prepare_ch == SDW_CP_MODE_NORMAL) &&
+ (rd_msg.buf[0] & SDW_DP0_INTSTAT_PORT_READY_MASK)) {
+ complete(&slv_priv->port_ready[0]);
+ wr_msg.buf[0] |= SDW_DP0_INTCLEAR_PORT_READY_MASK;
+ }
+
+ if (rd_msg.buf[0] & SDW_DP0_INTMASK_BRA_FAILURE_MASK) {
+ /* TODO: Handle BRA failure */
+ dev_err(&mstr->dev, "BRA failed for slave %d\n",
+ sdw_slv->dev_num);
+ wr_msg.buf[0] |= SDW_DP0_INTCLEAR_BRA_FAILURE_MASK;
+ }
+
+ impl_def_mask = SDW_DP0_INTSTAT_IMPDEF1_MASK |
+ SDW_DP0_INTSTAT_IMPDEF2_MASK |
+ SDW_DP0_INTSTAT_IMPDEF3_MASK;
+ if (rd_msg.buf[0] & impl_def_mask) {
+ wr_msg.buf[0] |= impl_def_mask;
+ *status = wr_msg.buf[0];
+ }
+
+ /* Ack DP0 interrupts */
+ ret = sdw_wr_msg(&wr_msg, 0x0, SDW_DP0_INTCLEAR, 1, &wbuf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Ack DP0 interrupts failed\n");
+ goto out;
+ }
+
+out:
+ return ret;
+
+}
+
+static int sdw_handle_port_interrupts(struct sdw_master *mstr,
+ struct sdw_slave *sdw_slv, int port_num,
+ unsigned int *status)
+{
+ int ret;
+ struct sdw_msg rd_msg, wr_msg;
+ u8 rbuf, wbuf;
+ int impl_def_mask = 0;
+ u16 intr_clr_addr, intr_stat_addr;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ /*
+ * Handle the Data port0 interrupt separately since the interrupt
+ * mask and stat register is different than other DPn registers
+ */
+ if (port_num == 0 && slv_priv->caps.dp0_present)
+ return sdw_handle_dp0_interrupts(mstr, sdw_slv, status);
+
+ intr_stat_addr = SDW_DPN_INTSTAT + (SDW_NUM_DATA_PORT_REGISTERS *
+ port_num);
+
+ /* Read the interrupt status register of port and parse bits */
+ ret = sdw_rd_msg(&rd_msg, 0x0, intr_stat_addr, 1, &rbuf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Port Status read failed for slv %x port %x\n",
+ sdw_slv->dev_num, port_num);
+ goto out;
+ }
+
+ if (rd_msg.buf[0] & SDW_DPN_INTSTAT_TEST_FAIL_MASK) {
+ dev_err(&mstr->dev, "Test fail for slave %x port %x\n",
+ sdw_slv->dev_num, port_num);
+ wr_msg.buf[0] |= SDW_DPN_INTCLEAR_TEST_FAIL_MASK;
+ }
+
+ /*
+ * Port Ready interrupt is only for Normal Channel prepare state
+ * machine
+ */
+ if ((rd_msg.buf[0] & SDW_DPN_INTSTAT_PORT_READY_MASK)) {
+ complete(&slv_priv->port_ready[port_num]);
+ wr_msg.buf[0] |= SDW_DPN_INTCLEAR_PORT_READY_MASK;
+ }
+
+ impl_def_mask = SDW_DPN_INTSTAT_IMPDEF1_MASK |
+ SDW_DPN_INTSTAT_IMPDEF2_MASK |
+ SDW_DPN_INTSTAT_IMPDEF3_MASK;
+ if (rd_msg.buf[0] & impl_def_mask) {
+ wr_msg.buf[0] |= impl_def_mask;
+ *status = wr_msg.buf[0];
+ }
+
+ intr_clr_addr = SDW_DPN_INTCLEAR +
+ (SDW_NUM_DATA_PORT_REGISTERS * port_num);
+
+ /* Clear and Ack the Port interrupt */
+ ret = sdw_wr_msg(&wr_msg, 0x0, intr_clr_addr, 1, &wbuf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Clear and ACK port interrupt failed for slv %x port %x\n",
+ sdw_slv->dev_num, port_num);
+ goto out;
+ }
+
+out:
+ return ret;
+
+}
+
+/*
+ * Get the Slave status
+ */
+static int sdw_get_slv_intr_stat(struct sdw_master *mstr, struct sdw_slave *slv,
+ u8 *intr_stat_buf)
+{
+ struct sdw_msg rd_msg[3];
+ int ret;
+ int num_rd_messages = 1;
+ struct sdw_slave_priv *slv_priv = &slv->priv;
+
+ sdw_create_rd_msg(&rd_msg[0], 0x0, SDW_SCP_INTSTAT1, 1,
+ &intr_stat_buf[0], slv->dev_num);
+
+ /*
+ * Create read message for reading the Instat2 registers if Slave
+ * supports more than 4 ports
+ */
+ if (slv_priv->caps.num_ports > SDW_CASC_PORT_START_INTSTAT2) {
+ sdw_create_rd_msg(&rd_msg[1], 0x0, SDW_SCP_INTSTAT2, 1,
+ &intr_stat_buf[1], slv->dev_num);
+ num_rd_messages = 2;
+
+ }
+
+ if (slv_priv->caps.num_ports > SDW_CASC_PORT_START_INTSTAT3) {
+ sdw_create_rd_msg(&rd_msg[2], 0x0, SDW_SCP_INTSTAT3, 1,
+ &intr_stat_buf[2], slv->dev_num);
+ num_rd_messages = 3;
+ }
+
+ /* Read Instat1, 2 and 3 registers */
+ ret = snd_sdw_slave_transfer(mstr, rd_msg, num_rd_messages);
+ if (ret != num_rd_messages) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Intr Status read failed for slv %x\n", slv->dev_num);
+ }
+
+ return ret;
+
+}
+
+static int sdw_ack_slv_intr(struct sdw_master *mstr, u8 dev_num,
+ u8 *intr_clr_buf)
+{
+ struct sdw_msg wr_msg;
+ int ret;
+
+ /* Ack the interrupts */
+ ret = sdw_wr_msg(&wr_msg, 0x0, SDW_SCP_INTCLEAR1, 1,
+ intr_clr_buf, dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ ret = -EINVAL;
+ dev_err(&mstr->dev, "Intr clear write failed for slv\n");
+ }
+
+ return ret;
+
+}
+
+static int sdw_handle_casc_port_intr(struct sdw_master *mstr, struct sdw_slave
+ *sdw_slv, u8 cs_port_start,
+ unsigned int *port_status,
+ u8 *intr_stat_buf)
+{
+ int i, ret;
+ int cs_port_mask, cs_port_reg_offset, num_cs_ports;
+
+ switch (cs_port_start) {
+
+ case SDW_CASC_PORT_START_INTSTAT1:
+ /* Number of port status bits in this register */
+ num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT1;
+ /* Bit mask for the starting port intr status */
+ cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT1;
+ /* Register offset to read Cascaded instat 1 */
+ cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT1;
+ break;
+
+ case SDW_CASC_PORT_START_INTSTAT2:
+ num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT2;
+ cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT2;
+ cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT2;
+ break;
+
+ case SDW_CASC_PORT_START_INTSTAT3:
+ num_cs_ports = SDW_NUM_CASC_PORT_INTSTAT3;
+ cs_port_mask = SDW_CASC_PORT_MASK_INTSTAT3;
+ cs_port_reg_offset = SDW_CASC_PORT_REG_OFFSET_INTSTAT3;
+ break;
+
+ default:
+ return -EINVAL;
+
+ }
+
+ /*
+ * Look for cascaded port interrupts, if found handle port
+ * interrupts. Do this for all the Int_stat registers.
+ */
+ for (i = cs_port_start; i < cs_port_start + num_cs_ports; i++) {
+ if (intr_stat_buf[cs_port_reg_offset] & cs_port_mask) {
+ ret = sdw_handle_port_interrupts(mstr, sdw_slv,
+ cs_port_start + i,
+ &port_status[i]);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "Handling port intr failed ret = %d\n", ret);
+ return ret;
+ }
+ }
+ cs_port_mask = cs_port_mask << i;
+ }
+ return 0;
+}
+
+static int sdw_handle_impl_def_intr(struct sdw_slave *sdw_slv,
+ struct sdw_impl_def_intr_stat *intr_status,
+ unsigned int *port_status,
+ u8 *control_port_stat)
+{
+ int ret, i;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ /* Update the implementation defined status to Slave */
+ for (i = 1; i < slv_priv->caps.num_ports; i++) {
+
+ intr_status->portn_stat[i].status = port_status[i];
+ intr_status->portn_stat[i].num = i;
+ }
+
+ intr_status->port0_stat = port_status[0];
+ intr_status->control_port_stat = control_port_stat[0];
+
+ ret = slv_priv->driver->slave_irq(sdw_slv, intr_status);
+ if (ret < 0) {
+ dev_err(&sdw_slv->mstr->dev, "Impl defined interrupt handling failed ret = %d\n", ret);
+ return ret;
+ }
+ return 0;
+}
+
+/*
+ * sdw_handle_slv_alerts: This function handles the Slave alert. Following
+ * things are done as part of handling Slave alert. Attempt is done to
+ * complete the interrupt handling in as less read/writes as possible
+ * based on number of ports defined by Slave.
+ *
+ * 1. Get the interrupt status of the Slave (sdw_get_slv_intr_stat) 1a.
+ * Read Instat1, Instat2 and Intstat3 registers based on on number of
+ * ports defined by the Slave.
+ *
+ * 2. Parse Interrupt Status registers for the SCP interrupts and take
+ * action.
+ *
+ * 3. Parse the interrupt status registers for the Port interrupts and
+ * take action.
+ *
+ * 4. Ack port interrupts.
+ * 5. Call the Slave implementation defined interrupt, if Slave has
+ * registered for it.
+ *
+ * 6. Ack the Slave interrupt.
+ * 7. Get interrupt status of the Slave again, to make sure no new
+ * interrupt came when we were servicing the interrupts.
+ *
+ * 8. Goto step 2 if any interrupt pending.
+ *
+ * 9. Return if no new interrupt pending.
+ * TODO: Poorly-designed or faulty Slaves may continuously generate
+ * interrupts and delay handling of interrupts signaled by other
+ * Slaves. A better QoS could rely on a priority scheme, where Slaves
+ * with the lowest DeviceNumber are handled first. Currently the
+ * priority is based on the enumeration sequence and arbitration;
+ * additional information would be needed from firmware/BIOS or module
+ * parameters to rank Slaves by relative interrupt processing priority.
+ */
+static int sdw_handle_slv_alerts(struct sdw_master *mstr,
+ struct sdw_slave *sdw_slv)
+{
+ int slave_stat, count = 0, ret;
+ int max_tries = SDW_INTR_STAT_READ_MAX_TRIES;
+ unsigned int port_status[SDW_MAX_DATA_PORTS] = {0};
+ struct sdw_impl_def_intr_stat intr_status;
+ struct sdw_portn_intr_stat portn_stat;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+ u8 intr_clr_buf[SDW_NUM_INT_CLEAR_REGISTERS];
+ u8 intr_stat_buf[SDW_NUM_INT_STAT_REGISTERS] = {0};
+ u8 cs_port_start;
+
+ mstr->sdw_addr[sdw_slv->dev_num].status = SDW_SLAVE_STAT_ALERT;
+ /*
+ * Keep on servicing interrupts till Slave interrupts are ACKed and
+ * device returns to attached state instead of ALERT state
+ */
+ ret = sdw_get_slv_intr_stat(mstr, sdw_slv, intr_stat_buf);
+ if (ret < 0)
+ return ret;
+
+ do {
+
+ if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_PARITY_MASK) {
+ dev_err(&mstr->dev, "Parity error detected\n");
+ intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_PARITY_MASK;
+ }
+
+ if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_BUS_CLASH_MASK) {
+ dev_err(&mstr->dev, "Bus clash error detected\n");
+ intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_BUS_CLASH_MASK;
+ }
+
+ /* Handle implementation defined mask */
+ if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_IMPL_DEF_MASK)
+ intr_clr_buf[0] |= SDW_SCP_INTCLEAR1_IMPL_DEF_MASK;
+
+ cs_port_start = SDW_NUM_CASC_PORT_INTSTAT1;
+
+ /* Handle Cascaded Port interrupts from Instat_1 registers */
+ ret = sdw_handle_casc_port_intr(mstr, sdw_slv, cs_port_start,
+ port_status, intr_stat_buf);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * If there are more than 4 ports and cascaded interrupt is
+ * set, handle those interrupts
+ */
+ if (intr_stat_buf[0] & SDW_SCP_INTSTAT1_SCP2_CASCADE_MASK) {
+ cs_port_start = SDW_NUM_CASC_PORT_INTSTAT2;
+ ret = sdw_handle_casc_port_intr(mstr, sdw_slv,
+ cs_port_start, port_status,
+ intr_stat_buf);
+ }
+
+ /*
+ * Handle cascaded interrupts from instat_2 register, if no
+ * cascaded interrupt from SCP2 cascade move to impl_def
+ * intrs
+ */
+ if (intr_stat_buf[1] & SDW_SCP_INTSTAT2_SCP3_CASCADE_MASK) {
+ cs_port_start = SDW_NUM_CASC_PORT_INTSTAT3;
+ ret = sdw_handle_casc_port_intr(mstr, sdw_slv,
+ cs_port_start, port_status,
+ intr_stat_buf);
+ }
+
+ /*
+ * Handle implementation defined interrupts if Slave has
+ * registered for it.
+ */
+ intr_status.portn_stat = &portn_stat;
+ if (slv_priv->driver->slave_irq) {
+
+ ret = sdw_handle_impl_def_intr(sdw_slv, &intr_status,
+ port_status, intr_clr_buf);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Ack the Slave interrupt */
+ ret = sdw_ack_slv_intr(mstr, sdw_slv->dev_num, intr_clr_buf);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "Slave interrupt ack failed ret = %d\n", ret);
+ return ret;
+ }
+
+ /*
+ * Read status once again before exiting loop to make sure
+ * no new interrupts came while we were servicing the
+ * interrupts
+ */
+ ret = sdw_get_slv_intr_stat(mstr, sdw_slv, intr_stat_buf);
+ if (ret < 0)
+ return ret;
+
+ /* Make sure no interrupts are pending */
+ slave_stat = intr_stat_buf[0] || intr_stat_buf[1] ||
+ intr_stat_buf[2];
+ /*
+ * Exit loop if Slave is continuously in ALERT state even
+ * after servicing the interrupt multiple times.
+ */
+ count++;
+
+ } while (slave_stat != 0 && count < max_tries);
+
+ return 0;
+}
+
+/*
+ * Enable the Slave Control Port (SCP) interrupts and DP0 interrupts if
+ * Slave supports DP0. Enable implementation defined interrupts based on
+ * Slave interrupt mask.
+ * This function enables below interrupts.
+ * 1. Bus clash interrupt for SCP
+ * 2. Parity interrupt for SCP.
+ * 3. Enable implementation defined interrupt if slave requires.
+ * 4. Port ready interrupt for the DP0 if required based on Slave support
+ * for DP0 and normal channel prepare supported by DP0 port. For simplified
+ * channel prepare Port ready interrupt is not required to be enabled.
+ */
+static int sdw_enable_scp_intr(struct sdw_slave *sdw_slv, int mask)
+{
+ struct sdw_msg rd_msg, wr_msg;
+ u8 buf;
+ int ret;
+ struct sdw_master *mstr = sdw_slv->mstr;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+ u32 reg_addr = SDW_SCP_INTMASK1;
+
+
+ ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "SCP Intr mask read failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+
+ buf |= mask;
+
+ buf |= SDW_SCP_INTMASK1_BUS_CLASH_MASK;
+ buf |= SDW_SCP_INTMASK1_PARITY_MASK;
+
+ ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "SCP Intr mask write failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+
+ if (!slv_priv->caps.dp0_present)
+ return 0;
+
+ reg_addr = SDW_DP0_INTMASK;
+ mask = slv_priv->caps.dp0_caps->imp_def_intr_mask;
+ buf = 0;
+
+ ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "DP0 Intr mask read failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+
+ buf |= mask;
+
+ if (slv_priv->caps.dp0_caps->prepare_ch == SDW_CP_MODE_NORMAL)
+ buf |= SDW_DPN_INTMASK_PORT_READY_MASK;
+
+ ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num,
+ mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "DP0 Intr mask write failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num,
+ int port_direction, bool enable)
+{
+
+ struct sdw_msg rd_msg, wr_msg;
+ u8 buf;
+ int ret;
+ struct sdw_master *mstr = sdw_slv->mstr;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+ u32 reg_addr;
+ struct sdw_dpn_caps *dpn_caps;
+ u8 mask;
+
+ reg_addr = SDW_DPN_INTMASK +
+ (SDW_NUM_DATA_PORT_REGISTERS * port_num);
+
+ dpn_caps = &slv_priv->caps.dpn_caps[port_direction][port_num];
+ mask = dpn_caps->imp_def_intr_mask;
+
+ /* Read DPn interrupt mask register */
+ ret = sdw_rd_msg(&rd_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "DPn Intr mask read failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+
+ /* Enable the Slave defined interrupts. */
+ buf |= mask;
+
+ /*
+ * Enable port prepare interrupt only if port is not having
+ * simplified channel prepare state machine
+ */
+ if (dpn_caps->prepare_ch == SDW_CP_MODE_NORMAL)
+ buf |= SDW_DPN_INTMASK_PORT_READY_MASK;
+
+ /* Enable DPn interrupt */
+ ret = sdw_wr_msg(&wr_msg, 0, reg_addr, 1, &buf,
+ sdw_slv->dev_num, mstr,
+ SDW_NUM_OF_MSG1_XFRD);
+ if (ret != SDW_NUM_OF_MSG1_XFRD) {
+ dev_err(&mstr->dev, "DPn Intr mask write failed for slave %x\n",
+ sdw_slv->dev_num);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * snd_sdw_slave_set_intr_mask: Set the implementation defined interrupt
+ * mask. Slave sets the implementation defined interrupt mask as part
+ * of registering Slave capabilities. Slave driver can also modify
+ * implementation defined interrupt dynamically using below function.
+ *
+ * @slave: SoundWire Slave handle for which interrupt needs to be enabled.
+ * @intr_mask: Implementation defined interrupt mask.
+ */
+int snd_sdw_slave_set_intr_mask(struct sdw_slave *slave,
+ struct sdw_impl_def_intr_mask *intr_mask)
+{
+ int ret, i, j;
+ struct sdw_slave_caps *caps = &slave->priv.caps;
+ struct sdw_slave_dp0_caps *dp0_caps = caps->dp0_caps;
+ u8 ports;
+
+ caps->scp_impl_def_intr_mask = intr_mask->control_port_mask;
+
+ if (caps->dp0_present)
+ dp0_caps->imp_def_intr_mask = intr_mask->port0_mask;
+
+ for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) {
+ if (i == 0)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+ for (j = 0; j < ports; j++) {
+ caps->dpn_caps[i][j].imp_def_intr_mask =
+ intr_mask->portn_mask[i][j].mask;
+ }
+ }
+
+ ret = sdw_enable_scp_intr(slave, caps->scp_impl_def_intr_mask);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+
+}
+EXPORT_SYMBOL(snd_sdw_slave_set_intr_mask);
+
+static int sdw_program_slv(struct sdw_slave *sdw_slv)
+{
+ struct sdw_slave_caps *cap;
+ int ret;
+ struct sdw_master *mstr = sdw_slv->mstr;
+ struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ cap = &slv_priv->caps;
+
+ /* Enable DP0 and SCP interrupts */
+ ret = sdw_enable_scp_intr(sdw_slv, cap->scp_impl_def_intr_mask);
+ if (ret < 0) {
+ dev_err(&mstr->dev, "SCP program failed ret = %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void sdw_update_slv_status_event(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+
+{
+ struct sdw_slave_priv *slv_priv = &slave->priv;
+ struct sdw_slave_driver *slv_drv = slv_priv->driver;
+
+ if (slv_drv->status_change_event)
+ slv_drv->status_change_event(slave, status);
+}
+
+
+/*
+ * Following thing are done in below loop for each of the registered Slaves.
+ * This handles only Slaves which were already registered before update
+ * status.
+ * 1. Mark Slave as not present, if status is unattached from from bus and
+ * logical address assigned is true, update status to Slave driver.
+ *
+ * 2. Handle the Slave alerts, if the Status is Alert for any of the Slaves.
+ * 3. Mark the Slave as present, if Status is Present and logical address is
+ * assigned.
+ * 3a. Update the Slave status to driver, driver will use to make sure
+ * its enumerated before doing read/writes.
+ *
+ * 3b. De-prepare if the Slave is exiting from clock stop mode 1 and
+ * capability is updated as "de-prepare" required after exiting clock
+ * stop mode 1.
+ *
+ * 3c. Program Slave registers for the implementation defined
+ * interrupts and wake enable based on Slave capabilities.
+ */
+static void sdw_process_slv_status(struct sdw_master *mstr,
+ struct sdw_slv_status *status)
+{
+ int i, ret;
+
+ for (i = 1; i <= SDW_MAX_DEVICES; i++) {
+
+ if (mstr->sdw_addr[i].assigned != true)
+ continue;
+ /*
+ * If current state of device is same as previous
+ * state, nothing to be done for this device.
+ */
+ else if (status->status[i] == mstr->sdw_addr[i].status)
+ continue;
+
+ /*
+ * If Slave got unattached, mark it as not present
+ * Slave can get unattached from attached state or
+ * Alert State
+ */
+ if (status->status[i] == SDW_SLAVE_STAT_NOT_PRESENT) {
+
+ mstr->sdw_addr[i].status =
+ SDW_SLAVE_STAT_NOT_PRESENT;
+
+ /*
+ * If Slave is in alert state, handle the Slave
+ * interrupts. Slave can get into alert state from
+ * attached state only.
+ */
+ } else if (status->status[i] == SDW_SLAVE_STAT_ALERT) {
+
+ ret = sdw_handle_slv_alerts(mstr,
+ mstr->sdw_addr[i].slave);
+
+ /*
+ * If Slave is re-attaching on the bus program all
+ * the interrupt and wake_en registers based on
+ * capabilities. De-prepare the Slave based on
+ * capability. Slave can move from Alert to
+ * Attached_Ok, but nothing needs to be done on that
+ * transition, it can also move from Not_present to
+ * Attached_ok, in this case only registers needs to
+ * be reprogrammed and deprepare needs to be done.
+ */
+ } else if (status->status[i] ==
+ SDW_SLAVE_STAT_ATTACHED_OK &&
+ mstr->sdw_addr[i].status ==
+ SDW_SLAVE_STAT_NOT_PRESENT) {
+
+ ret = sdw_program_slv(mstr->sdw_addr[i].slave);
+
+ if (ret < 0)
+ continue;
+
+ mstr->sdw_addr[i].status =
+ SDW_SLAVE_STAT_ATTACHED_OK;
+ }
+
+ /*
+ * Update the status to Slave, This is used by Slave
+ * during resume to make sure its enumerated before
+ * Slave register access
+ */
+ sdw_update_slv_status_event(mstr->sdw_addr[i].slave,
+ mstr->sdw_addr[i].status);
+ }
+}
+
+/*
+ * sdw_handle_slv_status: Worker thread to handle the Slave status.
+ */
+static void sdw_handle_slv_status(struct kthread_work *work)
+{
+ int ret = 0;
+ struct sdw_slv_status *status, *__status__;
+ struct sdw_bus *bus =
+ container_of(work, struct sdw_bus, kwork);
+ struct sdw_master *mstr = bus->mstr;
+ unsigned long flags;
+
+ /*
+ * Loop through each of the status nodes. Each node contains status
+ * for all Slaves. Master driver reports Slave status for all Slaves
+ * in interrupt context. Bus driver adds it to list and schedules
+ * this thread.
+ */
+ list_for_each_entry_safe(status, __status__, &bus->status_list, node) {
+
+ /*
+ * Handle newly attached Slaves, Register the Slaves with
+ * bus for all newly attached Slaves. Slaves may be
+ * attaching first time to bus or may have re-enumerated
+ * after hard or soft reset or clock stop exit 1.
+ */
+ if (status->status[0] == SDW_SLAVE_STAT_ATTACHED_OK) {
+ ret = sdw_slv_register(mstr);
+ if (ret < 0)
+ /*
+ * Even if adding new Slave fails, we will
+ * continue to add Slaves till we find all
+ * the enumerated Slaves.
+ */
+ dev_err(&mstr->dev, "Register new slave failed ret = %d\n", ret);
+ }
+
+ sdw_process_slv_status(mstr, status);
+
+ spin_lock_irqsave(&bus->spinlock, flags);
+ list_del(&status->node);
+ spin_unlock_irqrestore(&bus->spinlock, flags);
+ kfree(status);
+ }
+}
+
+/**
+ * snd_sdw_master_update_slave_status: Update the status of the Slave to the
+ * bus driver. Master calls this function based on the interrupt it
+ * gets once the Slave changes its state or from interrupts for the
+ * Master hardware that caches status information reported in PING
+ * commands.
+ *
+ * @master: Master handle for which status is reported.
+ * @status: Array of status of each Slave.
+ *
+ * This function can be called from interrupt context by Master driver to
+ * report Slave status without delay.
+ */
+int snd_sdw_master_update_slave_status(struct sdw_master *master,
+ struct sdw_status *status)
+{
+ struct sdw_bus *bus = NULL;
+ struct sdw_slv_status *slv_status;
+ unsigned long flags;
+
+ bus = master->bus;
+
+ slv_status = kzalloc(sizeof(*slv_status), GFP_ATOMIC);
+ if (!slv_status)
+ return -ENOMEM;
+
+ memcpy(slv_status->status, status, sizeof(*slv_status));
+
+ /*
+ * Bus driver will take appropriate action for Slave status change
+ * in thread context. Master driver can call this from interrupt
+ * context as well. Memory for the Slave status will be freed in
+ * workqueue, once its handled.
+ */
+ spin_lock_irqsave(&bus->spinlock, flags);
+ list_add_tail(&slv_status->node, &bus->status_list);
+ spin_unlock_irqrestore(&bus->spinlock, flags);
+
+ kthread_queue_work(&bus->kworker, &bus->kwork);
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_update_slave_status);
+
/**
* snd_sdw_master_register_driver: This API will register the Master driver
* with the SoundWire bus. It is typically called from the driver's
@@ -910,6 +1967,21 @@ int snd_sdw_master_add(struct sdw_master *master)
dev_dbg(&master->dev, "master [%s] registered\n", master->name);
+ kthread_init_worker(&sdw_bus->kworker);
+ sdw_bus->status_thread = kthread_run(kthread_worker_fn,
+ &sdw_bus->kworker, "%s",
+ dev_name(&master->dev));
+
+ if (IS_ERR(sdw_bus->status_thread)) {
+ dev_err(&master->dev, "error: failed to create status message task\n");
+ ret = PTR_ERR(sdw_bus->status_thread);
+ goto thread_create_failed;
+ }
+
+ kthread_init_work(&sdw_bus->kwork, sdw_handle_slv_status);
+ INIT_LIST_HEAD(&sdw_bus->status_list);
+ spin_lock_init(&sdw_bus->spinlock);
+
/*
* Add bus to the list of buses inside core. This is list of Slave
* devices enumerated on this bus. Adding new devices at end. It can
@@ -920,6 +1992,8 @@ int snd_sdw_master_add(struct sdw_master *master)
return 0;
+thread_create_failed:
+ device_unregister(&master->dev);
dev_reg_failed:
kfree(sdw_bus);
alloc_failed:
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
index 0af1c99..bbba27a 100644
--- a/sound/sdw/sdw_priv.h
+++ b/sound/sdw/sdw_priv.h
@@ -59,6 +59,27 @@
#ifndef _LINUX_SDW_PRIV_H
#define _LINUX_SDW_PRIV_H
+#include <linux/kthread.h>
+#include <linux/spinlock.h>
+
+/* Number of message(s) transferred on bus. */
+#define SDW_NUM_OF_MSG1_XFRD 1
+#define SDW_NUM_OF_MSG2_XFRD 2
+#define SDW_NUM_OF_MSG3_XFRD 3
+#define SDW_NUM_OF_MSG4_XFRD 4
+
+/* Maximum number of Data Ports. */
+#define SDW_MAX_DATA_PORTS 15
+
+/**
+ * Max retries to service Slave interrupts, once Slave is in ALERT state.
+ * Bus driver tries to service interrupt till Slave state is changed to
+ * "ATTACHED_OK". In case Slave remains in ALERT state because of error
+ * condition on Slave like, PLL not getting locked or continuous Jack
+ * sensing, bus driver exits after MAX retries.
+ */
+#define SDW_INTR_STAT_READ_MAX_TRIES 10
+
/**
* sdw_driver: Structure to typecast both Master and Slave driver to generic
* SoundWire driver, to find out the driver type.
@@ -72,16 +93,44 @@ struct sdw_driver {
};
#define to_sdw_driver(d) \
container_of(d, struct sdw_driver, driver)
+
+/**
+ * sdw_slv_status: List of Slave status.
+ *
+ * @node: Node for adding status to list of Slave status.
+ * @status: Slave status.
+ */
+struct sdw_slv_status {
+ struct list_head node;
+ enum sdw_slave_status status[SDW_MAX_DEVICES];
+};
+
/**
* sdw_bus: Bus structure holding bus related information.
*
* @bus_node: Node to add the bus in the sdw_core list.
* @mstr: Master reference for the bus.
+ * @status_thread: Thread to process the Slave status.
+ * @kworker: Worker for updating the Slave status.
+ * @kwork: Work for worker
+ * @status_list: List where status update from master is added. List is
+ * executed one by one.
+ *
+ * @spinlock: Lock to protect the list between work thread and interrupt
+ * context. Bus driver does Slave status processing in the thread
+ * context, spinlock is used to put the status reported by Master into
+ * the status list which is processed by bus driver in thread context
+ * later.
*/
struct sdw_bus {
struct list_head bus_node;
struct sdw_master *mstr;
+ struct task_struct *status_thread;
+ struct kthread_worker kworker;
+ struct kthread_work kwork;
+ struct list_head status_list;
+ spinlock_t spinlock;
};
/**
@@ -117,6 +166,23 @@ struct snd_sdw_core {
*/
void sdw_bank_switch_deferred(struct sdw_master *mstr, struct sdw_msg *msg,
struct sdw_deferred_xfer_data *data);
+
+/**
+ * sdw_enable_disable_dpn_intr: Enable or Disable Slave Data Port interrupt.
+ * This is called by bus driver before prepare and after deprepare of
+ * the ports.
+ *
+ * @sdw_slv: Slave handle.
+ * @port_num: Port number.
+ * @port_direction: Direction of the port configuration while doing
+ * prepare/deprepare.
+ *
+ * @enable: Enable (1) or disable (0) the port interrupts.
+ */
+int sdw_enable_disable_dpn_intr(struct sdw_slave *sdw_slv, int port_num,
+ int port_direction, bool enable);
+
+
/*
* Helper function for bus driver to write messages. Since bus driver
* operates on MIPI defined Slave registers, addr_page1 and addr_page2 is
--
1.7.9.5
This patch adds following SoundWire bus driver APIs.
1. Register SoundWire Master device and driver.
2. Register SoundWire Slave driver.
3. Register Slave device capabilities.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
sound/Kconfig | 2 +
sound/Makefile | 1 +
sound/sdw/Kconfig | 6 +
sound/sdw/Makefile | 1 +
sound/sdw/sdw.c | 886 ++++++++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw_priv.h | 102 ++++++
6 files changed, 998 insertions(+)
create mode 100644 sound/sdw/Kconfig
create mode 100644 sound/sdw/Makefile
create mode 100644 sound/sdw/sdw.c
create mode 100644 sound/sdw/sdw_priv.h
diff --git a/sound/Kconfig b/sound/Kconfig
index 5a240e0..9f67cf7 100644
--- a/sound/Kconfig
+++ b/sound/Kconfig
@@ -108,6 +108,8 @@ source "sound/parisc/Kconfig"
source "sound/soc/Kconfig"
+source "sound/sdw/Kconfig"
+
endif # SND
menuconfig SOUND_PRIME
diff --git a/sound/Makefile b/sound/Makefile
index c41bdf5..914101e 100644
--- a/sound/Makefile
+++ b/sound/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
+obj-$(CONFIG_SOUND_SDW) += sdw/
obj-$(CONFIG_DMASOUND) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/
diff --git a/sound/sdw/Kconfig b/sound/sdw/Kconfig
new file mode 100644
index 0000000..052079f
--- /dev/null
+++ b/sound/sdw/Kconfig
@@ -0,0 +1,6 @@
+config SOUND_SDW
+ tristate
+ help
+ SoundWire interface is typically used for transporting data
+ related to audio functions. Concerned drivers should "select"
+ this.
diff --git a/sound/sdw/Makefile b/sound/sdw/Makefile
new file mode 100644
index 0000000..6ed1881
--- /dev/null
+++ b/sound/sdw/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SOUND_SDW) += sdw.o
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
new file mode 100644
index 0000000..d4e79b8a
--- /dev/null
+++ b/sound/sdw/sdw.c
@@ -0,0 +1,886 @@
+/*
+ * sdw.c - SoundWire bus driver implementation.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <sound/sdw_bus.h>
+#include <sound/sdw_master.h>
+#include <sound/sdw_slave.h>
+
+#include "sdw_priv.h"
+
+/*
+ * Global SoundWire core instance contains list of Masters registered, core
+ * lock and SoundWire stream tags.
+ */
+struct snd_sdw_core snd_sdw_core;
+
+static void sdw_slv_release(struct device *dev)
+{
+ kfree(to_sdw_slave(dev));
+}
+
+static void sdw_mstr_release(struct device *dev)
+{
+ struct sdw_master *mstr = to_sdw_master(dev);
+
+ complete(&mstr->slv_released_complete);
+}
+
+static struct device_type sdw_slv_type = {
+ .groups = NULL,
+ .release = sdw_slv_release,
+};
+
+static struct device_type sdw_mstr_type = {
+ .groups = NULL,
+ .release = sdw_mstr_release,
+};
+
+/**
+ * sdw_slv_verify - return parameter as sdw_slave, or NULL
+ * @dev: device, probably from some driver model iterator
+ *
+ * When traversing the driver model tree, perhaps using driver model
+ * iterators like @device_for_each_child(), you can't assume very much
+ * about the nodes you find. Use this function to avoid oopses caused
+ * by wrongly treating some non-SDW device as an sdw_slave.
+ */
+static struct sdw_slave *sdw_slv_verify(struct device *dev)
+{
+ return (dev->type == &sdw_slv_type)
+ ? to_sdw_slave(dev)
+ : NULL;
+}
+
+/**
+ * sdw_mstr_verify: return parameter as sdw_master, or NULL
+ *
+ * @dev: device, probably from some driver model iterator
+ *
+ * When traversing the driver model tree, perhaps using driver model
+ * iterators like @device_for_each_child(), you can't assume very much
+ * about the nodes you find. Use this function to avoid oopses caused
+ * by wrongly treating some non-SDW device as an sdw_master.
+ */
+static struct sdw_master *sdw_mstr_verify(struct device *dev)
+{
+ return (dev->type == &sdw_mstr_type)
+ ? to_sdw_master(dev)
+ : NULL;
+}
+
+static const struct sdw_slave_id *sdw_match_slv(const struct sdw_slave_id *id,
+ const struct sdw_slave *sdw_slv)
+{
+ const struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ if (!id)
+ return NULL;
+
+ /*
+ * IDs should be NULL terminated like the last ID in the list should
+ * be null, as done for drivers like platform, i2c etc.
+ */
+ while (id->name[0]) {
+ if (strncmp(slv_priv->name, id->name, SOUNDWIRE_NAME_SIZE) == 0)
+ return id;
+
+ id++;
+ }
+
+ return NULL;
+}
+
+static const struct sdw_master_id *sdw_match_mstr(
+ const struct sdw_master_id *id,
+ const struct sdw_master *sdw_mstr)
+{
+ if (!id)
+ return NULL;
+
+ /*
+ * IDs should be NULL terminated like the last ID in the list should
+ * be null, as done for drivers like platform, i2c etc.
+ */
+ while (id->name[0]) {
+ if (strncmp(sdw_mstr->name, id->name, SOUNDWIRE_NAME_SIZE) == 0)
+ return id;
+ id++;
+ }
+ return NULL;
+}
+
+static int sdw_slv_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_slave *sdw_slv;
+ struct sdw_driver *sdw_drv = to_sdw_driver(driver);
+ struct sdw_slave_driver *drv;
+ int ret = 0;
+
+
+ if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
+ return ret;
+
+ drv = to_sdw_slave_driver(driver);
+ sdw_slv = to_sdw_slave(dev);
+
+ /*
+ * We are matching based on the dev_id field, dev_id field is unique
+ * based on part_id and manufacturer id. Device will be registered
+ * based on dev_id and driver will also have same dev_id for device
+ * its controlling.
+ */
+ ret = (sdw_match_slv(drv->id_table, sdw_slv) != NULL);
+
+ if (ret < 0)
+ sdw_slv->priv.driver = drv;
+
+ return ret;
+}
+
+static int sdw_mstr_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_master *sdw_mstr;
+ struct sdw_driver *sdw_drv = to_sdw_driver(driver);
+ struct sdw_master_driver *drv;
+ int ret = 0;
+
+ if (sdw_drv->driver_type != SDW_DRIVER_TYPE_MASTER)
+ return ret;
+
+ drv = to_sdw_master_driver(driver);
+ sdw_mstr = to_sdw_master(dev);
+
+ ret = (sdw_match_mstr(drv->id_table, sdw_mstr) != NULL);
+
+ if (driver->name && !ret)
+ ret = (strncmp(sdw_mstr->name, driver->name,
+ SOUNDWIRE_NAME_SIZE) == 0);
+
+ if (ret < 0)
+ sdw_mstr->driver = drv;
+
+ return ret;
+}
+
+static int sdw_mstr_probe(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+ struct sdw_master *mstr = to_sdw_master(dev);
+ int ret;
+
+ ret = dev_pm_domain_attach(dev, true);
+
+ if (ret != -EPROBE_DEFER) {
+ ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
+ if (ret < 0)
+ dev_pm_domain_detach(dev, true);
+ }
+
+ return ret;
+}
+
+static int sdw_slv_probe(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver);
+ struct sdw_slave *sdwslv = to_sdw_slave(dev);
+ int ret;
+
+ ret = dev_pm_domain_attach(dev, true);
+
+ if (ret != -EPROBE_DEFER) {
+ ret = sdrv->probe(sdwslv, sdw_match_slv(sdrv->id_table,
+ sdwslv));
+ if (ret < 0)
+ dev_pm_domain_detach(dev, true);
+ }
+
+ return ret;
+}
+
+static int sdw_mstr_remove(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+ int ret;
+
+ ret = sdrv->remove(to_sdw_master(dev));
+ dev_pm_domain_detach(dev, true);
+ return ret;
+
+}
+
+static int sdw_slv_remove(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver);
+ int ret;
+
+ ret = sdrv->remove(to_sdw_slave(dev));
+ dev_pm_domain_detach(dev, true);
+
+ return ret;
+}
+
+static void sdw_slv_shutdown(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv =
+ to_sdw_slave_driver(dev->driver);
+
+ sdrv->shutdown(to_sdw_slave(dev));
+}
+
+static void sdw_mstr_shutdown(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+
+ sdrv->shutdown(to_sdw_master(dev));
+}
+
+static int sdw_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_slave *sdw_slv;
+ struct sdw_master *sdw_mstr;
+
+ sdw_slv = sdw_slv_verify(dev);
+ if (sdw_slv)
+ return sdw_slv_match(dev, driver);
+
+ sdw_mstr = sdw_mstr_verify(dev);
+ if (sdw_mstr)
+ return sdw_mstr_match(dev, driver);
+
+ /*
+ * Returning 0 to calling function means match not found, so calling
+ * function will not call probe
+ */
+ return 0;
+
+}
+
+static const struct dev_pm_ops soundwire_pm = {
+ .suspend = pm_generic_suspend,
+ .resume = pm_generic_resume,
+ SET_RUNTIME_PM_OPS(
+ pm_generic_runtime_suspend,
+ pm_generic_runtime_resume,
+ NULL)
+};
+
+static struct bus_type sdw_bus_type = {
+ .name = "soundwire",
+ .match = sdw_match,
+ .pm = &soundwire_pm,
+};
+
+/**
+ * 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.
+ *
+ * @driver: Master Driver to be associated with Master interface.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_master_register_driver(struct sdw_master_driver *driver,
+ struct module *owner)
+{
+ int ret;
+
+ if (!driver->probe)
+ return -EINVAL;
+
+ if (!driver->ops->xfer_msg || !driver->ops->reset_page_addr)
+ return -EINVAL;
+
+ if (!driver->port_ops->dpn_set_port_params ||
+ !driver->port_ops->dpn_set_port_transport_params ||
+ !driver->port_ops->dpn_port_enable_ch)
+ return -EINVAL;
+
+ driver->driver.probe = sdw_mstr_probe;
+
+ if (driver->remove)
+ driver->driver.remove = sdw_mstr_remove;
+ if (driver->shutdown)
+ driver->driver.shutdown = sdw_mstr_shutdown;
+
+ /* add the driver to the list of sdw drivers in the driver core */
+ driver->driver.owner = owner;
+ driver->driver.bus = &sdw_bus_type;
+
+ /*
+ * When registration returns, the driver core will have called
+ * probe() for all matching-but-unbound Slaves, devices which are
+ * not bind to any driver still.
+ */
+ ret = driver_register(&driver->driver);
+ if (ret)
+ return ret;
+
+ pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
+
+/**
+ * snd_sdw_slave_driver_register: SoundWire Slave driver registration with
+ * bus. This API will register the Slave driver with the SoundWire bus.
+ * It is typically called from the driver's module-init function.
+ *
+ * @driver: Driver to be associated with Slave.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_slave_driver_register(struct sdw_slave_driver *driver,
+ struct module *owner)
+{
+ int ret;
+
+ if (driver->probe)
+ driver->driver.probe = sdw_slv_probe;
+ if (driver->remove)
+ driver->driver.remove = sdw_slv_remove;
+ if (driver->shutdown)
+ driver->driver.shutdown = sdw_slv_shutdown;
+
+ /* Add the driver to the list of sdw drivers in the driver core */
+ driver->driver.owner = owner;
+ driver->driver.bus = &sdw_bus_type;
+
+ /*
+ * When registration returns, the driver core will have called
+ * probe() for all matching-but-unbound Slaves.
+ */
+ ret = driver_register(&driver->driver);
+ if (ret)
+ return ret;
+
+ pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_slave_driver_register);
+
+static int sdw_copy_aud_mod_prop(struct sdw_port_aud_mode_prop *slv_prop,
+ struct sdw_port_aud_mode_prop *prop)
+{
+ /*
+ * Currently goto is used in API to perform different
+ * operations. TODO: Avoid usage of goto statement
+ */
+ memcpy(slv_prop, prop, sizeof(*prop));
+
+ if (!prop->num_bus_freq_cfgs)
+ goto handle_sample_rate;
+
+ slv_prop->clk_freq_buf = kcalloc(prop->num_bus_freq_cfgs,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+
+ if (!slv_prop->clk_freq_buf)
+ goto mem_error;
+
+ memcpy(slv_prop->clk_freq_buf, prop->clk_freq_buf,
+ (prop->num_bus_freq_cfgs *
+ sizeof(unsigned int)));
+
+handle_sample_rate:
+
+ if (!prop->num_sample_rate_cfgs)
+ return 0;
+
+ slv_prop->sample_rate_buf = kcalloc(prop->num_sample_rate_cfgs,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+
+ if (!slv_prop->sample_rate_buf)
+ goto mem_error;
+
+ memcpy(slv_prop->sample_rate_buf, prop->sample_rate_buf,
+ (prop->num_sample_rate_cfgs *
+ sizeof(unsigned int)));
+
+ return 0;
+
+mem_error:
+ kfree(prop->clk_freq_buf);
+ kfree(slv_prop->sample_rate_buf);
+ return -ENOMEM;
+
+}
+
+static int sdw_update_dpn_caps(struct sdw_dpn_caps *slv_dpn_cap,
+ struct sdw_dpn_caps *dpn_cap)
+{
+ int j, ret = 0;
+ struct sdw_port_aud_mode_prop *slv_prop, *prop;
+
+ /*
+ * Currently goto is used in API to perform different
+ * operations. TODO: Avoid usage of goto statement
+ */
+
+ /*
+ * slv_prop and prop are using to make copy of mode properties.
+ * prop holds mode properties received which needs to be updated to
+ * slv_prop.
+ */
+
+ memcpy(slv_dpn_cap, dpn_cap, sizeof(*dpn_cap));
+
+ /*
+ * Copy bps (bits per sample) buffer as part of Slave capabilities
+ */
+ if (!dpn_cap->num_bps)
+ goto handle_ch_cnt;
+
+ slv_dpn_cap->bps_buf = kcalloc(dpn_cap->num_bps, sizeof(u8),
+ GFP_KERNEL);
+
+ if (!slv_dpn_cap->bps_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ memcpy(slv_dpn_cap->bps_buf, dpn_cap->bps_buf,
+ (dpn_cap->num_bps * sizeof(u8)));
+
+handle_ch_cnt:
+ if (!dpn_cap->num_ch_cnt)
+ goto handle_audio_mode_prop;
+
+ slv_dpn_cap->ch_cnt_buf = kcalloc(dpn_cap->num_ch_cnt, sizeof(u8),
+ GFP_KERNEL);
+ if (!dpn_cap->num_ch_cnt) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Copy channel count buffer as part of Slave capabilities */
+ memcpy(slv_dpn_cap->ch_cnt_buf, dpn_cap->ch_cnt_buf,
+ (dpn_cap->num_ch_cnt * sizeof(u8)));
+
+handle_audio_mode_prop:
+
+ slv_dpn_cap->mode_properties = kzalloc((sizeof(*slv_prop) *
+ dpn_cap->num_audio_modes),
+ GFP_KERNEL);
+
+ if (!slv_dpn_cap->mode_properties) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ for (j = 0; j < dpn_cap->num_audio_modes; j++) {
+
+ prop = &dpn_cap->mode_properties[j];
+ slv_prop = &slv_dpn_cap->mode_properties[j];
+
+ /* Copy audio properties as part of Slave capabilities */
+ ret = sdw_copy_aud_mod_prop(slv_prop, prop);
+ if (ret < 0)
+ goto error;
+ }
+
+ return ret;
+
+error:
+ kfree(slv_dpn_cap->mode_properties);
+ kfree(slv_dpn_cap->ch_cnt_buf);
+ kfree(slv_dpn_cap->bps_buf);
+ return ret;
+
+}
+
+/* Free all the memory allocated for registering the capabilities */
+static void sdw_unregister_slv_caps(struct sdw_slave *sdw,
+ unsigned int num_port_direction)
+{
+ int i, j, k;
+ struct sdw_slave_caps *caps = &sdw->priv.caps;
+ struct sdw_dpn_caps *dpn_cap;
+ struct sdw_port_aud_mode_prop *mode_prop;
+ u8 ports;
+
+ for (i = 0; i < num_port_direction; i++) {
+
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+ for (j = 0; j < ports; j++) {
+ dpn_cap = &caps->dpn_caps[i][j];
+ kfree(dpn_cap->bps_buf);
+ kfree(dpn_cap->ch_cnt_buf);
+
+ for (k = 0; k < dpn_cap->num_audio_modes; k++) {
+ mode_prop = dpn_cap->mode_properties;
+ kfree(mode_prop->clk_freq_buf);
+ kfree(mode_prop->sample_rate_buf);
+ }
+ }
+ }
+}
+
+static inline void sdw_copy_slv_caps(struct sdw_slave *sdw,
+ struct sdw_slave_caps *caps)
+{
+ struct sdw_slave_caps *slv_caps;
+
+ slv_caps = &sdw->priv.caps;
+
+ memcpy(slv_caps, caps, sizeof(*slv_caps));
+}
+
+/**
+ * snd_sdw_slave_register_caps: Register Slave device capabilities to the
+ * bus driver. Since bus driver handles bunch of Slave register
+ * programming it should be aware of Slave device capabilities. Slave
+ * device is attached to bus based on enumeration. Once Slave driver is
+ * attached to device and probe of Slave driver is called on device and
+ * driver binding, Slave driver should call this function to register
+ * its capabilities to bus. This should be the very first function to
+ * bus driver from Slave driver once Slave driver is registered and
+ * probed.
+ *
+ * @slave: SoundWire Slave handle.
+ * @cap: Slave caps to be registered to bus driver.
+ */
+int snd_sdw_slave_register_caps(struct sdw_slave *slave,
+ struct sdw_slave_caps *cap)
+{
+ struct sdw_slave_caps *caps;
+ struct sdw_dpn_caps *slv_dpn_cap, *dpn_cap;
+ int i, j, ret;
+ u8 ports;
+
+ caps = &slave->priv.caps;
+
+ sdw_copy_slv_caps(slave, cap);
+
+ for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) {
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+
+ caps->dpn_caps[i] = kzalloc((sizeof(*slv_dpn_cap) *
+ ports), GFP_KERNEL);
+
+ if (caps->dpn_caps[i] == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ }
+
+ for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) {
+
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+
+ for (j = 0; j < ports; j++) {
+
+ dpn_cap = &cap->dpn_caps[i][j];
+ slv_dpn_cap = &caps->dpn_caps[i][j];
+
+ ret = sdw_update_dpn_caps(&caps->dpn_caps[i][j],
+ &cap->dpn_caps[i][j]);
+ if (ret < 0) {
+ dev_err(&slave->mstr->dev, "Failed to update Slave caps ret = %d\n", ret);
+ goto error;
+ }
+ }
+ }
+
+ slave->priv.slave_cap_updated = true;
+
+ return 0;
+
+error:
+ sdw_unregister_slv_caps(slave, i);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_slave_register_caps);
+
+/**
+ * snd_sdw_master_add: Registers the SoundWire Master interface. This needs
+ * to be called for each Master interface supported by SoC. This
+ * represents One clock and data line (Optionally multiple data lanes)
+ * of Master interface.
+ *
+ * @master: the Master to be added.
+ */
+int snd_sdw_master_add(struct sdw_master *master)
+{
+ int i, id, ret;
+ struct sdw_bus *sdw_bus = NULL;
+
+ /* Sanity checks */
+ if (unlikely(master->name[0] == '\0')) {
+ pr_err("sdw-core: Attempt to register a master with no name!\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Always start bus with 0th Index */
+ id = idr_alloc(&snd_sdw_core.idr, master, 0, 0, GFP_KERNEL);
+
+ if (id < 0) {
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return id;
+ }
+
+ master->nr = id;
+
+ /*
+ * Initialize the DeviceNumber in the Master structure. Each of
+ * these is assigned to the Slaves enumerating on this Master
+ * interface.
+ */
+ for (i = 0; i <= SDW_MAX_DEVICES; i++)
+ master->sdw_addr[i].dev_num = i;
+
+ mutex_init(&master->lock);
+ mutex_init(&master->msg_lock);
+ INIT_LIST_HEAD(&master->slv_list);
+ INIT_LIST_HEAD(&master->mstr_rt_list);
+
+ sdw_bus = kzalloc(sizeof(*sdw_bus), GFP_KERNEL);
+ if (!sdw_bus) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ sdw_bus->mstr = master;
+ master->bus = sdw_bus;
+
+ dev_set_name(&master->dev, "sdw-%d", master->nr);
+ master->dev.bus = &sdw_bus_type;
+ master->dev.type = &sdw_mstr_type;
+
+ ret = device_register(&master->dev);
+ if (ret < 0)
+ goto dev_reg_failed;
+
+ dev_dbg(&master->dev, "master [%s] registered\n", master->name);
+
+ /*
+ * Add bus to the list of buses inside core. This is list of Slave
+ * devices enumerated on this bus. Adding new devices at end. It can
+ * be added at any location in list.
+ */
+ list_add_tail(&sdw_bus->bus_node, &snd_sdw_core.bus_list);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ return 0;
+
+dev_reg_failed:
+ kfree(sdw_bus);
+alloc_failed:
+ idr_remove(&snd_sdw_core.idr, master->nr);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_add);
+
+static void sdw_unregister_slv(struct sdw_slave *sdw_slv)
+{
+ struct sdw_master *mstr;
+
+ mstr = sdw_slave_to_master(sdw_slv);
+
+ sdw_unregister_slv_caps(sdw_slv, SDW_MAX_PORT_DIRECTIONS);
+
+ mutex_lock(&mstr->lock);
+ list_del(&sdw_slv->priv.node);
+ mutex_unlock(&mstr->lock);
+
+ mstr->sdw_addr[sdw_slv->dev_num].assigned = false;
+
+ device_unregister(&sdw_slv->dev);
+ kfree(sdw_slv);
+}
+
+static int __unregister_slv(struct device *dev, void *dummy)
+{
+ struct sdw_slave *slave = sdw_slv_verify(dev);
+
+ if (slave)
+ sdw_unregister_slv(slave);
+
+ return 0;
+}
+
+/**
+ * snd_sdw_master_del - unregister SDW Master
+ *
+ * @master: the Master being unregistered
+ */
+void snd_sdw_master_del(struct sdw_master *master)
+{
+ struct sdw_master *found;
+
+ /* First make sure that this Master was ever added */
+ mutex_lock(&snd_sdw_core.core_mutex);
+ found = idr_find(&snd_sdw_core.idr, master->nr);
+
+ if (found != master) {
+ pr_debug("sdw-core: attempting to delete unregistered master [%s]\n",
+ master->name);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return;
+ }
+ /*
+ * Detach any active Slaves. This can't fail, thus we do not check
+ * the returned value.
+ */
+ device_for_each_child(&master->dev, NULL, __unregister_slv);
+
+ /* device name is gone after device_unregister */
+ dev_dbg(&master->dev, "master [%s] unregistered\n", master->name);
+
+ /* wait until all references to the device are gone */
+ init_completion(&master->slv_released_complete);
+ device_unregister(&master->dev);
+ wait_for_completion(&master->slv_released_complete);
+
+ /* free bus id */
+ idr_remove(&snd_sdw_core.idr, master->nr);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ /*
+ * Clear the device structure in case this Master is ever going to
+ * be added again
+ */
+ memset(&master->dev, 0, sizeof(master->dev));
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_del);
+
+/**
+ * snd_sdw_master_get: Return the Master handle from Master number.
+ * Increments the reference count of the module. Similar to
+ * i2c_get_adapter.
+ *
+ * @nr: Master number.
+ *
+ * Returns Master handle on success, else NULL
+ */
+struct sdw_master *snd_sdw_master_get(int nr)
+{
+ struct sdw_master *master;
+
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ master = idr_find(&snd_sdw_core.idr, nr);
+ if (master && !try_module_get(master->driver->driver.owner))
+ master = NULL;
+
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ return master;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_get);
+
+/**
+ * snd_sdw_master_put: Reverses the effect of sdw_master_get
+ *
+ * @master: Master handle.
+ */
+void snd_sdw_master_put(struct sdw_master *master)
+{
+ if (master)
+ module_put(master->driver->driver.owner);
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_put);
+
+static void sdw_exit(void)
+{
+ bus_unregister(&sdw_bus_type);
+}
+
+static int sdw_init(void)
+{
+ int retval;
+
+ mutex_init(&snd_sdw_core.core_mutex);
+ INIT_LIST_HEAD(&snd_sdw_core.bus_list);
+ idr_init(&snd_sdw_core.idr);
+ retval = bus_register(&sdw_bus_type);
+
+ if (retval)
+ bus_unregister(&sdw_bus_type);
+ return retval;
+}
+
+subsys_initcall(sdw_init);
+module_exit(sdw_exit);
+
+MODULE_AUTHOR("Hardik Shah <[email protected]>");
+MODULE_AUTHOR("Sanyog Kale <[email protected]>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SoundWire bus driver");
+MODULE_ALIAS("platform:soundwire");
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
new file mode 100644
index 0000000..5911aa6
--- /dev/null
+++ b/sound/sdw/sdw_priv.h
@@ -0,0 +1,102 @@
+/*
+ * sdw_priv.h - Private definition for SoundWire bus interface.
+ *
+ * Author: Hardik Shah <[email protected]>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_PRIV_H
+#define _LINUX_SDW_PRIV_H
+
+/**
+ * sdw_driver: Structure to typecast both Master and Slave driver to generic
+ * SoundWire driver, to find out the driver type.
+ *
+ * @driver_type: Type of SoundWire driver, Master or Slave.
+ * @driver: Generic Linux driver.
+ */
+struct sdw_driver {
+ enum sdw_driver_type driver_type;
+ struct device_driver driver;
+};
+#define to_sdw_driver(d) \
+ container_of(d, struct sdw_driver, driver)
+/**
+ * sdw_bus: Bus structure holding bus related information.
+ *
+ * @bus_node: Node to add the bus in the sdw_core list.
+ * @mstr: Master reference for the bus.
+ */
+
+struct sdw_bus {
+ struct list_head bus_node;
+ struct sdw_master *mstr;
+};
+
+/**
+ * snd_sdw_core: Global SoundWire structure. It handles all the streams
+ * spawned across masters and has list of bus structure per every
+ * Master registered.
+ *
+ * @bus_list: List of all the bus instance.
+ * @core_mutex: Global lock for all bus instances.
+ * @idr: For identifying the registered buses.
+ */
+struct snd_sdw_core {
+ struct list_head bus_list;
+ struct mutex core_mutex;
+ struct idr idr;
+};
+
+#endif /* _LINUX_SDW_PRIV_H */
--
1.7.9.5
This patch adds summary documentation of SoundWire bus driver support in
Linux.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
Documentation/sound/alsa/sdw/summary.txt | 253 ++++++++++++++++++++++++++++++
1 file changed, 253 insertions(+)
create mode 100644 Documentation/sound/alsa/sdw/summary.txt
diff --git a/Documentation/sound/alsa/sdw/summary.txt b/Documentation/sound/alsa/sdw/summary.txt
new file mode 100644
index 0000000..dc62817
--- /dev/null
+++ b/Documentation/sound/alsa/sdw/summary.txt
@@ -0,0 +1,253 @@
+SoundWire
+===========
+
+SoundWire is a new interface ratified in 2015 by the MIPI Alliance.
+SoundWire is used for transporting data typically related to audio
+functions. SoundWire interface is optimized to integrate audio devices
+in mobile or mobile inspired systems.
+
+SoundWire is a 2-Pin interface with data and clock line. It facilitates
+development of low cost, efficient, high performance systems. Broad
+level key features of SoundWire interface include:
+
+1. Transporting all of payload data channels, control information, and
+setup commands over a single two-pin interface.
+2. Lower clock frequency, and hence lower power consumption, by use of
+DDR (Dual Data Rate) data transmission.
+3. Clock scaling and optional multiple data lanes to give wide
+flexibility in data rate to match system requirements.
+4. Device status monitoring, including interrupt-style alerts to the
+Master.
+
+The SoundWire protocol supports up to eleven Slave interfaces. All the
+interfaces share the common bus containing data and clock line. Each of
+the Slaves can support up to 14 Data Ports. 13 Data Ports are dedicated
+to audio transport. Data Port0 is dedicated to transport of Bulk control
+information, each of the audio Data Ports (1..14) can support up to 8
+Channels in transmit or receiving mode (typically fixed direction but
+configurable direction is enabled by the specification). Bandwidth
+restrictions to ~19.2..24.576Mbits/s don't however allow for 11*13*8
+channels to be transmitted simultaneously.
+
+Below figure shows the SoundWire Master and Slave devices sample
+connectivity.
+
+|---------------| Clock Signal |---------------|
+| Master |-------|-------------------------------| Slave |
+| Interface | | Data Signal | Interface |
+| |-------|-------|-----------------------| 1 |
+|---------------| | | |---------------|
+ | |
+ | |
+ | |
+ |---------------|
+ | Slave |
+ | Interface |
+ | 2 |
+ |---------------|
+
+Terminology
+=============
+
+Following is the terminology used in Linux kernel driver. SoundWire
+Master interfaces registers as SoundWire Master device and Slave
+interfaces as SoundWire Slave device.
+
+Bus:
+Similar to I2C/AC97 bus driver, implements Linux bus for SoundWire
+handles the SoundWire protocol and manages bus. Programs all the MIPI
+defined Slave registers.
+
+Master:
+Registers as SoundWire Master device (Linux Device). Similar to
+i2c_adapter. One bus instance is created for every Master interface
+registered to the bus driver.
+
+Slave:
+Registers as SoundWire Slave device (Linux Device). Similar to
+i2c_client. Multiple Slave interfaces can register to same bus.
+
+Master driver:
+Driver controlling the Master device. Registers a set of ops to abstract
+Master registers away, so that the bus driver can control the bus in a
+hardware-agnostic manner.
+
+Slave driver:
+Driver controlling the Slave device. MIPI-specified registers are
+controlled directly by the bus driver (and transmitted through the
+Master driver/interface). Any implementation-defined Slave register is
+controlled by Slave driver. In practice, it is expected that the Slave
+driver relies on regmap and does not request direct register access.
+
+
+Programming interfaces (SoundWire Master interface Driver)
+==========================================================
+
+SoundWire bus driver supports programming interfaces for the SoundWire
+Master and SoundWire Slave devices. All the code uses the "sdw" prefix
+commonly used by SOC designers and 3rd party vendors.
+
+Each of the SoundWire Master interface needs to be registered to the Bus
+driver. Master interface capabilities also needs to be registered to
+bus driver since there is no discovery mechanism as a part of SoundWire
+protocol.
+
+The Master interface along with the Master interface capabilities are
+registered based on board file, DT or ACPI.
+
+Following is the API to register the SoundWire Master device.
+
+static int my_sdw_register_master()
+{
+ struct sdw_master master;
+ struct sdw_master_capabilities *m_cap;
+
+ m_cap = &master.mstr_capabilities;
+
+ /*
+ * Fill the Master device capability, this is required
+ * by bus driver to handle bus configurations.
+ */
+ m_cap->highphy_capable = false;
+ m_cap->monitor_handover_supported = false;
+ m_cap->sdw_dp0_supported = 1;
+ m_cap->num_data_ports = INTEL_SDW_MAX_PORTS;
+
+ return snd_sdw_master_add(&master);
+}
+
+Master driver gets registered for controlling the Master device. It
+provides the callback functions to the bus driver to control the bus in
+device specific way. Device and Driver binds according to the standard
+Linux device-driver bind model. Master driver is registered from the
+driver init code. Below code shows the sample Master driver
+registration.
+
+static struct sdw_master_driver intel_sdw_mstr_driver = {
+ .driver_type = SDW_DRIVER_TYPE_MASTER,
+ .driver = {
+ .name = "intel_sdw_mstr",
+ .pm = &intel_sdw_pm_ops,
+ },
+
+ .probe = intel_sdw_probe,
+ .remove = intel_sdw_remove,
+ .mstr_ops = &intel_sdw_master_ops,
+ .mstr_port_ops = &intel_sdw_master_port_ops,
+};
+
+static int __init intel_sdw_init(void) {
+ return snd_sdw_master_register_driver(&intel_sdw_mstr_driver);
+}
+
+As shown above Master driver registers itself with bus using
+"sdw_mstr_driver_register" API, It registers using set of "mstr_ops" and
+"mstr_port_ops" callback functions to the bus driver.
+
+"mstr_ops" is used by bus driver to control the bus in the hardware
+specific way. It includes bus control functions such as sending the
+SoundWire read/write messages on bus. The Bus driver also defines the
+clock frequency and frameshape allocation needed by active stream and
+configuration messages that need to be transmitted over the bus, to
+maximize the bandwidth needed while minimizing the power. The "mstr_ops"
+structure abstracts the hardware details of the Master from the bus
+driver for setting up of the clock frequency and frameshape.
+
+"mstr_port_ops" is used by bus driver to setup the Port parameters of
+the Master interface Port. Master interface Port register map is not
+defined by MIPI specification, so bus driver calls the "mstr_port_ops"
+call back function to do Port operations like "Port Prepare", "Port
+Transport params set", "Port enable and disable". The implementation of
+the Master driver can then perform hardware-specific configurations.
+
+Programming interfaces (SoundWire Slave Driver)
+===============================================
+
+The MIPI specification requires each Slave interface to expose a unique
+48-bit identifier, stored in 6 read only dev_id registers. This dev_id
+identifier contains vendor and part information, as well as a field
+enabling to differentiate between identical components. An additional
+class field is currently unused. Slave driver is written for the
+specific 48-bit identifier, Bus driver enumerates the Slave device based
+on in 48-bit identifier. Slave device and driver match is done based on
+this 48-bit identifier. Probe of the Slave driver is called by bus on
+successful match between device and driver id. A parent/child
+relationship is enforced between Slave and Master devices (the logical
+representation is aligned with the physical connectivity).
+
+The information on Master/Slave dependencies is stored in platform data,
+board-file, ACPI or DT. The MIPI Software specification defines an
+additional link_id parameters for controllers that have multiple Master
+interfaces. The dev_id registers are only unique in the scope of a link,
+and the link_ID unique in the scope of a controller. Both dev_id and
+link_id are not necessarily unique at the system level but the
+parent/child information is used to avoid ambiguity.
+
+
+static const struct sdw_slave_id intel_id[] = {
+ {"0b:00:f9:84:01:02", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(sdw, intel_id);
+
+static struct sdw_slave_driver intel_sdw_driver = {
+ .driver_type = SDW_DRIVER_TYPE_SLAVE,
+ .driver = {
+ .name = "intel",
+ },
+ .probe = intel_sdw_probe,
+ .remove = intel_sdw_remove,
+ .id_table = intel_id,
+};
+
+module_sdw_slave_driver(intel_sdw_driver);
+
+Slave driver needs to register the capabilities (number of ports,
+formats supported, etc) of the Slave device to the bus driver after
+registration. This is the first call to be called by Slave driver on
+probe. Bus driver needs to know a set of Slave capabilities to program
+Slave registers and to control the bus reconfigurations.
+
+Below code shows the sample for it. Bus drivers programs the MIPI
+defined registers of the Slave.
+
+static int slave_register_sdw_capabilities(struct sdw_slave *sdw,
+ const struct sdw_slave_id *sdw_id)
+{
+ struct sdw_slv_capabilities cap;
+ struct sdw_slv_dpn_capabilities *dpn_cap = NULL;
+ struct port_audio_mode_properties *prop = NULL;
+ int i, j;
+
+ cap.wake_up_unavailable = true;
+ cap.test_mode_supported = false;
+ cap.clock_stop1_mode_supported = false;
+ cap.simplified_clock_stop_prepare = false;
+ cap.highphy_capable = true;
+ cap.paging_supported = false;
+ cap.bank_delay_support = false;
+ cap.port_15_read_behavior = 0;
+ cap.sdw_dp0_supported = false;
+ cap.num_of_sdw_ports = 3;
+ [...] additional configuration.
+
+ return snd_sdw_slave_register_caps(sdw, &cap);
+
+}
+
+Future enhancements to be done:
+===============================
+
+1. Currently BRA transfers is not supported.
+2. Bus driver supports only Single Data lane Slaves and Masters
+interfaces.
+
+Links:
+=====
+
+SoundWire MIPI specification 1.1 is available at:
+https://members.mipi.org/wg/All-Members/document/70290
+
+SoundWire MIPI DisCo (Discovery and Configuration) specification is in
+press.
--
1.7.9.5
This patch adds following documentation:
1. Bus driver locking mechanism.
2. Bus driver error handling.
Signed-off-by: Hardik Shah <[email protected]>
Signed-off-by: Sanyog Kale <[email protected]>
Reviewed-by: Pierre-Louis Bossart <[email protected]>
---
Documentation/sound/alsa/sdw/error_handling.txt | 71 +++++++++++++++++++++++
Documentation/sound/alsa/sdw/locking.txt | 64 ++++++++++++++++++++
2 files changed, 135 insertions(+)
create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt
create mode 100644 Documentation/sound/alsa/sdw/locking.txt
diff --git a/Documentation/sound/alsa/sdw/error_handling.txt b/Documentation/sound/alsa/sdw/error_handling.txt
new file mode 100644
index 0000000..9441cfa
--- /dev/null
+++ b/Documentation/sound/alsa/sdw/error_handling.txt
@@ -0,0 +1,71 @@
+The SoundWire PHY was designed with care and errors on the bus are going
+to be very unlikely, and if they happen they should be limited to single
+bit errors. Examples of this design can be found in the the
+synchronization mechanism (sync loss after two errors) and short CRCs
+used for the Bulk Register Access.
+
+The errors can be detected with multiple mechanisms:
+
+1. Bus clash or parity errors: This mechanism relies on low-level
+detectors that are independent of the payload and usages, and they
+cover both control and audio data. The current implementation only logs
+such errors. Improvements could consist in invalidating an entire
+programming sequence and restarting from a known position. In the case
+of such errors happening outside of a control/command sequence, there is
+no concealment or recovery for audio data enabled by the SoundWire
+protocol, the location of the error will also impact its audibility
+(most-significant bits will be more impacted in PCM), and after a number
+of such errors are detected the bus might be reset. Note that bus
+clashes due to programming errors (two streams using the same bit slots)
+or electrical issues during the transmit/receive transition cannot be
+distinguished, although a recurring bus clash when audio is enabled is a
+clear hint of a bus allocation issue. The interrupt mechanism can also
+help identify Slaves which detected a Bus Clash or a Parity Error, but
+they may not be responsible for the errors so resetting them
+individually is not a viable recovery strategy.
+
+2. Command status: Each command is associated with a status, which only
+covers transmission of the data between devices. The ACK status
+indicates that the command was received and will be executed by the end
+of the current frame. A NAK indicates that the command was in error and
+will not be applied. In case of a bad programming (command sent to
+non-existent Slave or to a non-implemented register) or electrical
+issue, no response signals the command was ignored. Some Master
+implementations allow for a command to be retransmitted several times.
+If the retransmission fails, backtracking and restarting the entire
+programming sequence might be a solution. Alternatively some
+implementations might directly issue a bus reset and re-enumerate all
+devices.
+
+3. Timeouts: In a number of cases such as ChannelPrepare or
+ClockStopPrepare, the bus driver is supposed to poll a register field
+until it transitions to a NotFinished value of zero. The MIPI SoundWire
+spec 1.1 does not define timeouts but the MIPI SoundWire DisCo document
+adds recommendation on timeouts. If such configurations do not complete,
+the driver will return a -ETIMEOUT. Such timeouts are symptoms of a
+faulty Slave device and are likely impossible to recover from.
+
+
+Errors during global reconfiguration sequences are extremely difficult
+to handle:
+
+1. BankSwitch: An error during the last command issuing a BankSwitch is
+difficult to backtrack from. Retransmitting the Bank Switch command
+may be possible in a single segment setup, but this can lead to
+synchronization problems when enabling multiple bus segments (a command
+with side effects such as frame reconfiguration would be handled at
+different times). A global hard-reset might be the best solution.
+
+2. ClockStop: If one Slave is not capable of engaging the clock stop and
+the system still needs to suspend, the priority might be to suspend
+anyway and reset the bus on startup. This would prevent any active
+standby/always-on activity but would not impact power targets.
+
+Note that SoundWire does not provide a detection mechanism for writing
+illegal values in valid registers. In a number of cases the standard
+even mentions that the Slave might behave in implementation-defined
+registers. The bus driver implementation does not provide a recovery
+mechanism for such errors, Slave or Master driver implementers are
+responsible for writing valid values in valid registers and implement
+additional range checking if needed.
+
diff --git a/Documentation/sound/alsa/sdw/locking.txt b/Documentation/sound/alsa/sdw/locking.txt
new file mode 100644
index 0000000..650162f
--- /dev/null
+++ b/Documentation/sound/alsa/sdw/locking.txt
@@ -0,0 +1,64 @@
+This document explains locking mechanism of the SoundWire bus driver.
+Following types of lock are used in SoundWire bus driver.
+
+1. Core lock
+2. Master lock
+3. Stream lock
+4. Message lock
+
+1. Core lock: Global SoundWire bus driver lock. Core lock is used to
+serialize each of the following operation(s) within SoundWire bus
+driver.
+ - Addition and removal of Master.
+ - Acquire "Master lock" of each Master associated with the
+ aggregated stream.
+
+
+2. Master lock: SoundWire bus instance lock. Master lock is used to
+serialize each of the following operation(s) within SoundWire bus
+instance.
+ - Addition and removal of Slave(s).
+ - Prepare and enable, disable and de-prepare.
+
+
+3. Stream lock: SoundWire stream lock. Stream lock is used to serialize
+access of stream data structure for a SoundWire stream.
+
+
+4. Message lock: SoundWire message transfer lock. This lock is used to
+serialize the message transfers(read/write) within the SoundWire bus
+instance.
+
+
+Lock Hierarchy
+==============
+
+- Core lock is the parent of Master and Stream lock.
+- Master lock is parent of Message lock.
+- Master and Stream locks are independent of each other.
+
+Example
+=======
+
+Below example shows how locks are acquired during prepare and enable
+operation for aggregated stream. In this example, stream 1 is associated
+with Master 1 and Master 2.
+
+1. Acquire Core lock.
+2. Acquire Master 1 lock.
+3. Acquire Master 2 lock.
+4. Release Core lock.
+
+5. Prepare operation.
+ 5.1 Acquire Message lock.
+ 5.2 Transfer message on bus (Single message transfer example).
+ 5.3 Release Message lock.
+6. Enable operation.
+ 6.1 Acquire Message lock.
+ 6.2 Transfer message on bus (Single message transfer example).
+ 6.3 Release Message lock.
+
+7. Acquire Core lock.
+8. Release Master 1 lock.
+9. Release Master 2 lock.
+10. Release Core lock.
--
1.7.9.5
On Fri, Oct 21, 2016 at 06:11:10PM +0530, Hardik Shah wrote:
> +static inline void get_t_size(size_t *t_val_size, size_t *t_size,
> + int *reg_addr,
> + int *offset,
> + size_t *val_size)
> +{
> +
> + *t_val_size += *t_size;
> + *offset += *t_size;
So, I started by taking a look at this patch to get a sense of what the
API would look like and I see that we're not following CodingStyle with
everything indented twice :(
> +static int regmap_sdw_gather_write(void *context,
> + const void *reg, size_t reg_size,
> + const void *val, size_t val_size)
> +{
> + if (!sdw)
> + return 0;
Silently ignoring errors :(
> + if (val_size > SDW_MAX_REG_ADDR)
> + t_size = SDW_MAX_REG_ADDR - reg_command;
> + else
> + t_size = val_size;
This needs at least some kind of comment?
On Fri, Oct 28, 2016 at 07:03:55PM +0100, Mark Brown wrote:
> On Fri, Oct 21, 2016 at 06:11:10PM +0530, Hardik Shah wrote:
>
> > +static inline void get_t_size(size_t *t_val_size, size_t *t_size,
> > + int *reg_addr,
> > + int *offset,
> > + size_t *val_size)
> > +{
> > +
> > + *t_val_size += *t_size;
> > + *offset += *t_size;
>
> So, I started by taking a look at this patch to get a sense of what the
> API would look like and I see that we're not following CodingStyle with
> everything indented twice :(
>
Sorry Mark for delayed response, there was festival holidays in India, so I
was not able to reply.
Regarding two tabs, it got missed for this function somehow, I will fix it
in next patchset.
> > +static int regmap_sdw_gather_write(void *context,
> > + const void *reg, size_t reg_size,
> > + const void *val, size_t val_size)
> > +{
>
> > + if (!sdw)
> > + return 0;
>
> Silently ignoring errors :(
>
This should never happen ideally. This was extra check to make sure there is
not kernel crash. I can remove it as well.
> > + if (val_size > SDW_MAX_REG_ADDR)
> > + t_size = SDW_MAX_REG_ADDR - reg_command;
> > + else
> > + t_size = val_size;
>
> This needs at least some kind of comment?
I will add few comments here.
--
On Fri, Oct 21, 2016 at 06:10:58PM +0530, Hardik Shah wrote:
> Following RFC series adds SoundWire bus driver interface based on the
> MIPI SoundWire specification 1.1
This is adding a new bus so you should get Greg to review. It also
seems implausible that SoundWire is going to be exclusively used by
audio devices so you should add the bus level code in drivers/ rather
than sound/, that will make life easier when other users appear.
On Fri, Oct 21, 2016 at 06:11:03PM +0530, Hardik Shah wrote:
> This patch adds the SoundWire bus driver interfaces for following.
>
> 1. APIs to register/unregister SoundWire Master device and driver.
> 2. APIs to register/unregister SoundWire Slave driver.
> 3. API to Read/Write Slave registers.
> 4. API for Master driver to update Slave status to bus driver.
> 5. APIs for Master driver to prepare and initiate clock stop.
> 6. APIs for SoundWire stream configuration.
> 7. APIs to prepare, enable, deprepare and disable SoundWire stream.
This is a very large patch (85K, 2000 lines) and the changelog is just a
long list of different things being added. This suggests that you could
easily split the patch up along the lines you've given above which would
make it a lot more digestable, as it is it's really hard to review. It
is also very hard to review because it's pure header, there's none of
the implementation in here, which makes it hard to see how things are
going to be used.
At a very high level this doesn't look or feel like normal Linux code,
there's some obvious coding style stuff, a bunch of things that look
like this was copied from legacy code and perhaps most worryingly all
the stuff about a "bus driver" which doesn't seem to fit with the normal
Linux models at all.
> +/**
> + * sdw_slave_addr: Structure representing the read-only unique device ID
> + * and the SoundWire logical device number (assigned by bus driver).
With the name of this structure I was expecting it to represent an
address in the same way that an in_addr is an IP address but really it's
a bundle of several different addresses and runtime state associated
with them. This is going to cause confusion I imagine, it seems better
to rename it or bundle it into the slave struct.
> +/**
> + * sdw_dpn_caps: Capabilities of the Data Port, other than Data Port 0 for
> + * SoundWire Master and Slave. This follows the definitions in the
Why not just _dp_caps?
> + * @max_bps: Maximum bits per sample supported by Port. This is same as word
> + * length in SoundWire Spec.
Might be better to rename all these _bps variables to _wl or something -
bps looks like bits per second which is going to confuse people.
> +/**
> + * sdw_prepare_ch: Prepare/De-prepare the Data Port channel. This is similar
> + * to the prepare API of Alsa, where most of the hardware interfaces
> + * are prepared for the playback/capture to start. All the parameters
> + * are known to the hardware at this point for starting
> + * playback/capture next.
This is information used for preparing or depreparing, the description
reads like this is a function.
> + * @num: Port number.
> + * @ch_mask: Channels to be prepared/deprepared specified by ch_mask.
> + * @prepare: Prepare/de-prepare channel, true (prepare) or false
> + * (de-prepare).
Do actual implementations look like something other than an if
statement with two unrelated halves?
> + * @bank: Register bank, which bank Slave/Master driver should program for
> + * implementation defined registers. This is the inverted value of the
> + * current bank.
If this is the inverted value of the current bank should we not just
look at the current bank?
> + * @r_w_flag: Read/Write operation. Read(0) or Write(1) based on above #def
Which above #define? (please write out #define fully too). I'm also
noticing lots of random capitalisation in the comments in this patch.
> +/**
> + * sdw_msg: Message to be sent on bus. This is similar to i2c_msg on I2C
> + * bus. Message is sent from the bus driver to the Slave device. Slave
> + * driver can also initiate transfer of the message to program
> + * implementation defined registers. Message is formatted and
> + * transmitted on to the bus by Master interface in hardware-specific
> + * way. Bus driver initiates the Master interface callback to transmit
> + * the message on bus.
> + *
> + * @addr: Address of the register to be read.
Only reads?
> + * @frame_rate: Audio frame rate of the stream (not the bus frame rate
> + * defining command bandwidth).
In units of...?
> +/**
> + * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is
> + * a unique identifier for each SoundWire stream across all SoundWire
> + * bus instances. Stream tag is a software concept defined by bus
> + * driver for stream management and not by MIPI SoundWire Spec. Each
> + * SoundWire Stream is individually configured and controlled using the
> + * stream tag. Multiple Master(s) and Slave(s) associated with the
> + * stream, uses stream tag as an identifier. All the operations on the
> + * stream e.g. stream configuration, port configuration, prepare and
> + * enable of the ports are done based on stream tag. This API shall be
> + * called once per SoundWire stream either by the Master or Slave
> + * associated with the stream.
> + *
> + * @stream_tag: Stream tag returned by bus driver.
> + */
> +int snd_sdw_alloc_stream_tag(unsigned int *stream_tag);
In Linux we put documentation for functions next to their
implementation not their prototype. This also doesn't look like good
kerneldoc - normally we'd have a separate paragraph for the summary.
This sort of stylistic stuff is important not just for itself but also
because it's a warning sign that the code hasn't been written by someone
who's really familiar with how things are expected to look and work in
terms of the kernel internal abstractions.
> + * @slv_list: List of SoundWire Slaves registered to the bus.
The driver model already maintains a list of devices on a bus, why are
we maintaining a separate one?
> + * @sdw_addr: Array containing Slave SoundWire bus Slave address
> + * information. Its a part of Slave list as well, but for easier access
> + * its part of array to find the Slave reference by directly indexing
> + * the Slave number into the array.
This seems like a worrying idea... I'd be entirely unsurprised if this
cache went wrong, are we sure we need the optimisation?
> + * @num_slv: Number of SoundWire Slaves assigned DeviceNumber after
> + * enumeration. The SoundWire specification does not place a
> + * restriction on how many Slaves are physically connected, as long as
> + * only 11 are active concurrently. This bus driver adds a pragmatic
> + * restriction to 11 Slaves, the current implementation assigns a
> + * DeviceNumber once and will never use the same DeviceNumber for
> + * different devices.
This is not a driver, it is a bus. Please do not refer to it as a
driver, that's at best confusing and at worst worrying. It's also
unclear why we're maintaining this count separately to the driver model.
> + char name[SOUNDWIRE_NAME_SIZE];
> + struct sdw_slave_addr sdw_addr[SDW_MAX_DEVICES];
Best to use SDW_ consistently.
> +/**
> + * sdw_master_driver: Manage SoundWire Master device driver.
> + *
> + * @driver_type: To distinguish between Master and Slave driver. This should
> + * be set by driver based on its handling Slave or Master SoundWire
> + * interface.
> + *
> + * @probe: Binds this driver to a SoundWire Master.
> + * @remove: Unbinds this driver from the SoundWire Master.
> + * @shutdown: Standard shutdown callback used during power down/halt.
> + * @suspend: Standard suspend callback used during system suspend
> + * @resume: Standard resume callback used during system resume
Modern buses have moved to using dev_pm_ops which enables them to use a
lot of generic code for power management implementation.
> + * @driver: SoundWire device drivers should initialize name and owner field
> + * of this structure.
Initializing .owner shouldn't be required with kernels from the past few
years.
> +/**
> + * snd_sdw_master_get: Return the Master handle from Master number.
> + * Increments the reference count of the module. Similar to
> + * i2c_get_adapter.
> + *
> + * @nr: Master number.
> + *
> + * Returns Master handle on success, else NULL
> + */
> +struct sdw_master *snd_sdw_master_get(int nr);
This is a legacy interface for I2C, why are we adding it for a new bus?
> + * @portn_mask: Implementation defined mask for Slave Ports other than port0.
> + * Mask bits are exactly same as defined in MIPI Spec 1.1. Array size
> + * shall be same as number of Ports in Slave. For bidirectional ports,
> + * masks can be different for Source and Sink ports.
Is this implementation defined or spec defined? It's really non-obvious
what these implementation defined things actually mean or do.
> +struct sdw_slave {
> + struct device dev;
> + struct sdw_master *mstr;
Why do we have a direct reference to the master here? Shouldn't this
just be the parent of the device?
On Fri, Oct 21, 2016 at 06:11:04PM +0530, Hardik Shah wrote:
> +static void sdw_mstr_release(struct device *dev)
> +{
> + struct sdw_master *mstr = to_sdw_master(dev);
> +
> + complete(&mstr->slv_released_complete);
> +}
Other buses don't do this... this is a big warning sign that you're
abusing the driver model.
> +/**
> + * sdw_slv_verify - return parameter as sdw_slave, or NULL
> + * @dev: device, probably from some driver model iterator
> + *
> + * When traversing the driver model tree, perhaps using driver model
> + * iterators like @device_for_each_child(), you can't assume very much
> + * about the nodes you find. Use this function to avoid oopses caused
> + * by wrongly treating some non-SDW device as an sdw_slave.
> + */
This is also *very* scary, especially given that there's no analysis
presented as to why there might be random other things on the bus. Why
does SoundWire need this when other buses don't?
> +static struct sdw_slave *sdw_slv_verify(struct device *dev)
> +{
> + return (dev->type == &sdw_slv_type)
> + ? to_sdw_slave(dev)
> + : NULL;
This is needlessly obfuscated, if you want to write an if statement
write an if statement.
> +static int sdw_slv_match(struct device *dev, struct device_driver *driver)
> +{
> + struct sdw_slave *sdw_slv;
> + struct sdw_driver *sdw_drv = to_sdw_driver(driver);
> + struct sdw_slave_driver *drv;
> + int ret = 0;
> +
> +
> + if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
> + return ret;
Why do we need a check like this?
> +static int sdw_mstr_probe(struct device *dev)
> +{
> + const struct sdw_master_driver *sdrv =
> + to_sdw_master_driver(dev->driver);
> + struct sdw_master *mstr = to_sdw_master(dev);
> + int ret;
> +
> + ret = dev_pm_domain_attach(dev, true);
> +
> + if (ret != -EPROBE_DEFER) {
> + ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
> + if (ret < 0)
> + dev_pm_domain_detach(dev, true);
> + }
This looks *very* broken. Surely if we fail to attach a pm_domain for
any reason other than one not being there to attach we shouldn't be
trying to probe the device?
> +EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
This is EXPORT_SYMBOL_GPL() but the bus itself is dual licensed GPL/BSD
- seems a bit inconsistent.
> +/**
> + * snd_sdw_master_add: Registers the SoundWire Master interface. This needs
> + * to be called for each Master interface supported by SoC. This
> + * represents One clock and data line (Optionally multiple data lanes)
> + * of Master interface.
> + *
> + * @master: the Master to be added.
> + */
> +int snd_sdw_master_add(struct sdw_master *master)
This lies at the heart of the issues that seem to exist with the misuse
of the driver model in this code. Normally what we see is that the
controller would instantiate as whatever bus type the controller is
attached by (typically a PCI or platform device) and then it wouild
register a bus with the bus subsystem which would then instantiate
slaves. Instead we have this system where the bus is registered by
something in the system and then the master is a driver on the bus
parallel to the slaves but with a separate driver type that causes
confusion. Without having seen a master driver it's not even clear how
this is going to work and allow the master to talk to its own hardware.
On Fri, Oct 21, 2016 at 06:10:59PM +0530, Hardik Shah wrote:
> This patch adds summary documentation of SoundWire bus driver support in
> Linux.
>
> Signed-off-by: Hardik Shah <[email protected]>
> Signed-off-by: Sanyog Kale <[email protected]>
> Reviewed-by: Pierre-Louis Bossart <[email protected]>
> ---
> Documentation/sound/alsa/sdw/summary.txt | 253 ++++++++++++++++++++++++++++++
> 1 file changed, 253 insertions(+)
> create mode 100644 Documentation/sound/alsa/sdw/summary.txt
>
> diff --git a/Documentation/sound/alsa/sdw/summary.txt b/Documentation/sound/alsa/sdw/summary.txt
> new file mode 100644
> index 0000000..dc62817
> --- /dev/null
> +++ b/Documentation/sound/alsa/sdw/summary.txt
> @@ -0,0 +1,253 @@
<snip>
> +Programming interfaces (SoundWire Master interface Driver)
> +==========================================================
> +
> +SoundWire bus driver supports programming interfaces for the SoundWire
> +Master and SoundWire Slave devices. All the code uses the "sdw" prefix
> +commonly used by SOC designers and 3rd party vendors.
> +
> +Each of the SoundWire Master interface needs to be registered to the Bus
> +driver. Master interface capabilities also needs to be registered to
> +bus driver since there is no discovery mechanism as a part of SoundWire
> +protocol.
> +
> +The Master interface along with the Master interface capabilities are
> +registered based on board file, DT or ACPI.
> +
> +Following is the API to register the SoundWire Master device.
> +
> +static int my_sdw_register_master()
> +{
> + struct sdw_master master;
> + struct sdw_master_capabilities *m_cap;
> +
> + m_cap = &master.mstr_capabilities;
> +
> + /*
> + * Fill the Master device capability, this is required
> + * by bus driver to handle bus configurations.
> + */
> + m_cap->highphy_capable = false;
> + m_cap->monitor_handover_supported = false;
> + m_cap->sdw_dp0_supported = 1;
> + m_cap->num_data_ports = INTEL_SDW_MAX_PORTS;
> +
> + return snd_sdw_master_add(&master);
> +}
> +
> +Master driver gets registered for controlling the Master device. It
> +provides the callback functions to the bus driver to control the bus in
> +device specific way. Device and Driver binds according to the standard
> +Linux device-driver bind model. Master driver is registered from the
> +driver init code. Below code shows the sample Master driver
> +registration.
> +
> +static struct sdw_master_driver intel_sdw_mstr_driver = {
> + .driver_type = SDW_DRIVER_TYPE_MASTER,
> + .driver = {
> + .name = "intel_sdw_mstr",
> + .pm = &intel_sdw_pm_ops,
> + },
> +
> + .probe = intel_sdw_probe,
> + .remove = intel_sdw_remove,
> + .mstr_ops = &intel_sdw_master_ops,
> + .mstr_port_ops = &intel_sdw_master_port_ops,
> +};
> +
> +static int __init intel_sdw_init(void) {
> + return snd_sdw_master_register_driver(&intel_sdw_mstr_driver);
> +}
Would be good to hear some detail the reasoning for the design
choices here? Normally (I2C/SPI) the master sits on whatever bus
the host uses to talk to the master so often this might be the
platform bus for memory mapped devices, it then creates a bus and
slaves register to that. This also has the nice property that its
easy to create devices that sit behind other buses, for example
here we might want a SoundWire master that sits behind a SPI bus.
But you seem to have gone in the other direction and have the
master sitting on the same bus as the slaves.
> +
> +As shown above Master driver registers itself with bus using
> +"sdw_mstr_driver_register" API, It registers using set of "mstr_ops" and
> +"mstr_port_ops" callback functions to the bus driver.
> +
> +"mstr_ops" is used by bus driver to control the bus in the hardware
> +specific way. It includes bus control functions such as sending the
> +SoundWire read/write messages on bus. The Bus driver also defines the
> +clock frequency and frameshape allocation needed by active stream and
> +configuration messages that need to be transmitted over the bus, to
> +maximize the bandwidth needed while minimizing the power. The "mstr_ops"
> +structure abstracts the hardware details of the Master from the bus
> +driver for setting up of the clock frequency and frameshape.
> +
> +"mstr_port_ops" is used by bus driver to setup the Port parameters of
> +the Master interface Port. Master interface Port register map is not
> +defined by MIPI specification, so bus driver calls the "mstr_port_ops"
> +call back function to do Port operations like "Port Prepare", "Port
> +Transport params set", "Port enable and disable". The implementation of
> +the Master driver can then perform hardware-specific configurations.
Thanks,
Charles
On Fri, Oct 21, 2016 at 06:11:00PM +0530, Hardik Shah wrote:
> This patch adds stream documentation describing SoundWire stream and
> stream states.
>
> Signed-off-by: Hardik Shah <[email protected]>
> Signed-off-by: Sanyog Kale <[email protected]>
> Reviewed-by: Pierre-Louis Bossart <[email protected]>
> ---
> Documentation/sound/alsa/sdw/stream.txt | 346 +++++++++++++++++++++++++++++++
> 1 file changed, 346 insertions(+)
> create mode 100644 Documentation/sound/alsa/sdw/stream.txt
>
> diff --git a/Documentation/sound/alsa/sdw/stream.txt b/Documentation/sound/alsa/sdw/stream.txt
> new file mode 100644
> index 0000000..a1a2ed0
> --- /dev/null
> +++ b/Documentation/sound/alsa/sdw/stream.txt
> @@ -0,0 +1,346 @@
<snip>
> +
> +SoundWire stream states
> +=======================
> +Below figure shows the SoundWire stream states and possible state
> +transition diagram.
> +
> +|--------------| |-------------| |--------------| |--------------|
> +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE |
> +| STATE | | STATE | | STATE | | STATE |
> +|--------------| |-------------| |--------------| |--------------|
> + ^ |
> + | |
> + | |
> + | |
> + | \/
> + |--------------| |--------------| |--------------|
> + | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
> + | STATE | | STATE | | STATE |
> + |--------------| |--------------| |--------------|
> +
One minor comment, this looks very similar to the clock
frameworks state model, but the clock framework calls it
unprepare would there be some milage in aligning to?
> +
> +SoundWire Stream State Operations
> +==================================
> +Below section explains the operations done by the bus driver on
> +Master(s) and Slave(s) as part of stream state transitions.
> +
> +SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry
> +state of the stream. Operations performed before entering in this
> +state:
> +1. An unique stream tag is assigned to stream. This stream tag is used
A unique
> +as a reference for all the operations performed on stream.
> +
> +2. The resources required for holding stream runtime information are
> +allocated and initialized. This holds all stream related information
> +such as stream type (PCM/PDM) and parameters, Master and Slave interface
> +associated with the stream, reference counting, stream state etc.
> +
> +After all above operations are successful, stream state is set to
> +SDW_STATE_STRM_ALLOC.
> +
> +
> +SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations
> +performed before entering in this state:
> +1. The resources allocated for stream information in
> +SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters,
> +Masters and Slaves runtime information associated with the stream.
> +
> +2. All the Masters and Slaves associated with the stream updates the
> +port configuration to bus driver. This includes port numbers allocated
> +by Master(s) and Slave(s) for this stream.
> +
> +After all above operations are successful, stream state is set to
> +SDW_STATE_STRM_CONFIG.
> +
> +
> +SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed
> +before entering in this state:
> +1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP
> +interval are computed based on current stream as well as already active
> +streams on bus. Re-computation is required to accommodate current stream
> +on the bus.
> +
> +2. Transport parameters of all Master and Slave ports are computed for
> +the current as well as already active stream based on above calculated
> +frame shape and clock frequency.
> +
> +3. Computed bus and transport parameters are programmed in Master and
> +Slave registers. The banked registers programming is done on the
> +alternate bank (bank currently unused). Port channels are enabled for
> +the already active streams on the alternate bank (bank currently
> +unused). This is done in order to not to disrupt already active
> +stream(s).
> +
> +4. Once all the new values are programmed, bus initiates switch to
> +alternate bank. Once switch is successful, the port channels enabled on
> +previous bank for already active streams are disabled.
> +
> +5. Ports of Master and Slave for current stream are prepared.
> +
> +After all above operations are successful, stream state is set to
> +SDW_STATE_STRM_PREPARE.
> +
> +
> +SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed
> +before entering in this state:
> +1. All the values computed in SDW_STATE_STRM_PREPARE state are
> +programmed in alternate bank (bank currently unused). It includes
> +programming of already active streams as well.
> +
> +2. All the Master and Slave port channels for the current stream are
> +enabled on alternate bank (bank currently unused).
> +
This could probably use a little more explaination to show how it
differs from step 3/4 in PREPARE, as it looks like all the
computed values where applied there. I imagine this is just my lack
of understanding rather than an actual issue but even looking at
the code I am having a little difficulty tying up these two.
sdw_prepare_op
- sdw_compute_params (prepare step 1/2)
- sdw_program_params (prepare step 3)
- sdw_update_bus_params (prepare step 4)
sdw_enable_op
- sdw_program_params (enable step 1)
- sdw_update_bus_params (enable step 2)
It looks like the params are still basically the same as they
were when we called sdw_program_params in prepare.
> +3. Once all the new values are programmed, bus initiates switch to
> +alternate bank. Once the switch is successful, the port channels enabled
> +on previous bank for already active streams are disabled.
> +
> +After all above operations are successful, stream state is set to
> +SDW_STATE_STRM_ENABLE.
> +
> +
Thanks,
Charles
On Fri, Oct 21, 2016 at 06:11:01PM +0530, Hardik Shah wrote:
> This patch adds following documentation:
> 1. Bus driver locking mechanism.
> 2. Bus driver error handling.
>
> Signed-off-by: Hardik Shah <[email protected]>
> Signed-off-by: Sanyog Kale <[email protected]>
> Reviewed-by: Pierre-Louis Bossart <[email protected]>
> ---
> Documentation/sound/alsa/sdw/error_handling.txt | 71 +++++++++++++++++++++++
> Documentation/sound/alsa/sdw/locking.txt | 64 ++++++++++++++++++++
> 2 files changed, 135 insertions(+)
> create mode 100644 Documentation/sound/alsa/sdw/error_handling.txt
> create mode 100644 Documentation/sound/alsa/sdw/locking.txt
>
> diff --git a/Documentation/sound/alsa/sdw/error_handling.txt b/Documentation/sound/alsa/sdw/error_handling.txt
> new file mode 100644
> index 0000000..9441cfa
> --- /dev/null
> +++ b/Documentation/sound/alsa/sdw/error_handling.txt
> @@ -0,0 +1,71 @@
<snip>
> diff --git a/Documentation/sound/alsa/sdw/locking.txt b/Documentation/sound/alsa/sdw/locking.txt
> new file mode 100644
> index 0000000..650162f
> --- /dev/null
> +++ b/Documentation/sound/alsa/sdw/locking.txt
> @@ -0,0 +1,64 @@
> +This document explains locking mechanism of the SoundWire bus driver.
> +Following types of lock are used in SoundWire bus driver.
> +
> +1. Core lock
> +2. Master lock
> +3. Stream lock
> +4. Message lock
> +
> +1. Core lock: Global SoundWire bus driver lock. Core lock is used to
> +serialize each of the following operation(s) within SoundWire bus
> +driver.
> + - Addition and removal of Master.
> + - Acquire "Master lock" of each Master associated with the
> + aggregated stream.
> +
> +
> +2. Master lock: SoundWire bus instance lock. Master lock is used to
> +serialize each of the following operation(s) within SoundWire bus
> +instance.
> + - Addition and removal of Slave(s).
> + - Prepare and enable, disable and de-prepare.
> +
> +
> +3. Stream lock: SoundWire stream lock. Stream lock is used to serialize
> +access of stream data structure for a SoundWire stream.
> +
> +
> +4. Message lock: SoundWire message transfer lock. This lock is used to
> +serialize the message transfers(read/write) within the SoundWire bus
> +instance.
> +
> +
> +Lock Hierarchy
> +==============
> +
> +- Core lock is the parent of Master and Stream lock.
> +- Master lock is parent of Message lock.
> +- Master and Stream locks are independent of each other.
> +
A small diagram might be nice here, just would make it easier to
see the hierarchy at a glance.
Thanks,
Charles
On Fri, Oct 21, 2016 at 06:11:07PM +0530, Hardik Shah wrote:
> This patch adds the support for updating the Slave status to bus driver.
> Master driver updates Slave status change to the bus driver. Bus driver
> takes appropriate action on Slave status change like.
>
> 1. Registering new device if new Slave got enumerated on bus.
> 2. Assigning the device number to the Slave device
> 3. Marking Slave as un-attached if Slave got detached from bus.
> 4. Handling Slave alerts.
>
> Signed-off-by: Hardik Shah <[email protected]>
> Signed-off-by: Sanyog Kale <[email protected]>
> Reviewed-by: Pierre-Louis Bossart <[email protected]>
> ---
> sound/sdw/sdw.c | 1074 ++++++++++++++++++++++++++++++++++++++++++++++++++
> sound/sdw/sdw_priv.h | 66 ++++
> 2 files changed, 1140 insertions(+)
>
<snip>
> +static int sdw_slv_register(struct sdw_master *mstr)
> +{
> + int ret, i;
> + struct sdw_msg msg;
> + u8 buf[SDW_NUM_DEV_ID_REGISTERS];
> + struct sdw_slave *sdw_slave;
> + int dev_num = -1;
> + bool found = false;
> +
> + /* Create message to read the 6 dev_id registers */
> + sdw_create_rd_msg(&msg, 0, SDW_SCP_DEVID_0, SDW_NUM_DEV_ID_REGISTERS,
> + buf, 0x0);
> +
> + /*
> + * Multiple Slaves may report an Attached_OK status as Device0.
> + * Since the enumeration relies on a hardware arbitration and is
> + * done one Slave at a time, a loop needs to run until all Slaves
> + * have been assigned a non-zero DeviceNumber. The loop exits when
> + * the reads from Device0 devID registers are no longer successful,
> + * i.e. there is no Slave left to enumerate
> + */
> + while ((ret = (snd_sdw_slave_transfer(mstr, &msg, SDW_NUM_OF_MSG1_XFRD))
> + == SDW_NUM_OF_MSG1_XFRD)) {
> +
> + /*
> + * Find is Slave is re-enumerating, and was already
> + * registered earlier.
> + */
> + found = sdw_find_slv(mstr, &msg, &dev_num);
> +
> + /*
> + * Reprogram the Slave device number if its getting
> + * re-enumerated. If that fails we continue finding new
> + * slaves, we flag error but don't stop since there may be
> + * new Slaves trying to get enumerated.
> + */
> + if (found) {
> + ret = sdw_program_dev_num(mstr, dev_num);
> + if (ret < 0)
> + dev_err(&mstr->dev, "Re-registering slave failed ret = %d", ret);
> +
> + continue;
> +
> + }
> +
> + /*
> + * Find the free device_number for the new Slave getting
> + * enumerated 1st time.
> + */
> + dev_num = sdw_find_free_dev_num(mstr, &msg);
> + if (dev_num < 0) {
> + dev_err(&mstr->dev, "Failed to find free dev_num ret = %d\n", ret);
> + goto dev_num_assign_fail;
> + }
> +
> + /*
> + * Allocate and initialize the Slave device on first
> + * enumeration
> + */
> + sdw_slave = kzalloc(sizeof(*sdw_slave), GFP_KERNEL);
> + if (!sdw_slave) {
> + ret = -ENOMEM;
> + goto mem_alloc_failed;
> + }
> +
> + /*
> + * Initialize the allocated Slave device, set bus type and
> + * device type to SoundWire.
> + */
> + sdw_slave->mstr = mstr;
> + sdw_slave->dev.parent = &sdw_slave->mstr->dev;
> + sdw_slave->dev.bus = &sdw_bus_type;
> + sdw_slave->dev.type = &sdw_slv_type;
> + sdw_slave->priv.addr = &mstr->sdw_addr[dev_num];
> + sdw_slave->priv.addr->slave = sdw_slave;
> +
> + for (i = 0; i < SDW_NUM_DEV_ID_REGISTERS; i++)
> + sdw_slave->priv.dev_id[i] = msg.buf[i];
> +
> + dev_dbg(&mstr->dev, "SDW slave slave id found with values\n");
> + dev_dbg(&mstr->dev, "dev_id0 to dev_id5: %x:%x:%x:%x:%x:%x\n",
> + msg.buf[0], msg.buf[1], msg.buf[2],
> + msg.buf[3], msg.buf[4], msg.buf[5]);
> + dev_dbg(&mstr->dev, "Dev number assigned is %x\n", dev_num);
> +
> + /*
> + * Set the Slave device name, its based on the dev_id and
> + * to bus which it is attached.
> + */
> + dev_set_name(&sdw_slave->dev, "sdw-slave%d-%02x:%02x:%02x:%02x:%02x:%02x",
> + sdw_master_get_id(mstr),
> + sdw_slave->priv.dev_id[0],
> + sdw_slave->priv.dev_id[1],
> + sdw_slave->priv.dev_id[2],
> + sdw_slave->priv.dev_id[3],
> + sdw_slave->priv.dev_id[4],
> + sdw_slave->priv.dev_id[5]);
> +
> + /*
> + * Set name based on dev_id. This will be used in match
> + * function to bind the device and driver.
> + */
> + sprintf(sdw_slave->priv.name, "%02x:%02x:%02x:%02x:%02x:%02x",
> + sdw_slave->priv.dev_id[0],
> + sdw_slave->priv.dev_id[1],
> + sdw_slave->priv.dev_id[2],
> + sdw_slave->priv.dev_id[3],
> + sdw_slave->priv.dev_id[4],
> + sdw_slave->priv.dev_id[5]);
> + ret = device_register(&sdw_slave->dev);
> + if (ret) {
> + dev_err(&mstr->dev, "Register slave failed ret = %d\n", ret);
> + goto reg_slv_failed;
> + }
There are some issues with this, as the slave driver only probes
when the device actually shows up on the bus. However often
(especially in embedded contexts) some things may need to be
done to enable the slave. For example it may be held in reset or
its power supplies switched off until they are need. As such it
generally helps if the device probe can be called before it shows
up on the bus, the device probe can then do the necessary actions
to enable the device at which point it will show up on the bus.
Thanks,
Charles
>> +SoundWire stream states
>> +=======================
>> +Below figure shows the SoundWire stream states and possible state
>> +transition diagram.
>> +
>> +|--------------| |-------------| |--------------| |--------------|
>> +| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE |
>> +| STATE | | STATE | | STATE | | STATE |
>> +|--------------| |-------------| |--------------| |--------------|
>> + ^ |
>> + | |
>> + | |
>> + | |
>> + | \/
>> + |--------------| |--------------| |--------------|
>> + | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
>> + | STATE | | STATE | | STATE |
>> + |--------------| |--------------| |--------------|
>> +
>
> One minor comment, this looks very similar to the clock
> frameworks state model, but the clock framework calls it
> unprepare would there be some milage in aligning to?
The SoundWire spec uses de-prepare, e.g. "De-prepare_Finished"
I'd rather stick to the wording between a spec and the implementation of
said spec, rather than introduce a term/concept from an unrelated framework.
>
>> +
>> +SoundWire Stream State Operations
>> +==================================
>> +Below section explains the operations done by the bus driver on
>> +Master(s) and Slave(s) as part of stream state transitions.
>> +
>> +SDW_STATE_STRM_ALLOC: Allocation state for stream. This is the entry
>> +state of the stream. Operations performed before entering in this
>> +state:
>> +1. An unique stream tag is assigned to stream. This stream tag is used
>
> A unique
ok
>
>> +as a reference for all the operations performed on stream.
>> +
>> +2. The resources required for holding stream runtime information are
>> +allocated and initialized. This holds all stream related information
>> +such as stream type (PCM/PDM) and parameters, Master and Slave interface
>> +associated with the stream, reference counting, stream state etc.
>> +
>> +After all above operations are successful, stream state is set to
>> +SDW_STATE_STRM_ALLOC.
>> +
>> +
>> +SDW_STATE_STRM_CONFIG: Configuration state of stream. Operations
>> +performed before entering in this state:
>> +1. The resources allocated for stream information in
>> +SDW_STATE_STRM_ALLOC state are updated. This includes stream parameters,
>> +Masters and Slaves runtime information associated with the stream.
>> +
>> +2. All the Masters and Slaves associated with the stream updates the
>> +port configuration to bus driver. This includes port numbers allocated
>> +by Master(s) and Slave(s) for this stream.
>> +
>> +After all above operations are successful, stream state is set to
>> +SDW_STATE_STRM_CONFIG.
>> +
>> +
>> +SDW_STATE_STRM_PREPARE: Prepare state of stream. Operations performed
>> +before entering in this state:
>> +1. Bus parameters such as bandwidth, frame shape, clock frequency, SSP
>> +interval are computed based on current stream as well as already active
>> +streams on bus. Re-computation is required to accommodate current stream
>> +on the bus.
>> +
>> +2. Transport parameters of all Master and Slave ports are computed for
>> +the current as well as already active stream based on above calculated
>> +frame shape and clock frequency.
>> +
>> +3. Computed bus and transport parameters are programmed in Master and
>> +Slave registers. The banked registers programming is done on the
>> +alternate bank (bank currently unused). Port channels are enabled for
>> +the already active streams on the alternate bank (bank currently
>> +unused). This is done in order to not to disrupt already active
>> +stream(s).
>> +
>> +4. Once all the new values are programmed, bus initiates switch to
>> +alternate bank. Once switch is successful, the port channels enabled on
>> +previous bank for already active streams are disabled.
This last sentence makes no sense in this context, probably a copy/paste
that shouldn't be there. The previously active streams remain active in
this prepare step.
>> +
>> +5. Ports of Master and Slave for current stream are prepared.
>> +
>> +After all above operations are successful, stream state is set to
>> +SDW_STATE_STRM_PREPARE.
>> +
>> +
>> +SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed
>> +before entering in this state:
>> +1. All the values computed in SDW_STATE_STRM_PREPARE state are
>> +programmed in alternate bank (bank currently unused). It includes
>> +programming of already active streams as well.
>> +
>> +2. All the Master and Slave port channels for the current stream are
>> +enabled on alternate bank (bank currently unused).
>> +
>
> This could probably use a little more explaination to show how it
> differs from step 3/4 in PREPARE, as it looks like all the
> computed values where applied there. I imagine this is just my lack
> of understanding rather than an actual issue but even looking at
> the code I am having a little difficulty tying up these two.
Yes, see above there was an extra sentence that isn't right.
>
> sdw_prepare_op
> - sdw_compute_params (prepare step 1/2)
> - sdw_program_params (prepare step 3)
> - sdw_update_bus_params (prepare step 4)
>
> sdw_enable_op
> - sdw_program_params (enable step 1)
> - sdw_update_bus_params (enable step 2)
>
> It looks like the params are still basically the same as they
> were when we called sdw_program_params in prepare.
The parameters are the same except for the channel-enable flags which
are only programmed and activated via a bank switch in the enable step.
On Mon, Nov 14, 2016 at 10:50:10AM -0600, Pierre-Louis Bossart wrote:
>
> >>+SoundWire stream states
> >>+=======================
> >>+Below figure shows the SoundWire stream states and possible state
> >>+transition diagram.
> >>+
> >>+|--------------| |-------------| |--------------| |--------------|
> >>+| ALLOC |---->| CONFIG |---->| PREPARE |---->| ENABLE |
> >>+| STATE | | STATE | | STATE | | STATE |
> >>+|--------------| |-------------| |--------------| |--------------|
> >>+ ^ |
> >>+ | |
> >>+ | |
> >>+ | |
> >>+ | \/
> >>+ |--------------| |--------------| |--------------|
> >>+ | RELEASE |<--------------------| DEPREPARE |<----| DISABLE |
> >>+ | STATE | | STATE | | STATE |
> >>+ |--------------| |--------------| |--------------|
> >>+
> >
> >One minor comment, this looks very similar to the clock
> >frameworks state model, but the clock framework calls it
> >unprepare would there be some milage in aligning to?
>
> The SoundWire spec uses de-prepare, e.g. "De-prepare_Finished"
> I'd rather stick to the wording between a spec and the implementation of
> said spec, rather than introduce a term/concept from an unrelated framework.
> >
Cool we should leave that as is then :-)
> >>+4. Once all the new values are programmed, bus initiates switch to
> >>+alternate bank. Once switch is successful, the port channels enabled on
> >>+previous bank for already active streams are disabled.
>
> This last sentence makes no sense in this context, probably a copy/paste
> that shouldn't be there. The previously active streams remain active in this
> prepare step.
>
> >>+
> >>+5. Ports of Master and Slave for current stream are prepared.
> >>+
> >>+After all above operations are successful, stream state is set to
> >>+SDW_STATE_STRM_PREPARE.
> >>+
> >>+
> >>+SDW_STATE_STRM_ENABLE: Enable state of stream. Operations performed
> >>+before entering in this state:
> >>+1. All the values computed in SDW_STATE_STRM_PREPARE state are
> >>+programmed in alternate bank (bank currently unused). It includes
> >>+programming of already active streams as well.
> >>+
> >>+2. All the Master and Slave port channels for the current stream are
> >>+enabled on alternate bank (bank currently unused).
> >>+
> >
> >This could probably use a little more explaination to show how it
> >differs from step 3/4 in PREPARE, as it looks like all the
> >computed values where applied there. I imagine this is just my lack
> >of understanding rather than an actual issue but even looking at
> >the code I am having a little difficulty tying up these two.
>
> Yes, see above there was an extra sentence that isn't right.
>
> >
> >sdw_prepare_op
> >- sdw_compute_params (prepare step 1/2)
> >- sdw_program_params (prepare step 3)
> >- sdw_update_bus_params (prepare step 4)
> >
> >sdw_enable_op
> >- sdw_program_params (enable step 1)
> >- sdw_update_bus_params (enable step 2)
> >
> >It looks like the params are still basically the same as they
> >were when we called sdw_program_params in prepare.
>
> The parameters are the same except for the channel-enable flags which are
> only programmed and activated via a bank switch in the enable step.
Ah ok that is what is getting pushed out there, thanks for
explaining.
Thanks,
Charles
Thanks for the reviews Mark, comment below:
On 11/14/16 7:17 AM, Mark Brown wrote:
> On Fri, Oct 21, 2016 at 06:11:03PM +0530, Hardik Shah wrote:
>> This patch adds the SoundWire bus driver interfaces for following.
>>
>> 1. APIs to register/unregister SoundWire Master device and driver.
>> 2. APIs to register/unregister SoundWire Slave driver.
>> 3. API to Read/Write Slave registers.
>> 4. API for Master driver to update Slave status to bus driver.
>> 5. APIs for Master driver to prepare and initiate clock stop.
>> 6. APIs for SoundWire stream configuration.
>> 7. APIs to prepare, enable, deprepare and disable SoundWire stream.
>
> This is a very large patch (85K, 2000 lines) and the changelog is just a
> long list of different things being added. This suggests that you could
> easily split the patch up along the lines you've given above which would
> make it a lot more digestable, as it is it's really hard to review. It
> is also very hard to review because it's pure header, there's none of
> the implementation in here, which makes it hard to see how things are
> going to be used.
I am probably to blame for this since I wanted to a have all prototypes
in a single file to make my review easier and check that the exposed
functionality matches the spec requirements. If the spec is implemented
in various increments it makes it more difficult to check for
consistency, but you are correct that for someone looking at code for
the first time without prior experience with SoundWire it may be too
complicated.
>
> At a very high level this doesn't look or feel like normal Linux code,
> there's some obvious coding style stuff, a bunch of things that look
> like this was copied from legacy code and perhaps most worryingly all
> the stuff about a "bus driver" which doesn't seem to fit with the normal
> Linux models at all.
Yes agree. The patches were released ahead of the Audio Miniconference
and there are known issues with the coding style that will be fixed. And
the 'bus driver' part will be fixed as well, i don't view it as a
fundamental design issue but more awkward wording.
>
>> +/**
>> + * sdw_slave_addr: Structure representing the read-only unique device ID
>> + * and the SoundWire logical device number (assigned by bus driver).
>
> With the name of this structure I was expecting it to represent an
> address in the same way that an in_addr is an IP address but really it's
> a bundle of several different addresses and runtime state associated
> with them. This is going to cause confusion I imagine, it seems better
> to rename it or bundle it into the slave struct.
It really is the mapping between the read-only information exposed by
the Slave and the logical address configured by the bus. Maybe we can
rename it but that mapping is required.
>
>> +/**
>> + * sdw_dpn_caps: Capabilities of the Data Port, other than Data Port 0 for
>> + * SoundWire Master and Slave. This follows the definitions in the
>
> Why not just _dp_caps?
SoundWire makes a distinction between Port0 and PortN (1..15). the
former is to reclaim parts of the audio transport bandwidth for fast
control/configurations, the latter are for true audio transports. there
are different configurations/capabilities for DP0 and DPN.
>
>> + * @max_bps: Maximum bits per sample supported by Port. This is same as word
>> + * length in SoundWire Spec.
>
> Might be better to rename all these _bps variables to _wl or something -
> bps looks like bits per second which is going to confuse people.
That's what we used in the SoundWire DisCo document (in press). Word
Length is equally confusing for folks who believe a word is 16 bits.
>
>> +/**
>> + * sdw_prepare_ch: Prepare/De-prepare the Data Port channel. This is similar
>> + * to the prepare API of Alsa, where most of the hardware interfaces
>> + * are prepared for the playback/capture to start. All the parameters
>> + * are known to the hardware at this point for starting
>> + * playback/capture next.
>
> This is information used for preparing or depreparing, the description
> reads like this is a function.
Yes, will be fixed.
>
>> + * @num: Port number.
>> + * @ch_mask: Channels to be prepared/deprepared specified by ch_mask.
>> + * @prepare: Prepare/de-prepare channel, true (prepare) or false
>> + * (de-prepare).
>
> Do actual implementations look like something other than an if
> statement with two unrelated halves?
There is a prepare register field which is either set or cleared. there
aren't two parts in the function. We should clarify this.
>
>> + * @bank: Register bank, which bank Slave/Master driver should program for
>> + * implementation defined registers. This is the inverted value of the
>> + * current bank.
>
> If this is the inverted value of the current bank should we not just
> look at the current bank?
We should update the wording, the intent is that you program the new
setup in the alternate bank then switch banks.
>
>> + * @r_w_flag: Read/Write operation. Read(0) or Write(1) based on above #def
>
> Which above #define? (please write out #define fully too). I'm also
> noticing lots of random capitalisation in the comments in this patch.
Most of the capitalisation isn't random but follows the SoundWire spec
definitions. I think this was mentioned upfront in the header, if this
was missed then we need to clarify.
>
>> +/**
>> + * sdw_msg: Message to be sent on bus. This is similar to i2c_msg on I2C
>> + * bus. Message is sent from the bus driver to the Slave device. Slave
>> + * driver can also initiate transfer of the message to program
>> + * implementation defined registers. Message is formatted and
>> + * transmitted on to the bus by Master interface in hardware-specific
>> + * way. Bus driver initiates the Master interface callback to transmit
>> + * the message on bus.
>> + *
>> + * @addr: Address of the register to be read.
>
> Only reads?
no, this should be accessed for read or write.
>
>> + * @frame_rate: Audio frame rate of the stream (not the bus frame rate
>> + * defining command bandwidth).
>
> In units of...?
Hz. will be corrected.
>
>> +/**
>> + * snd_sdw_alloc_stream_tag: Allocates unique stream_tag. Stream tag is
>> + * a unique identifier for each SoundWire stream across all SoundWire
>> + * bus instances. Stream tag is a software concept defined by bus
>> + * driver for stream management and not by MIPI SoundWire Spec. Each
>> + * SoundWire Stream is individually configured and controlled using the
>> + * stream tag. Multiple Master(s) and Slave(s) associated with the
>> + * stream, uses stream tag as an identifier. All the operations on the
>> + * stream e.g. stream configuration, port configuration, prepare and
>> + * enable of the ports are done based on stream tag. This API shall be
>> + * called once per SoundWire stream either by the Master or Slave
>> + * associated with the stream.
>> + *
>> + * @stream_tag: Stream tag returned by bus driver.
>> + */
>> +int snd_sdw_alloc_stream_tag(unsigned int *stream_tag);
>
> In Linux we put documentation for functions next to their
> implementation not their prototype. This also doesn't look like good
> kerneldoc - normally we'd have a separate paragraph for the summary.
>
> This sort of stylistic stuff is important not just for itself but also
> because it's a warning sign that the code hasn't been written by someone
> who's really familiar with how things are expected to look and work in
> terms of the kernel internal abstractions.
yes Vinod made that comment as well but I asked that the patches be
shared without additional delays. This will be fixed.
>
>> + * @slv_list: List of SoundWire Slaves registered to the bus.
>
> The driver model already maintains a list of devices on a bus, why are
> we maintaining a separate one?
>
>> + * @sdw_addr: Array containing Slave SoundWire bus Slave address
>> + * information. Its a part of Slave list as well, but for easier access
>> + * its part of array to find the Slave reference by directly indexing
>> + * the Slave number into the array.
>
> This seems like a worrying idea... I'd be entirely unsurprised if this
> cache went wrong, are we sure we need the optimisation?
There will be additional changes to simplify the master/slave
definitions. It's not clear to me if all these optimizations are needed
either, if they create confusion or concern then we should really fix this.
>
>> + * @num_slv: Number of SoundWire Slaves assigned DeviceNumber after
>> + * enumeration. The SoundWire specification does not place a
>> + * restriction on how many Slaves are physically connected, as long as
>> + * only 11 are active concurrently. This bus driver adds a pragmatic
>> + * restriction to 11 Slaves, the current implementation assigns a
>> + * DeviceNumber once and will never use the same DeviceNumber for
>> + * different devices.
>
> This is not a driver, it is a bus. Please do not refer to it as a
> driver, that's at best confusing and at worst worrying. It's also
> unclear why we're maintaining this count separately to the driver model.
Point taken.
>
>> + char name[SOUNDWIRE_NAME_SIZE];
>> + struct sdw_slave_addr sdw_addr[SDW_MAX_DEVICES];
>
> Best to use SDW_ consistently.
Yes.
>
>> +/**
>> + * sdw_master_driver: Manage SoundWire Master device driver.
>> + *
>> + * @driver_type: To distinguish between Master and Slave driver. This should
>> + * be set by driver based on its handling Slave or Master SoundWire
>> + * interface.
>> + *
>> + * @probe: Binds this driver to a SoundWire Master.
>> + * @remove: Unbinds this driver from the SoundWire Master.
>> + * @shutdown: Standard shutdown callback used during power down/halt.
>> + * @suspend: Standard suspend callback used during system suspend
>> + * @resume: Standard resume callback used during system resume
>
> Modern buses have moved to using dev_pm_ops which enables them to use a
> lot of generic code for power management implementation.
Point taken.
>
>> + * @driver: SoundWire device drivers should initialize name and owner field
>> + * of this structure.
>
> Initializing .owner shouldn't be required with kernels from the past few
> years.
yes the comment is bad and 0-day flags this.
>
>> +/**
>> + * snd_sdw_master_get: Return the Master handle from Master number.
>> + * Increments the reference count of the module. Similar to
>> + * i2c_get_adapter.
>> + *
>> + * @nr: Master number.
>> + *
>> + * Returns Master handle on success, else NULL
>> + */
>> +struct sdw_master *snd_sdw_master_get(int nr);
>
> This is a legacy interface for I2C, why are we adding it for a new bus?
Good point, this was missed.
>
>> + * @portn_mask: Implementation defined mask for Slave Ports other than port0.
>> + * Mask bits are exactly same as defined in MIPI Spec 1.1. Array size
>> + * shall be same as number of Ports in Slave. For bidirectional ports,
>> + * masks can be different for Source and Sink ports.
>
> Is this implementation defined or spec defined? It's really non-obvious
> what these implementation defined things actually mean or do.
Yes the wording was bad, it's not implementation defined. It's a
standard feature but implementations can select a single-direction port
or dual-direction port (one direction at a time). In the latter case the
direction is set prior to enabling transport. This functionality was
added to help designers save gates.
>
>> +struct sdw_slave {
>> + struct device dev;
>> + struct sdw_master *mstr;
>
> Why do we have a direct reference to the master here? Shouldn't this
> just be the parent of the device?
yes this will be simplified/corrected.
On 11/14/16 10:08 AM, Charles Keepax wrote:
> There are some issues with this, as the slave driver only probes
> when the device actually shows up on the bus. However often
> (especially in embedded contexts) some things may need to be
> done to enable the slave. For example it may be held in reset or
> its power supplies switched off until they are need. As such it
> generally helps if the device probe can be called before it shows
> up on the bus, the device probe can then do the necessary actions
> to enable the device at which point it will show up on the bus.
Yes, this point was made at the LPC miniconference. What's not clear to
me is if you would want the codec driver to be notified that the bus is
operational and let it handle things like sideband power management for
that device, or is someone else needs to know.
On Mon, Nov 14, 2016 at 11:38:08AM -0600, Pierre-Louis Bossart wrote:
> On 11/14/16 10:08 AM, Charles Keepax wrote:
> >There are some issues with this, as the slave driver only probes
> >when the device actually shows up on the bus. However often
> >(especially in embedded contexts) some things may need to be
> >done to enable the slave. For example it may be held in reset or
> >its power supplies switched off until they are need. As such it
> >generally helps if the device probe can be called before it shows
> >up on the bus, the device probe can then do the necessary actions
> >to enable the device at which point it will show up on the bus.
>
> Yes, this point was made at the LPC miniconference. What's not clear to me
> is if you would want the codec driver to be notified that the bus is
> operational and let it handle things like sideband power management for that
> device, or is someone else needs to know.
>
I would think it would make sense to provide callbacks that
notify the end driver that the device has appeared on/disappears
from the bus. Similar to the Qualcomm SLIMBus stuff with the
device_up and device_down callbacks. Not all devices will need to
use them but they are probably handy to have.
As for actually notifying the end driver that bus itself is
operational is the PM runtime stuff going to be sufficient there?
As the slaves will be children of the master can't we use that to
ensure the bus is always powered when something is in use, and
the children themselves can then decide how much power management
to apply when they are not in use.
Thanks,
Charles
On Mon, Nov 14, 2016 at 12:11:23PM +0000, Mark Brown wrote:
> On Fri, Oct 21, 2016 at 06:10:58PM +0530, Hardik Shah wrote:
> > Following RFC series adds SoundWire bus driver interface based on the
> > MIPI SoundWire specification 1.1
>
> This is adding a new bus so you should get Greg to review. It also
> seems implausible that SoundWire is going to be exclusively used by
> audio devices so you should add the bus level code in drivers/ rather
> than sound/, that will make life easier when other users appear.
Yeah he was supposed to be on the CC list, but somehow got missed in the
end. I did ping him though and he wants to review only when it is ready to
merge and not yet..
--
~Vinod
On Mon, Nov 14, 2016 at 01:37:51PM +0000, Mark Brown wrote:
> On Fri, Oct 21, 2016 at 06:11:04PM +0530, Hardik Shah wrote:
>
> > +static void sdw_mstr_release(struct device *dev)
> > +{
> > + struct sdw_master *mstr = to_sdw_master(dev);
> > +
> > + complete(&mstr->slv_released_complete);
> > +}
>
> Other buses don't do this... this is a big warning sign that you're
> abusing the driver model.
The whole master enumeration stuff, as we discussed in LPC will go away now.
So it will be more like other buses :)
> > +/**
> > + * sdw_slv_verify - return parameter as sdw_slave, or NULL
> > + * @dev: device, probably from some driver model iterator
> > + *
> > + * When traversing the driver model tree, perhaps using driver model
> > + * iterators like @device_for_each_child(), you can't assume very much
> > + * about the nodes you find. Use this function to avoid oopses caused
> > + * by wrongly treating some non-SDW device as an sdw_slave.
> > + */
>
> This is also *very* scary, especially given that there's no analysis
> presented as to why there might be random other things on the bus. Why
> does SoundWire need this when other buses don't?
Sure I will double check on this one and sounds to me we cna remove this..
>
> > +static struct sdw_slave *sdw_slv_verify(struct device *dev)
> > +{
> > + return (dev->type == &sdw_slv_type)
> > + ? to_sdw_slave(dev)
> > + : NULL;
>
> This is needlessly obfuscated, if you want to write an if statement
> write an if statement.
Sure thing
> > +static int sdw_slv_match(struct device *dev, struct device_driver *driver)
> > +{
> > + struct sdw_slave *sdw_slv;
> > + struct sdw_driver *sdw_drv = to_sdw_driver(driver);
> > + struct sdw_slave_driver *drv;
> > + int ret = 0;
> > +
> > +
> > + if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
> > + return ret;
>
> Why do we need a check like this?
Since folks were doing both slave and master matches, this was done to be
double sure, but this will go away now.
> > +static int sdw_mstr_probe(struct device *dev)
> > +{
> > + const struct sdw_master_driver *sdrv =
> > + to_sdw_master_driver(dev->driver);
> > + struct sdw_master *mstr = to_sdw_master(dev);
> > + int ret;
> > +
> > + ret = dev_pm_domain_attach(dev, true);
> > +
> > + if (ret != -EPROBE_DEFER) {
> > + ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
> > + if (ret < 0)
> > + dev_pm_domain_detach(dev, true);
> > + }
>
> This looks *very* broken. Surely if we fail to attach a pm_domain for
> any reason other than one not being there to attach we shouldn't be
> trying to probe the device?
Yes I agree, we shouldnt be doing probing in that case. Will fix that up.
>
> > +EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
>
> This is EXPORT_SYMBOL_GPL() but the bus itself is dual licensed GPL/BSD
> - seems a bit inconsistent.
Thanks for pointing this out. The symbols should use EXPORT_SYMBOL() only
and the ones which link to other kernel GPL ones would need to be GPL ones.
> > +/**
> > + * snd_sdw_master_add: Registers the SoundWire Master interface. This needs
> > + * to be called for each Master interface supported by SoC. This
> > + * represents One clock and data line (Optionally multiple data lanes)
> > + * of Master interface.
> > + *
> > + * @master: the Master to be added.
> > + */
> > +int snd_sdw_master_add(struct sdw_master *master)
>
> This lies at the heart of the issues that seem to exist with the misuse
> of the driver model in this code. Normally what we see is that the
> controller would instantiate as whatever bus type the controller is
> attached by (typically a PCI or platform device) and then it wouild
> register a bus with the bus subsystem which would then instantiate
> slaves. Instead we have this system where the bus is registered by
> something in the system and then the master is a driver on the bus
> parallel to the slaves but with a separate driver type that causes
> confusion. Without having seen a master driver it's not even clear how
> this is going to work and allow the master to talk to its own hardware.
Yes as discussed the whole master stuff will eb redone so you wont see these
bits in next rev.
--
~Vinod
On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
> > +static int my_sdw_register_master()
> > +{
> > + struct sdw_master master;
> > + struct sdw_master_capabilities *m_cap;
> > +
> > + m_cap = &master.mstr_capabilities;
> > +
> > + /*
> > + * Fill the Master device capability, this is required
> > + * by bus driver to handle bus configurations.
> > + */
> > + m_cap->highphy_capable = false;
> > + m_cap->monitor_handover_supported = false;
> > + m_cap->sdw_dp0_supported = 1;
> > + m_cap->num_data_ports = INTEL_SDW_MAX_PORTS;
> > +
> > + return snd_sdw_master_add(&master);
> > +}
> > +
> > +Master driver gets registered for controlling the Master device. It
> > +provides the callback functions to the bus driver to control the bus in
> > +device specific way. Device and Driver binds according to the standard
> > +Linux device-driver bind model. Master driver is registered from the
> > +driver init code. Below code shows the sample Master driver
> > +registration.
> > +
> > +static struct sdw_master_driver intel_sdw_mstr_driver = {
> > + .driver_type = SDW_DRIVER_TYPE_MASTER,
> > + .driver = {
> > + .name = "intel_sdw_mstr",
> > + .pm = &intel_sdw_pm_ops,
> > + },
> > +
> > + .probe = intel_sdw_probe,
> > + .remove = intel_sdw_remove,
> > + .mstr_ops = &intel_sdw_master_ops,
> > + .mstr_port_ops = &intel_sdw_master_port_ops,
> > +};
> > +
> > +static int __init intel_sdw_init(void) {
> > + return snd_sdw_master_register_driver(&intel_sdw_mstr_driver);
> > +}
>
> Would be good to hear some detail the reasoning for the design
> choices here? Normally (I2C/SPI) the master sits on whatever bus
> the host uses to talk to the master so often this might be the
> platform bus for memory mapped devices, it then creates a bus and
> slaves register to that. This also has the nice property that its
> easy to create devices that sit behind other buses, for example
> here we might want a SoundWire master that sits behind a SPI bus.
> But you seem to have gone in the other direction and have the
> master sitting on the same bus as the slaves.
Since the controller on our SoC was enumerable, people went with this
approach. In this hindsight that may not have been the best choice.
So it will be fixed in next rev.
--
~Vinod
On Mon, Nov 14, 2016 at 03:44:35PM +0000, Charles Keepax wrote:
> > +
> > +Lock Hierarchy
> > +==============
> > +
> > +- Core lock is the parent of Master and Stream lock.
> > +- Master lock is parent of Message lock.
> > +- Master and Stream locks are independent of each other.
> > +
>
> A small diagram might be nice here, just would make it easier to
> see the hierarchy at a glance.
Sure will add..
--
~Vinod
On Tue, Nov 15, 2016 at 07:59:14PM +0530, Vinod Koul wrote:
> On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
> > slaves register to that. This also has the nice property that its
> > easy to create devices that sit behind other buses, for example
> > here we might want a SoundWire master that sits behind a SPI bus.
> > But you seem to have gone in the other direction and have the
> > master sitting on the same bus as the slaves.
> Since the controller on our SoC was enumerable, people went with this
> approach. In this hindsight that may not have been the best choice.
Doing buses properly isn't an obstacle to doing enumeration, indeed I'd
expect it to make it a lot easier - just have your driver for your
controller do the enumeration at probe time.
On Wed, Nov 16, 2016 at 05:59:29PM +0000, Mark Brown wrote:
> On Tue, Nov 15, 2016 at 07:59:14PM +0530, Vinod Koul wrote:
> > On Mon, Nov 14, 2016 at 02:15:48PM +0000, Charles Keepax wrote:
>
> > > slaves register to that. This also has the nice property that its
> > > easy to create devices that sit behind other buses, for example
> > > here we might want a SoundWire master that sits behind a SPI bus.
> > > But you seem to have gone in the other direction and have the
> > > master sitting on the same bus as the slaves.
>
> > Since the controller on our SoC was enumerable, people went with this
> > approach. In this hindsight that may not have been the best choice.
>
> Doing buses properly isn't an obstacle to doing enumeration, indeed I'd
> expect it to make it a lot easier - just have your driver for your
> controller do the enumeration at probe time.
Yes it is not :) This is somthing we are fixing now..
--
~Vinod