soundwire: Add support for SoundWire stream management
authorSanyog Kale <sanyog.r.kale@intel.com>
Thu, 26 Apr 2018 13:08:08 +0000 (18:38 +0530)
committerVinod Koul <vkoul@kernel.org>
Fri, 11 May 2018 16:17:03 +0000 (21:47 +0530)
This patch adds APIs and relevant stream data structures
for initialization and release of stream.

Signed-off-by: Hardik T Shah <hardik.t.shah@intel.com>
Signed-off-by: Sanyog Kale <sanyog.r.kale@intel.com>
Signed-off-by: Shreyas NC <shreyas.nc@intel.com>
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/soundwire/Makefile
drivers/soundwire/bus.c
drivers/soundwire/bus.h
drivers/soundwire/stream.c [new file with mode: 0644]
include/linux/soundwire/sdw.h

index e1a74c5..5817bea 100644 (file)
@@ -3,7 +3,7 @@
 #
 
 #Bus Objs
-soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o
+soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o
 obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o
 
 #Cadence Objs
index d6dc8e7..abf046f 100644 (file)
@@ -32,6 +32,7 @@ int sdw_add_bus_master(struct sdw_bus *bus)
        mutex_init(&bus->msg_lock);
        mutex_init(&bus->bus_lock);
        INIT_LIST_HEAD(&bus->slaves);
