soundwire: Add support for multi link bank switch
authorShreyas NC <shreyas.nc@intel.com>
Fri, 27 Jul 2018 09:14:16 +0000 (14:44 +0530)
committerVinod Koul <vkoul@kernel.org>
Mon, 27 Aug 2018 04:19:48 +0000 (09:49 +0530)
In cases of multiple Masters in a stream, synchronization
between multiple Master(s) is achieved by performing bank switch
together and using Master methods.

Add sdw_ml_bank_switch() to wait for completion of bank switch.

Signed-off-by: Sanyog Kale <sanyog.r.kale@intel.com>
Signed-off-by: Shreyas NC <shreyas.nc@intel.com>
Acked-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Vinod Koul <vkoul@kernel.org>
drivers/soundwire/bus.c
drivers/soundwire/bus.h
drivers/soundwire/stream.c
include/linux/soundwire/sdw.h

index dbabd5e69343fa65273fdc17d2ccee5656e6258a..1cbfedfc20efd51700d29b4d2600a76d84d4d98b 100644 (file)
@@ -35,6 +35,11 @@ int sdw_add_bus_master(struct sdw_bus *bus)
        INIT_LIST_HEAD(&bus->slaves);
        INIT_LIST_HEAD(&bus->m_rt_list);
 
+       /*
+        * Initialize multi_link flag
+        * TODO: populate this flag by reading property from FW node
+        */
+       bus->multi_link = false;
        if (bus->ops->read_prop) {
                ret = bus->ops->read_prop(bus);
                if (ret < 0) {
index b6cfbdfc47d5bdfcb4152b09d4bdc776c8e5a9a0..c77de05b81005d3afa1474ffd03099faa3f777bd 100644 (file)
@@ -4,6 +4,8 @@
 #ifndef __SDW_BUS_H
 #define __SDW_BUS_H
 
+#define DEFAULT_BANK_SWITCH_TIMEOUT 3000
+
 #if IS_ENABLED(CONFIG_ACPI)
 int sdw_acpi_find_slaves(struct sdw_bus *bus);
 #else
index ee024d72dd7b71ee064ea4321c67958df00f34d3..3d98f20cbd6a9e3701082026716db35201ed63c5 100644 (file)
@@ -626,9 +626,10 @@ static int sdw_program_params(struct sdw_bus *bus)
        return ret;
 }
 
-static int sdw_bank_switch(struct sdw_bus *bus)
+static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count)
 {
        int col_index, row_index;
+       bool multi_link;
        struct sdw_msg *wr_msg;
        u8 *wbuf = NULL;
        int ret = 0;
@@ -638,6 +639,8 @@ static int sdw_bank_switch(struct sdw_bus *bus)
        if (!wr_msg)
                return -ENOMEM;
 
+       bus->defer_msg.msg = wr_msg;
+
        wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL);
        if (!wbuf) {
                ret = -ENOMEM;
@@ -658,17 +661,29 @@ static int sdw_bank_switch(struct sdw_bus *bus)
                                        SDW_MSG_FLAG_WRITE, wbuf);
        wr_msg->ssp_sync = true;
 
-       ret = sdw_transfer(bus, wr_msg);
+       /*
+        * Set the multi_link flag only when both the hardware supports
+        * and there is a stream handled by multiple masters
+        */
+       multi_link = bus->multi_link && (m_rt_count > 1);
+
+       if (multi_link)
+               ret = sdw_transfer_defer(bus, wr_msg, &bus->defer_msg);
+       else
+               ret = sdw_transfer(bus, wr_msg);
+
        if (ret < 0) {
                dev_err(bus->dev, "Slave frame_ctrl reg write failed");
                goto error;
        }
 
-       kfree(wr_msg);
-       kfree(wbuf);
-       bus->defer_msg.msg = NULL;
-       bus->params.curr_bank = !bus->params.curr_bank;
-       bus->params.next_bank = !bus->params.next_bank;
+       if (!multi_link) {
+               kfree(wr_msg);
+               kfree(wbuf);
+               bus->defer_msg.msg = NULL;
+               bus->params.curr_bank = !bus->params.curr_bank;
+               bus->params.next_bank = !bus->params.next_bank;
+       }
 
        return 0;
 
@@ -679,36 +694,87 @@ error_1:
        return ret;
 }
 
