slimbus: stream: add stream support
[linux-2.6-microblaze.git] / drivers / slimbus / stream.c
diff --git a/drivers/slimbus/stream.c b/drivers/slimbus/stream.c
new file mode 100644 (file)
index 0000000..2fa0532
--- /dev/null
@@ -0,0 +1,477 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018, Linaro Limited
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/list.h>
+#include <linux/slimbus.h>
+#include <uapi/sound/asound.h>
+#include "slimbus.h"
+
+/**
+ * struct segdist_code - Segment Distributions code from
+ *     Table 20 of SLIMbus Specs Version 2.0
+ *
+ * @ratem: Channel Rate Multipler(Segments per Superframe)
+ * @seg_interval: Number of slots between the first Slot of Segment
+ *             and the first slot of the next  consecutive Segment.
+ * @segdist_code: Segment Distribution Code SD[11:0]
+ * @seg_offset_mask: Segment offset mask in SD[11:0]
+ * @segdist_codes: List of all possible Segmet Distribution codes.
+ */
+static const struct segdist_code {
+       int ratem;
+       int seg_interval;
+       int segdist_code;
+       u32 seg_offset_mask;
+
+} segdist_codes[] = {
+       {1,     1536,   0x200,   0xdff},
+       {2,     768,    0x100,   0xcff},
+       {4,     384,    0x080,   0xc7f},
+       {8,     192,    0x040,   0xc3f},
+       {16,    96,     0x020,   0xc1f},
+       {32,    48,     0x010,   0xc0f},
+       {64,    24,     0x008,   0xc07},
+       {128,   12,     0x004,   0xc03},
+       {256,   6,      0x002,   0xc01},
+       {512,   3,      0x001,   0xc00},
+       {3,     512,    0xe00,   0x1ff},
+       {6,     256,    0xd00,   0x0ff},
+       {12,    128,    0xc80,   0x07f},
+       {24,    64,     0xc40,   0x03f},
+       {48,    32,     0xc20,   0x01f},
+       {96,    16,     0xc10,   0x00f},
+       {192,   8,      0xc08,   0x007},
+       {364,   4,      0xc04,   0x003},
+       {768,   2,      0xc02,   0x001},
+};
+
+/*
+ * Presence Rate table for all Natural Frequencies
+ * The Presence rate of a constant bitrate stream is mean flow rate of the
+ * stream expressed in occupied Segments of that Data Channel per second.
+ * Table 66 from SLIMbus 2.0 Specs
+ *
+ * Index of the table corresponds to Presence rate code for the respective rate
+ * in the table.
+ */
+static const int slim_presence_rate_table[] = {
+       0, /* Not Indicated */
+       12000,
+       24000,
+       48000,
+       96000,
+       192000,
+       384000,
+       768000,
+       0, /* Reserved */
+       110250,
+       220500,
+       441000,
+       882000,
+       176400,
+       352800,
+       705600,
+       4000,
+       8000,
+       16000,
+       32000,
+       64000,
+       128000,
+       256000,
+       512000,
+};
+
+/*
+ * slim_stream_allocate() - Allocate a new SLIMbus Stream
+ * @dev:Slim device to be associated with
+ * @name: name of the stream
+ *
+ * This is very first call for SLIMbus streaming, this API will allocate
+ * a new SLIMbus stream and return a valid stream runtime pointer for client
+ * to use it in subsequent stream apis. state of stream is set to ALLOCATED
+ *
+ * Return: valid pointer on success and error code on failure.
+ * From ASoC DPCM framework, this state is linked to startup() operation.
+ */
+struct slim_stream_runtime *slim_stream_allocate(struct slim_device *dev,
+                                                const char *name)
+{
+       struct slim_stream_runtime *rt;
+
+       rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+       if (!rt)
+               return ERR_PTR(-ENOMEM);
+
+       rt->name = kasprintf(GFP_KERNEL, "slim-%s", name);
+       if (!rt->name) {
+               kfree(rt);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       rt->dev = dev;
+       spin_lock(&dev->stream_list_lock);
+       list_add_tail(&rt->node, &dev->stream_list);
+       spin_unlock(&dev->stream_list_lock);
+
+       return rt;
+}
+EXPORT_SYMBOL_GPL(slim_stream_allocate);
+
+static int slim_connect_port_channel(struct slim_stream_runtime *stream,
+                                    struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[2];
+       struct slim_val_inf msg = {0, 2, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_CONNECT_SOURCE;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 6, stream->dev->laddr, &msg);
+
+       if (port->direction == SLIM_PORT_SINK)
+               txn.mc = SLIM_MSG_MC_CONNECT_SINK;
+
+       wbuf[0] = port->id;
+       wbuf[1] = port->ch.id;
+       port->ch.state = SLIM_CH_STATE_ASSOCIATED;
+       port->state = SLIM_PORT_UNCONFIGURED;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+static int slim_disconnect_port(struct slim_stream_runtime *stream,
+                               struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[1];
+       struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_DISCONNECT_PORT;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
+
+       wbuf[0] = port->id;
+       port->ch.state = SLIM_CH_STATE_DISCONNECTED;
+       port->state = SLIM_PORT_DISCONNECTED;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+static int slim_deactivate_remove_channel(struct slim_stream_runtime *stream,
+                                         struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[1];
+       struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_NEXT_DEACTIVATE_CHANNEL;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
+       int ret;
+
+       wbuf[0] = port->ch.id;
+       ret = slim_do_transfer(sdev->ctrl, &txn);
+       if (ret)
+               return ret;
+
+       txn.mc = SLIM_MSG_MC_NEXT_REMOVE_CHANNEL;
+       port->ch.state = SLIM_CH_STATE_REMOVED;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+static int slim_get_prate_code(int rate)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(slim_presence_rate_table); i++) {
+               if (rate == slim_presence_rate_table[i])
+                       return i;
+       }
+
+       return -EINVAL;
+}
+
+/*
+ * slim_stream_prepare() - Prepare a SLIMbus Stream
+ *
+ * @rt: instance of slim stream runtime to configure
+ * @cfg: new configuration for the stream
+ *
+ * This API will configure SLIMbus stream with config parameters from cfg.
+ * return zero on success and error code on failure. From ASoC DPCM framework,
+ * this state is linked to hw_params() operation.
+ */
+int slim_stream_prepare(struct slim_stream_runtime *rt,
+                       struct slim_stream_config *cfg)
+{
+       struct slim_controller *ctrl = rt->dev->ctrl;
+       struct slim_port *port;
+       int num_ports, i, port_id;
+
+       if (rt->ports) {
+               dev_err(&rt->dev->dev, "Stream already Prepared\n");
+               return -EINVAL;
+       }
+
+       num_ports = hweight32(cfg->port_mask);
+       rt->ports = kcalloc(num_ports, sizeof(*port), GFP_KERNEL);
+       if (!rt->ports)
+               return -ENOMEM;
+
+       rt->num_ports = num_ports;
+       rt->rate = cfg->rate;
+       rt->bps = cfg->bps;
+       rt->direction = cfg->direction;
+
+       if (cfg->rate % ctrl->a_framer->superfreq) {
+               /*
+                * data rate not exactly multiple of super frame,
+                * use PUSH/PULL protocol
+                */
+               if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK)
+                       rt->prot = SLIM_PROTO_PUSH;
+               else
+                       rt->prot = SLIM_PROTO_PULL;
+       } else {
+               rt->prot = SLIM_PROTO_ISO;
+       }
+
+       rt->ratem = cfg->rate/ctrl->a_framer->superfreq;
+
+       i = 0;
+       for_each_set_bit(port_id, &cfg->port_mask, SLIM_DEVICE_MAX_PORTS) {
+               port = &rt->ports[i];
+               port->state = SLIM_PORT_DISCONNECTED;
+               port->id = port_id;
+               port->ch.prrate = slim_get_prate_code(cfg->rate);
+               port->ch.id = cfg->chs[i];
+               port->ch.data_fmt = SLIM_CH_DATA_FMT_NOT_DEFINED;
+               port->ch.aux_fmt = SLIM_CH_AUX_FMT_NOT_APPLICABLE;
+               port->ch.state = SLIM_CH_STATE_ALLOCATED;
+
+               if (cfg->direction == SNDRV_PCM_STREAM_PLAYBACK)
+                       port->direction = SLIM_PORT_SINK;
+               else
+                       port->direction = SLIM_PORT_SOURCE;
+
+               slim_connect_port_channel(rt, port);
+               i++;
+       }
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(slim_stream_prepare);
+
+static int slim_define_channel_content(struct slim_stream_runtime *stream,
+                                      struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[4];
+       struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CONTENT;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg);
+
+       wbuf[0] = port->ch.id;
+       wbuf[1] = port->ch.prrate;
+
+       /* Frequency Locked for ISO Protocol */
+       if (stream->prot != SLIM_PROTO_ISO)
+               wbuf[1] |= SLIM_CHANNEL_CONTENT_FL;
+
+       wbuf[2] = port->ch.data_fmt | (port->ch.aux_fmt << 4);
+       wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS;
+       port->ch.state = SLIM_CH_STATE_CONTENT_DEFINED;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+static int slim_get_segdist_code(int ratem)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(segdist_codes); i++) {
+               if (segdist_codes[i].ratem == ratem)
+                       return segdist_codes[i].segdist_code;
+       }
+
+       return -EINVAL;
+}
+
+static int slim_define_channel(struct slim_stream_runtime *stream,
+                                      struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[4];
+       struct slim_val_inf msg = {0, 4, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_NEXT_DEFINE_CHANNEL;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 8, stream->dev->laddr, &msg);
+
+       port->ch.seg_dist = slim_get_segdist_code(stream->ratem);
+
+       wbuf[0] = port->ch.id;
+       wbuf[1] = port->ch.seg_dist & 0xFF;
+       wbuf[2] = (stream->prot << 4) | ((port->ch.seg_dist & 0xF00) >> 8);
+       if (stream->prot == SLIM_PROTO_ISO)
+               wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS;
+       else
+               wbuf[3] = stream->bps/SLIM_SLOT_LEN_BITS + 1;
+
+       port->ch.state = SLIM_CH_STATE_DEFINED;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+static int slim_activate_channel(struct slim_stream_runtime *stream,
+                                struct slim_port *port)
+{
+       struct slim_device *sdev = stream->dev;
+       u8 wbuf[1];
+       struct slim_val_inf msg = {0, 1, NULL, wbuf, NULL};
+       u8 mc = SLIM_MSG_MC_NEXT_ACTIVATE_CHANNEL;
+       DEFINE_SLIM_LDEST_TXN(txn, mc, 5, stream->dev->laddr, &msg);
+
+       txn.msg->num_bytes = 1;
+       txn.msg->wbuf = wbuf;
+       wbuf[0] = port->ch.id;
+       port->ch.state = SLIM_CH_STATE_ACTIVE;
+
+       return slim_do_transfer(sdev->ctrl, &txn);
+}
+
+/*
+ * slim_stream_enable() - Enable a prepared SLIMbus Stream
+ *
+ * @stream: instance of slim stream runtime to enable
+ *
+ * This API will enable all the ports and channels associated with
+ * SLIMbus stream
+ *
+ * Return: zero on success and error code on failure. From ASoC DPCM framework,
+ * this state is linked to trigger() start operation.
+ */
+int slim_stream_enable(struct slim_stream_runtime *stream)
+{
+       DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION,
+                               3, SLIM_LA_MANAGER, NULL);
+       struct slim_controller *ctrl = stream->dev->ctrl;
+       int ret, i;
+
+       if (ctrl->enable_stream) {
+               ret = ctrl->enable_stream(stream);
+               if (ret)
+                       return ret;
+
+               for (i = 0; i < stream->num_ports; i++)
+                       stream->ports[i].ch.state = SLIM_CH_STATE_ACTIVE;
+
+               return ret;
+       }
+
+       ret = slim_do_transfer(ctrl, &txn);
+       if (ret)
+               return ret;
+
+       /* define channels first before activating them */
+       for (i = 0; i < stream->num_ports; i++) {
+               struct slim_port *port = &stream->ports[i];
+
+               slim_define_channel(stream, port);
+               slim_define_channel_content(stream, port);
+       }
+
+       for (i = 0; i < stream->num_ports; i++) {
+               struct slim_port *port = &stream->ports[i];
+
+               slim_activate_channel(stream, port);
+               port->state = SLIM_PORT_CONFIGURED;
+       }
+       txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW;
+
+       return slim_do_transfer(ctrl, &txn);
+}
+EXPORT_SYMBOL_GPL(slim_stream_enable);
+
+/*
+ * slim_stream_disable() - Disable a SLIMbus Stream
+ *
+ * @stream: instance of slim stream runtime to disable
+ *
+ * This API will disable all the ports and channels associated with
+ * SLIMbus stream
+ *
+ * Return: zero on success and error code on failure. From ASoC DPCM framework,
+ * this state is linked to trigger() pause operation.
+ */
+int slim_stream_disable(struct slim_stream_runtime *stream)
+{
+       DEFINE_SLIM_BCAST_TXN(txn, SLIM_MSG_MC_BEGIN_RECONFIGURATION,
+                               3, SLIM_LA_MANAGER, NULL);
+       struct slim_controller *ctrl = stream->dev->ctrl;
+       int ret, i;
+
+       if (ctrl->disable_stream)
+               ctrl->disable_stream(stream);
+
+       ret = slim_do_transfer(ctrl, &txn);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < stream->num_ports; i++)
+               slim_deactivate_remove_channel(stream, &stream->ports[i]);
+
+       txn.mc = SLIM_MSG_MC_RECONFIGURE_NOW;
+
+       return slim_do_transfer(ctrl, &txn);
+}
+EXPORT_SYMBOL_GPL(slim_stream_disable);
+
+/*
+ * slim_stream_unprepare() - Un-prepare a SLIMbus Stream
+ *
+ * @stream: instance of slim stream runtime to unprepare
+ *
+ * This API will un allocate all the ports and channels associated with
+ * SLIMbus stream
+ *
+ * Return: zero on success and error code on failure. From ASoC DPCM framework,
+ * this state is linked to trigger() stop operation.
+ */
+int slim_stream_unprepare(struct slim_stream_runtime *stream)
+{
+       int i;
+
+       for (i = 0; i < stream->num_ports; i++)
+               slim_disconnect_port(stream, &stream->ports[i]);
+
+       kfree(stream->ports);
+       stream->ports = NULL;
+       stream->num_ports = 0;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(slim_stream_unprepare);
+
+/*
+ * slim_stream_free() - Free a SLIMbus Stream
+ *
+ * @stream: instance of slim stream runtime to free
+ *
+ * This API will un allocate all the memory associated with
+ * slim stream runtime, user is not allowed to make an dereference
+ * to stream after this call.
+ *
+ * Return: zero on success and error code on failure. From ASoC DPCM framework,
+ * this state is linked to shutdown() operation.
+ */
+int slim_stream_free(struct slim_stream_runtime *stream)
+{
+       struct slim_device *sdev = stream->dev;
+
+       spin_lock(&sdev->stream_list_lock);
+       list_del(&stream->node);
+       spin_unlock(&sdev->stream_list_lock);
+
+       kfree(stream->name);
+       kfree(stream);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(slim_stream_free);