+       INIT_LIST_HEAD(&bus->m_rt_list);
 
        if (bus->ops->read_prop) {
                ret = bus->ops->read_prop(bus);
index 345c34d..2e5043d 100644 (file)
@@ -45,6 +45,42 @@ struct sdw_msg {
        bool page;
 };
 
+/**
+ * sdw_slave_runtime: Runtime Stream parameters for Slave
+ *
+ * @slave: Slave handle
+ * @direction: Data direction for Slave
+ * @ch_count: Number of channels handled by the Slave for
+ * this stream
+ * @m_rt_node: sdw_master_runtime list node
+ */
+struct sdw_slave_runtime {
+       struct sdw_slave *slave;
+       enum sdw_data_direction direction;
+       unsigned int ch_count;
+       struct list_head m_rt_node;
+};
+
+/**
+ * sdw_master_runtime: Runtime stream parameters for Master
+ *
+ * @bus: Bus handle
+ * @stream: Stream runtime handle
+ * @direction: Data direction for Master
+ * @ch_count: Number of channels handled by the Master for
+ * this stream, can be zero.
+ * @slave_rt_list: Slave runtime list
+ * @bus_node: sdw_bus m_rt_list node
+ */
+struct sdw_master_runtime {
+       struct sdw_bus *bus;
+       struct sdw_stream_runtime *stream;
+       enum sdw_data_direction direction;
+       unsigned int ch_count;
+       struct list_head slave_rt_list;
+       struct list_head bus_node;
+};
+
 int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg);
 int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg,
                                struct sdw_defer *defer);
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c
new file mode 100644 (file)
index 0000000..89b2550
--- /dev/null
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2015-18 Intel Corporation.
+
+/*
+ *  stream.c - SoundWire Bus stream operations.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/slab.h>
+#include <linux/soundwire/sdw.h>
+#include "bus.h"
+
+/**
+ * sdw_release_stream() - Free the assigned stream runtime
+ *
+ * @stream: SoundWire stream runtime
+ *
+ * sdw_release_stream should be called only once per stream
+ */
+void sdw_release_stream(struct sdw_stream_runtime *stream)
+{
+       kfree(stream);
+}
+EXPORT_SYMBOL(sdw_release_stream);
+
+/**
+ * sdw_alloc_stream() - Allocate and return stream runtime
+ *
+ * @stream_name: SoundWire stream name
+ *
+ * Allocates a SoundWire stream runtime instance.
+ * sdw_alloc_stream should be called only once per stream. Typically
+ * invoked from ALSA/ASoC machine/platform driver.
+ */
+struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name)
+{
+       struct sdw_stream_runtime *stream;
+
+       stream = kzalloc(sizeof(*stream), GFP_KERNEL);
+       if (!stream)
+               return NULL;
+
+       stream->name = stream_name;
+       stream->state = SDW_STREAM_ALLOCATED;
+
+       return stream;
+}
+EXPORT_SYMBOL(sdw_alloc_stream);
+
+/**
+ * sdw_alloc_master_rt() - Allocates and initialize Master runtime handle
+ *
+ * @bus: SDW bus instance
+ * @stream_config: Stream configuration
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_master_runtime
+*sdw_alloc_master_rt(struct sdw_bus *bus,
+                       struct sdw_stream_config *stream_config,
+                       struct sdw_stream_runtime *stream)
+{
+       struct sdw_master_runtime *m_rt;
+
+       m_rt = stream->m_rt;
+
+       /*
+        * check if Master is already allocated (as a result of Slave adding
+        * it first), if so skip allocation and go to configure
+        */
+       if (m_rt)
+               goto stream_config;
+
+       m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
+       if (!m_rt)
+               return NULL;
+
+       /* Initialization of Master runtime handle */
+       INIT_LIST_HEAD(&m_rt->slave_rt_list);
+       stream->m_rt = m_rt;
+
+       list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
+
+stream_config:
+       m_rt->ch_count = stream_config->ch_count;
+       m_rt->bus = bus;
+       m_rt->stream = stream;
+       m_rt->direction = stream_config->direction;
+
+       return m_rt;
+}
+
+/**
+ * sdw_alloc_slave_rt() - Allocate and initialize Slave runtime handle.
+ *
+ * @slave: Slave handle
+ * @stream_config: Stream configuration
+ * @stream: Stream runtime handle
+ *
+ * This function is to be called with bus_lock held.
+ */
+static struct sdw_slave_runtime
+*sdw_alloc_slave_rt(struct sdw_slave *slave,
+                       struct sdw_stream_config *stream_config,
+                       struct sdw_stream_runtime *stream)
+{
+       struct sdw_slave_runtime *s_rt = NULL;
+
+       s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL);
+       if (!s_rt)
+               return NULL;
+
+       s_rt->ch_count = stream_config->ch_count;
+       s_rt->direction = stream_config->direction;
+       s_rt->slave = slave;
+
+       return s_rt;
+}
+
+/**
+ * sdw_release_slave_stream() - Free Slave(s) runtime handle
+ *
+ * @slave: Slave handle.
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held.
+ */
+static void sdw_release_slave_stream(struct sdw_slave *slave,
+                       struct sdw_stream_runtime *stream)
+{
+       struct sdw_slave_runtime *s_rt, *_s_rt;
+       struct sdw_master_runtime *m_rt = stream->m_rt;
+
+       /* Retrieve Slave runtime handle */
+       list_for_each_entry_safe(s_rt, _s_rt,
+                       &m_rt->slave_rt_list, m_rt_node) {
+
+               if (s_rt->slave == slave) {
+                       list_del(&s_rt->m_rt_node);
+                       kfree(s_rt);
+                       return;
+               }
+       }
+}
+
+/**
+ * sdw_release_master_stream() - Free Master runtime handle
+ *
+ * @stream: Stream runtime handle.
+ *
+ * This function is to be called with bus_lock held
+ * It frees the Master runtime handle and associated Slave(s) runtime
+ * handle. If this is called first then sdw_release_slave_stream() will have
+ * no effect as Slave(s) runtime handle would already be freed up.
+ */
+static void sdw_release_master_stream(struct sdw_stream_runtime *stream)
+{
+       struct sdw_master_runtime *m_rt = stream->m_rt;
+       struct sdw_slave_runtime *s_rt, *_s_rt;
+
+       list_for_each_entry_safe(s_rt, _s_rt,
+                       &m_rt->slave_rt_list, m_rt_node)
+               sdw_stream_remove_slave(s_rt->slave, stream);
+
+       list_del(&m_rt->bus_node);
+}
+
+/**
+ * sdw_stream_remove_master() - Remove master from sdw_stream
+ *
+ * @bus: SDW Bus instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees master_rt from a stream
+ */
+int sdw_stream_remove_master(struct sdw_bus *bus,
+               struct sdw_stream_runtime *stream)
+{
+       mutex_lock(&bus->bus_lock);
+
+       sdw_release_master_stream(stream);
+       stream->state = SDW_STREAM_RELEASED;
+       kfree(stream->m_rt);
+       stream->m_rt = NULL;
+
+       mutex_unlock(&bus->bus_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_master);
+
+/**
+ * sdw_stream_remove_slave() - Remove slave from sdw_stream
+ *
+ * @slave: SDW Slave instance
+ * @stream: SoundWire stream
+ *
+ * This removes and frees slave_rt from a stream
+ */
+int sdw_stream_remove_slave(struct sdw_slave *slave,
+               struct sdw_stream_runtime *stream)
+{
+       mutex_lock(&slave->bus->bus_lock);
+
+       sdw_release_slave_stream(slave, stream);
+
+       mutex_unlock(&slave->bus->bus_lock);
+
+       return 0;
+}
+EXPORT_SYMBOL(sdw_stream_remove_slave);
+
+/**
+ * sdw_config_stream() - Configure the allocated stream
+ *
+ * @dev: SDW device
+ * @stream: SoundWire stream
+ * @stream_config: Stream configuration for audio stream
+ * @is_slave: is API called from Slave or Master
+ *
+ * This function is to be called with bus_lock held.
+ */
+static int sdw_config_stream(struct device *dev,
+               struct sdw_stream_runtime *stream,
+               struct sdw_stream_config *stream_config, bool is_slave)
+{
+       /*
+        * Update the stream rate, channel and bps based on data
+        * source. For more than one data source (multilink),
+        * match the rate, bps, stream type and increment number of channels.
+        *
+        * If rate/bps is zero, it means the values are not set, so skip
+        * comparison and allow the value to be set and stored in stream
+        */
+       if (stream->params.rate &&
+                       stream->params.rate != stream_config->frame_rate) {
+               dev_err(dev, "rate not matching, stream:%s", stream->name);
+               return -EINVAL;
+       }
+
+       if (stream->params.bps &&
+                       stream->params.bps != stream_config->bps) {
+               dev_err(dev, "bps not matching, stream:%s", stream->name);
+               return -EINVAL;
+       }
+
+       stream->type = stream_config->type;
+       stream->params.rate = stream_config->frame_rate;
+       stream->params.bps = stream_config->bps;
+
+       /* TODO: Update this check during Device-device support */
+       if (is_slave)
+               stream->params.ch_count += stream_config->ch_count;
+
+       return 0;
+}
+
+/**
+ * sdw_stream_add_master() - Allocate and add master runtime to a stream
+ *
+ * @bus: SDW Bus instance
+ * @stream_config: Stream configuration for audio stream
+ * @stream: SoundWire stream
+ */
+int sdw_stream_add_master(struct sdw_bus *bus,
+               struct sdw_stream_config *stream_config,
+               struct sdw_stream_runtime *stream)
+{
+       struct sdw_master_runtime *m_rt = NULL;
+       int ret;
+
+       mutex_lock(&bus->bus_lock);
+
+       m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
+       if (!m_rt) {
+               dev_err(bus->dev,
+                               "Master runtime config failed for stream:%s",
+                               stream->name);
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       ret = sdw_config_stream(bus->dev, stream, stream_config, false);
+       if (ret)
+               goto stream_error;
+
+       stream->state = SDW_STREAM_CONFIGURED;
+
+stream_error:
+       sdw_release_master_stream(stream);
+error:
+       mutex_unlock(&bus->bus_lock);
+       return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_master);
+
+/**
+ * sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
+ *
+ * @slave: SDW Slave instance
+ * @stream_config: Stream configuration for audio stream
+ * @stream: SoundWire stream
+ */
+int sdw_stream_add_slave(struct sdw_slave *slave,
+               struct sdw_stream_config *stream_config,
+               struct sdw_stream_runtime *stream)
+{
+       struct sdw_slave_runtime *s_rt;
+       struct sdw_master_runtime *m_rt;
+       int ret;
+
+       mutex_lock(&slave->bus->bus_lock);
+
+       /*
+        * If this API is invoked by Slave first then m_rt is not valid.
+        * So, allocate m_rt and add Slave to it.
+        */
+       m_rt = sdw_alloc_master_rt(slave->bus, stream_config, stream);
+       if (!m_rt) {
+               dev_err(&slave->dev,
+                               "alloc master runtime failed for stream:%s",
+                               stream->name);
+               ret = -ENOMEM;
+               goto error;
+       }
+
+       s_rt = sdw_alloc_slave_rt(slave, stream_config, stream);
+       if (!s_rt) {
+               dev_err(&slave->dev,
+                               "Slave runtime config failed for stream:%s",
+                               stream->name);
+               ret = -ENOMEM;
+               goto stream_error;
+       }
+
+       ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
+       if (ret)
+               goto stream_error;
+
+       list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
+
+       stream->state = SDW_STREAM_CONFIGURED;
+       goto error;
+
+stream_error:
+       /*
+        * we hit error so cleanup the stream, release all Slave(s) and
+        * Master runtime
+        */
+       sdw_release_master_stream(stream);
+error:
+       mutex_unlock(&slave->bus->bus_lock);
+       return ret;
+}
+EXPORT_SYMBOL(sdw_stream_add_slave);
index e91fdcf..610a98d 100644 (file)
@@ -61,6 +61,30 @@ enum sdw_command_response {
        SDW_CMD_FAIL_OTHER = 4,
 };
 
+/**
+ * enum sdw_stream_type: data stream type
+ *
+ * @SDW_STREAM_PCM: PCM data stream
+ * @SDW_STREAM_PDM: PDM data stream
+ *
+ * spec doesn't define this, but is used in implementation
+ */
+enum sdw_stream_type {
+       SDW_STREAM_PCM = 0,
+       SDW_STREAM_PDM = 1,
+};
+
+/**
+ * enum sdw_data_direction: Data direction
+ *
+ * @SDW_DATA_DIR_RX: Data into Port
+ * @SDW_DATA_DIR_TX: Data out of Port
+ */
+enum sdw_data_direction {
+       SDW_DATA_DIR_RX = 0,
+       SDW_DATA_DIR_TX = 1,
+};
+
 /*
  * SDW properties, defined in MIPI DisCo spec v1.0
  */
@@ -450,6 +474,9 @@ struct sdw_master_ops {
  * @msg_lock: message lock
  * @ops: Master callback ops
  * @prop: Master properties
+ * @m_rt_list: List of Master instance of all stream(s) running on Bus. This
+ * is used to compute and program bus bandwidth, clock, frame shape,
+ * transport and port parameters
  * @defer_msg: Defer message
  * @clk_stop_timeout: Clock stop timeout computed
  */
@@ -462,6 +489,7 @@ struct sdw_bus {
        struct mutex msg_lock;
        const struct sdw_master_ops *ops;
        struct sdw_master_prop prop;
+       struct list_head m_rt_list;
        struct sdw_defer defer_msg;
        unsigned int clk_stop_timeout;
 };
@@ -469,6 +497,87 @@ struct sdw_bus {
 int sdw_add_bus_master(struct sdw_bus *bus);
 void sdw_delete_bus_master(struct sdw_bus *bus);
 
+/**
+ * sdw_stream_config: Master or Slave stream configuration
+ *
+ * @frame_rate: Audio frame rate of the stream, in Hz
+ * @ch_count: Channel count of the stream
+ * @bps: Number of bits per audio sample
+ * @direction: Data direction
+ * @type: Stream type PCM or PDM
+ */
+struct sdw_stream_config {
+       unsigned int frame_rate;
+       unsigned int ch_count;
+       unsigned int bps;
+       enum sdw_data_direction direction;
+       enum sdw_stream_type type;
+};
+
+/**
+ * sdw_stream_state: Stream states
+ *
+ * @SDW_STREAM_ALLOCATED: New stream allocated.
+ * @SDW_STREAM_CONFIGURED: Stream configured
+ * @SDW_STREAM_PREPARED: Stream prepared
+ * @SDW_STREAM_ENABLED: Stream enabled
+ * @SDW_STREAM_DISABLED: Stream disabled
+ * @SDW_STREAM_DEPREPARED: Stream de-prepared
+ * @SDW_STREAM_RELEASED: Stream released
+ */
+enum sdw_stream_state {
+       SDW_STREAM_ALLOCATED = 0,
+       SDW_STREAM_CONFIGURED = 1,
+       SDW_STREAM_PREPARED = 2,
+       SDW_STREAM_ENABLED = 3,
+       SDW_STREAM_DISABLED = 4,
+       SDW_STREAM_DEPREPARED = 5,
+       SDW_STREAM_RELEASED = 6,
+};
+
+/**
+ * sdw_stream_params: Stream parameters
+ *
+ * @rate: Sampling frequency, in Hz
+ * @ch_count: Number of channels
+ * @bps: bits per channel sample
+ */
+struct sdw_stream_params {
+       unsigned int rate;
+       unsigned int ch_count;
+       unsigned int bps;
+};
+
+/**
+ * sdw_stream_runtime: Runtime stream parameters
+ *
+ * @name: SoundWire stream name
+ * @params: Stream parameters
+ * @state: Current state of the stream
+ * @type: Stream type PCM or PDM
+ * @m_rt: Master runtime
+ */
+struct sdw_stream_runtime {
+       char *name;
+       struct sdw_stream_params params;
+       enum sdw_stream_state state;
+       enum sdw_stream_type type;
+       struct sdw_master_runtime *m_rt;
+};
+
+struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name);
+void sdw_release_stream(struct sdw_stream_runtime *stream);
+int sdw_stream_add_master(struct sdw_bus *bus,
+               struct sdw_stream_config *stream_config,
+               struct sdw_stream_runtime *stream);
+int sdw_stream_add_slave(struct sdw_slave *slave,
+               struct sdw_stream_config *stream_config,
+               struct sdw_stream_runtime *stream);
+int sdw_stream_remove_master(struct sdw_bus *bus,
+               struct sdw_stream_runtime *stream);
+int sdw_stream_remove_slave(struct sdw_slave *slave,
+               struct sdw_stream_runtime *stream);
+
 /* messaging and data APIs */
 
 int sdw_read(struct sdw_slave *slave, u32 addr);