+/**
+ * sdw_ml_sync_bank_switch: Multilink register bank switch
+ *
+ * @bus: SDW bus instance
+ *
+ * Caller function should free the buffers on error
+ */
+static int sdw_ml_sync_bank_switch(struct sdw_bus *bus)
+{
+       unsigned long time_left;
+
+       if (!bus->multi_link)
+               return 0;
+
+       /* Wait for completion of transfer */
+       time_left = wait_for_completion_timeout(&bus->defer_msg.complete,
+                                               bus->bank_switch_timeout);
+
+       if (!time_left) {
+               dev_err(bus->dev, "Controller Timed out on bank switch");
+               return -ETIMEDOUT;
+       }
+
+       bus->params.curr_bank = !bus->params.curr_bank;
+       bus->params.next_bank = !bus->params.next_bank;
+
+       if (bus->defer_msg.msg) {
+               kfree(bus->defer_msg.msg->buf);
+               kfree(bus->defer_msg.msg);
+       }
+
+       return 0;
+}
+
 static int do_bank_switch(struct sdw_stream_runtime *stream)
 {
        struct sdw_master_runtime *m_rt = NULL;
        const struct sdw_master_ops *ops;
        struct sdw_bus *bus = NULL;
+       bool multi_link = false;
        int ret = 0;
 
-
        list_for_each_entry(m_rt, &stream->master_list, stream_node) {
                bus = m_rt->bus;
                ops = bus->ops;
 
+               if (bus->multi_link) {
+                       multi_link = true;
+                       mutex_lock(&bus->msg_lock);
+               }
+
                /* Pre-bank switch */
                if (ops->pre_bank_switch) {
                        ret = ops->pre_bank_switch(bus);
                        if (ret < 0) {
                                dev_err(bus->dev,
                                        "Pre bank switch op failed: %d", ret);
-                               return ret;
+                               goto msg_unlock;
                        }
                }
 
-               /* Bank switch */
-               ret = sdw_bank_switch(bus);
+               /*
+                * Perform Bank switch operation.
+                * For multi link cases, the actual bank switch is
+                * synchronized across all Masters and happens later as a
+                * part of post_bank_switch ops.
+                */
+               ret = sdw_bank_switch(bus, stream->m_rt_count);
                if (ret < 0) {
                        dev_err(bus->dev, "Bank switch failed: %d", ret);
-                       return ret;
+                       goto error;
+
                }
        }
 
+       /*
+        * For multi link cases, it is expected that the bank switch is
+        * triggered by the post_bank_switch for the first Master in the list
+        * and for the other Masters the post_bank_switch() should return doing
+        * nothing.
+        */
        list_for_each_entry(m_rt, &stream->master_list, stream_node) {
                bus = m_rt->bus;
                ops = bus->ops;
@@ -719,7 +785,47 @@ static int do_bank_switch(struct sdw_stream_runtime *stream)
                        if (ret < 0) {
                                dev_err(bus->dev,
                                        "Post bank switch op failed: %d", ret);
+                               goto error;
                        }
+               } else if (bus->multi_link && stream->m_rt_count > 1) {
+                       dev_err(bus->dev,
+                               "Post bank switch ops not implemented");
+                       goto error;
+               }
+
+               /* Set the bank switch timeout to default, if not set */
+               if (!bus->bank_switch_timeout)
+                       bus->bank_switch_timeout = DEFAULT_BANK_SWITCH_TIMEOUT;
+
+               /* Check if bank switch was successful */
+               ret = sdw_ml_sync_bank_switch(bus);
+               if (ret < 0) {
+                       dev_err(bus->dev,
+                               "multi link bank switch failed: %d", ret);
+                       goto error;
+               }
+
+               mutex_unlock(&bus->msg_lock);
+       }
+
+       return ret;
+
+error:
+       list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+
+               bus = m_rt->bus;
+
+               kfree(bus->defer_msg.msg->buf);
+               kfree(bus->defer_msg.msg);
+       }
+
+msg_unlock:
+
+       if (multi_link) {
+               list_for_each_entry(m_rt, &stream->master_list, stream_node) {
+                       bus = m_rt->bus;
+                       if (mutex_is_locked(&bus->msg_lock))
+                               mutex_unlock(&bus->msg_lock);
                }
        }
 
@@ -964,6 +1070,7 @@ int sdw_stream_remove_master(struct sdw_bus *bus,
 
                sdw_master_port_release(bus, m_rt);
                sdw_release_master_stream(m_rt, stream);
+               stream->m_rt_count--;
        }
 
        if (list_empty(&stream->master_list))
@@ -1150,6 +1257,18 @@ int sdw_stream_add_master(struct sdw_bus *bus,
 
        mutex_lock(&bus->bus_lock);
 
+       /*
+        * For multi link streams, add the second master only if
+        * the bus supports it.
+        * Check if bus->multi_link is set
+        */
+       if (!bus->multi_link && stream->m_rt_count > 0) {
+               dev_err(bus->dev,
+                       "Multilink not supported, link %d", bus->link_id);
+               ret = -EINVAL;
+               goto unlock;
+       }
+
        m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
        if (!m_rt) {
                dev_err(bus->dev,
@@ -1167,6 +1286,8 @@ int sdw_stream_add_master(struct sdw_bus *bus,
        if (ret)
                goto stream_error;
 
+       stream->m_rt_count++;
+
        goto unlock;
 
 stream_error:
index 03df709fb8ef35de538ed345b355bb9ec0d2cb97..c6aa2bf847c79de4417b58bbe26a6f78c0e99f58 100644 (file)
@@ -678,6 +678,9 @@ struct sdw_master_ops {
  * @defer_msg: Defer message
  * @clk_stop_timeout: Clock stop timeout computed
  * @bank_switch_timeout: Bank switch timeout computed
+ * @multi_link: Store bus property that indicates if multi links
+ * are supported. This flag is populated by drivers after reading
+ * appropriate firmware (ACPI/DT).
  */
 struct sdw_bus {
        struct device *dev;
@@ -694,6 +697,7 @@ struct sdw_bus {
        struct sdw_defer defer_msg;
        unsigned int clk_stop_timeout;
        u32 bank_switch_timeout;
+       bool multi_link;
 };
 
 int sdw_add_bus_master(struct sdw_bus *bus);