new file mode 100644
@@ -0,0 +1,642 @@
+/*
+ * Copyright (C) STMicroelectronics SA 2015
+ * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
+ * for STMicroelectronics.
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/module.h>
+
+#include <sound/dmaengine_pcm.h>
+
+#include "uniperif.h"
+
+/*
+ * Max buffering use case identified:
+ * 3 periods of 2048 frames @ 192kHz, 32 bits, 10 ch
+ */
+#define STI_PLATFORM_PERIODS_BYTES_MAX 196608
+#define STI_PLATFORM_PERIODS_MAX 3
+#define STI_PLATFORM_BUFFER_BYTES_MAX (STI_PLATFORM_PERIODS_BYTES_MAX * \
+ STI_PLATFORM_PERIODS_MAX)
+
+#define priv_to_dai_data(priv, i) ((priv)->dai_data + i)
+
+struct sti_platform_dai {
+ int stream;
+ int (*init)(struct platform_device *pdev, struct device_node *node,
+ struct uniperif **uni, int idx);
+ int (*remove)(struct platform_device *pdev);
+ struct uniperif *uni;
+ struct snd_dmaengine_dai_dma_data dma_data;
+};
+
+struct sti_platform_dai uni_player = {
+ .stream = SNDRV_PCM_STREAM_PLAYBACK,
+ .init = uni_player_init,
+ .remove = uni_player_remove,
+};
+
+struct sti_platform_dai uni_reader = {
+ .stream = SNDRV_PCM_STREAM_CAPTURE,
+ .init = uni_reader_init,
+ .remove = uni_reader_remove,
+};
+
+struct sti_platform_data {
+ struct platform_device *pdev;
+ struct snd_soc_dai_driver *dai;
+ struct sti_platform_dai dai_data[]; /* dynamically allocated */
+};
+
+struct sti_pcm_dma_params {
+ struct dma_chan *dma_channel;
+ dma_cookie_t dma_cookie;
+ struct dma_slave_config slave_config;
+};
+
+/*
+ * sti_platform_dai_create_ctrl
+ * This function is used to create Ctrl associated to DAI but also pcm device.
+ * Request is done by front end to associate ctrl with pcm device id
+ */
+int sti_platform_dai_create_ctrl(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_dai *dai = rtd->cpu_dai;
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+ struct snd_kcontrol_new *ctrl;
+ int i, ret = 0;
+
+ for (i = 0; i < uni->num_ctrls; i++) {
+ ctrl = &uni->snd_ctrls[i];
+ ctrl->index = rtd->pcm->device;
+ ctrl->device = rtd->pcm->device;
+
+ ret = snd_ctl_add(dai->component->card->snd_card,
+ snd_ctl_new1(ctrl, uni));
+ if (ret < 0) {
+ dev_err(dai->dev, "%s: Failed to add %s: %d\n",
+ __func__, ctrl->name, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * DAI
+ */
+
+static int sti_platform_dai_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ if (uni->ops->open)
+ return uni->ops->open(uni);
+ return 0;
+}
+
+static void sti_platform_dai_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ if (uni->ops->close)
+ uni->ops->close(uni);
+}
+
+static int sti_platform_dai_prepare(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ if (uni->ops->prepare)
+ return uni->ops->prepare(uni, substream->runtime);
+
+ return 0;
+}
+
+static int sti_platform_dai_trigger(struct snd_pcm_substream *substream,
+ int cmd, struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ if (uni->ops->trigger)
+ return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_START);
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ if (uni->ops->trigger)
+ return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_STOP);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int sti_platform_dai_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params,
+ struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct snd_dmaengine_dai_dma_data *dma_data = &dai_data->dma_data;
+ int transfer_size;
+
+ transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;
+
+ dma_data = snd_soc_dai_get_dma_data(dai, substream);
+ dma_data->maxburst = transfer_size;
+
+ return 0;
+}
+
+static int sti_platform_dai_set_fmt(struct snd_soc_dai *dai,
+ unsigned int fmt)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+
+ dai_data->uni->daifmt = fmt;
+ return 0;
+}
+
+static int sti_platform_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id,
+ int div)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+
+ dai_data->uni->clk_div = div;
+
+ return 0;
+}
+
+static int sti_platform_dai_suspend(struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ if (uni->ops->trigger)
+ return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_SUSPEND);
+
+ return 0;
+}
+
+static int sti_platform_dai_resume(struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv, dai->id);
+ struct uniperif *uni = dai_data->uni;
+
+ if (uni->ops->trigger)
+ return uni->ops->trigger(uni, SNDRV_PCM_TRIGGER_RESUME);
+
+ return 0;
+}
+
+static int sti_platform_dai_probe(struct snd_soc_dai *dai)
+{
+ struct sti_platform_data *ptf_data = snd_soc_dai_get_drvdata(dai);
+
+ if (ptf_data->dai_data[dai->id].stream == SNDRV_PCM_STREAM_PLAYBACK)
+ dai->playback_dma_data = &ptf_data->dai_data[dai->id].dma_data;
+ else
+ dai->capture_dma_data = &ptf_data->dai_data[dai->id].dma_data;
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops sti_platform_dai_ops[] = {
+ {
+ .startup = sti_platform_dai_startup,
+ .shutdown = sti_platform_dai_shutdown,
+ .prepare = sti_platform_dai_prepare,
+ .trigger = sti_platform_dai_trigger,
+ .hw_params = sti_platform_dai_hw_params,
+ .set_fmt = sti_platform_dai_set_fmt,
+ .set_clkdiv = sti_platform_dai_set_clkdiv,
+ }
+};
+
+static const struct snd_soc_dai_driver sti_platform_dai_template = {
+ .probe = sti_platform_dai_probe,
+ .ops = sti_platform_dai_ops,
+ .suspend = sti_platform_dai_suspend,
+ .resume = sti_platform_dai_resume
+};
+
+static const struct snd_soc_component_driver sti_platform_dai_component = {
+ .name = "sti_cpu_dai",
+};
+
+/*
+ * PCM
+ */
+
+static void sti_dma_complete(void *arg)
+{
+ struct snd_pcm_substream *substream = arg;
+
+ snd_pcm_period_elapsed(substream);
+}
+
+static int sti_pcm_open(struct snd_pcm_substream *substream)
+{
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct sti_pcm_dma_params *params;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv,
+ rtd->cpu_dai->id);
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ ret = snd_soc_set_runtime_hwparams(substream, dai_data->uni->hw);
+ if (ret < 0) {
+ dev_err(rtd->dev, "error on FE hw_constraint\n");
+ return ret;
+ }
+
+ /* Ensure that buffer size is a multiple of period size */
+ ret = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (ret < 0) {
+ dev_err(rtd->dev, "Error: pcm hw constraints failed (%d)\n",
+ ret);
+ return ret;
+ }
+
+ /* update private data */
+ runtime->private_data = params;
+
+ return ret;
+}
+
+static int sti_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sti_pcm_dma_params *dma_params = runtime->private_data;
+ int size, ret = 0;
+
+ size = params_buffer_bytes(params);
+
+ /* Use PCM Lib */
+ ret = snd_pcm_lib_malloc_pages(substream, size);
+ if (ret < 0) {
+ dev_err(rtd->dev, "Can't allocate pages!\n");
+ return -ENOMEM;
+ }
+
+ return snd_dmaengine_pcm_prepare_slave_config(substream, params,
+ &dma_params->slave_config);
+}
+
+static int sti_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sti_pcm_dma_params *params = runtime->private_data;
+ struct sti_platform_data *priv = snd_soc_dai_get_drvdata(rtd->cpu_dai);
+ struct sti_platform_dai *dai_data = priv_to_dai_data(priv,
+ rtd->cpu_dai->id);
+ int ret;
+ char prop[5];
+
+ if (!params->dma_channel) {
+ if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ snprintf(prop, sizeof(prop), "tx-%d", rtd->cpu_dai->id);
+ else
+ snprintf(prop, sizeof(prop), "rx-%d", rtd->cpu_dai->id);
+
+ params->dma_channel =
+ dma_request_slave_channel(rtd->platform->dev, prop);
+ if (!params->dma_channel) {
+ dev_err(rtd->dev, "Failed to request DMA channel");
+ return -ENODEV;
+ }
+ }
+ ret = dmaengine_slave_config(params->dma_channel,
+ ¶ms->slave_config);
+ if (ret)
+ dev_err(rtd->dev, "Failed to configure DMA channel");
+
+ return ret;
+}
+
+static int sti_pcm_trigger(struct snd_pcm_substream *substream,
+ int cmd)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sti_pcm_dma_params *params = runtime->private_data;
+ enum dma_transfer_direction direction;
+ struct dma_async_tx_descriptor *desc;
+ unsigned long flags = DMA_CTRL_ACK;
+ int ret = 0;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ direction = snd_pcm_substream_to_dma_direction(substream);
+
+ desc = dmaengine_prep_dma_cyclic(
+ params->dma_channel, runtime->dma_addr,
+ snd_pcm_lib_buffer_bytes(substream),
+ snd_pcm_lib_period_bytes(substream),
+ direction, flags);
+ if (!desc) {
+ dev_err(rtd->dev, "Failed to prepare DMA descriptor");
+ return -ENOMEM;
+ }
+
+ /* Set the dma callback */
+ desc->callback = sti_dma_complete;
+ desc->callback_param = substream;
+
+ /* Submit dma descriptor */
+ params->dma_cookie = dmaengine_submit(desc);
+ dma_async_issue_pending(params->dma_channel);
+ break;
+
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ if (params->dma_channel)
+ ret = dmaengine_terminate_all(params->dma_channel);
+ break;
+ default:
+ dev_err(rtd->dev, "%s: ERROR: Invalid command in pcm trigger!\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static snd_pcm_uframes_t sti_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct sti_pcm_dma_params *prtd = runtime->private_data;
+ struct dma_tx_state state;
+ enum dma_status status;
+ unsigned int buf_size;
+ unsigned int pos = 0;
+
+ status = dmaengine_tx_status(prtd->dma_channel, prtd->dma_cookie,
+ &state);
+ if (status == DMA_IN_PROGRESS || status == DMA_PAUSED) {
+ buf_size = snd_pcm_lib_buffer_bytes(substream);
+ if (state.residue > 0 && state.residue <= buf_size)
+ pos = buf_size - state.residue;
+ }
+
+ return bytes_to_frames(substream->runtime, pos);
+}
+
+static int sti_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct sti_pcm_dma_params *params;
+
+ params = substream->runtime->private_data;
+ if (params->dma_channel) {
+ dma_release_channel(params->dma_channel);
+ params->dma_channel = NULL;
+ }
+ kfree(params);
+
+ return 0;
+}
+
+int sti_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_pcm *pcm = rtd->pcm;
+ struct snd_card *card = rtd->card->snd_card;
+ int ret = 0;
+
+ ret = sti_platform_dai_create_ctrl(rtd);
+ if (ret < 0)
+ return ret;
+ else
+ return snd_pcm_lib_preallocate_pages_for_all(
+ pcm, SNDRV_DMA_TYPE_DEV, card->dev,
+ STI_PLATFORM_BUFFER_BYTES_MAX,
+ STI_PLATFORM_BUFFER_BYTES_MAX);
+}
+
+static struct snd_pcm_ops sti_pcm_ops = {
+ .open = sti_pcm_open,
+ .close = sti_pcm_close,
+ .hw_params = sti_pcm_hw_params,
+ .prepare = sti_pcm_prepare,
+ .hw_free = snd_pcm_lib_free_pages,
+ .trigger = sti_pcm_trigger,
+ .pointer = sti_pcm_pointer
+};
+
+static struct snd_soc_platform_driver sti_platform_platform = {
+ .ops = &sti_pcm_ops,
+ .pcm_new = sti_pcm_new,
+};
+
+static int sti_platform_cpu_dai_of(struct device_node *node,
+ struct sti_platform_data *priv, int idx)
+{
+ const char *str;
+ int ret, i;
+ struct device *dev = &priv->pdev->dev;
+ struct sti_platform_dai *dai_data = &priv->dai_data[idx];
+ struct snd_soc_dai_driver *dai = &priv->dai[idx];
+ struct snd_soc_pcm_stream *stream;
+ struct uniperif *uni;
+ struct {
+ char *name;
+ struct sti_platform_dai *val;
+ } of_fmt_table[] = {
+ { "rx", &uni_reader },
+ { "tx", &uni_player },
+ };
+
+ *dai = sti_platform_dai_template;
+ ret = of_property_read_string(node, "dai-name", &str);
+ if (ret < 0) {
+ dev_err(dev, "%s: dai name missing.\n", __func__);
+ return -EINVAL;
+ }
+ dai->name = str;
+
+ ret = of_property_read_string_index(dev->of_node, "dma-names",
+ idx, &str);
+ if (ret == 0) {
+ ret = -EINVAL;
+ for (i = 0; i < ARRAY_SIZE(of_fmt_table); i++)
+ if (strncmp(str, of_fmt_table[i].name, 2) == 0) {
+ *dai_data = *of_fmt_table[i].val;
+ ret = 0;
+ break;
+ }
+ }
+ if (ret < 0) {
+ dev_err(dev, "%s: invalid dai-type.\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Initialise uniperif */
+ if (!dai_data->init) {
+ dev_err(dev, "%s: context data missing.\n", __func__);
+ return -EINVAL;
+ }
+
+ ret = dai_data->init(priv->pdev, node,
+ &dai_data->uni, idx);
+ if (ret < 0) {
+ dev_err(dev, "%s: ERROR: Fail to init uniperif ( %d)!\n",
+ __func__, ret);
+ return ret;
+ }
+
+ uni = dai_data->uni;
+
+ if (dai_data->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ stream = &dai->playback;
+ else
+ stream = &dai->capture;
+
+ stream->stream_name = dai->name;
+ stream->channels_min = uni->hw->channels_min;
+ stream->channels_max = uni->hw->channels_max;
+ stream->rates = uni->hw->rates;
+ stream->formats = uni->hw->formats;
+
+ /* DMA settings*/
+ dai_data->dma_data.addr = uni->fifo_phys_address;
+ dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+ return 0;
+}
+
+static int sti_platform_engine_probe(struct platform_device *pdev)
+{
+ /*
+ * Driver can not use snd_dmaengine_pcm_register.
+ * Reason is that DMA needs to load a firmware.
+ * This firmware is loaded on request_channel from file system.
+ * So can not be done in probe while alsa card enumerated before
+ * file system is mounted
+ */
+
+ struct sti_platform_data *priv;
+ struct device_node *node = pdev->dev.of_node;
+ int num_dais, ret;
+
+ /* Get the number of DAI links */
+ if (node && of_get_child_by_name(node, "cpu-dai"))
+ num_dais = of_get_child_count(node);
+ else
+ num_dais = 1;
+
+ /* Allocate the private data and the CPU_DAI array */
+ priv = devm_kzalloc(&pdev->dev,
+ sizeof(*priv) +
+ sizeof(struct sti_platform_dai) * num_dais,
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai) * num_dais,
+ GFP_KERNEL);
+ if (!priv->dai)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+
+ if (of_get_child_by_name(node, "cpu-dai")) {
+ struct device_node *np = NULL;
+ int i = 0;
+
+ for_each_child_of_node(node, np) {
+ ret = sti_platform_cpu_dai_of(np, priv, i);
+ if (ret < 0) {
+ of_node_put(np);
+ return ret;
+ }
+ i++;
+ }
+ }
+
+ dev_set_drvdata(&pdev->dev, priv);
+
+ ret = snd_soc_register_component(&pdev->dev,
+ &sti_platform_dai_component,
+ priv->dai, num_dais);
+ if (ret < 0)
+ return ret;
+
+ return snd_soc_register_platform(&pdev->dev, &sti_platform_platform);
+}
+
+static int sti_platform_engine_remove(struct platform_device *pdev)
+{
+ struct sti_platform_data *priv = dev_get_drvdata(&pdev->dev);
+ struct sti_platform_dai *dai_data = priv->dai_data;
+ struct device_node *node = pdev->dev.of_node;
+ int num_dais, idx;
+
+ /* Get the number of DAI links */
+ if (node && of_get_child_by_name(node, "cpu-dai"))
+ num_dais = of_get_child_count(node);
+ else
+ num_dais = 1;
+
+ for (idx = 0; idx < num_dais; idx++) {
+ dai_data->remove(pdev);
+ dai_data++;
+ }
+
+ snd_soc_unregister_platform(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id snd_soc_sti_match[] = {
+ { .compatible = "st,sti-audio-platform", },
+ {},
+};
+
+static struct platform_driver sti_platform_driver = {
+ .driver = {
+ .name = "sti-audio-platform",
+ .owner = THIS_MODULE,
+ .of_match_table = snd_soc_sti_match,
+ },
+ .probe = sti_platform_engine_probe,
+ .remove = sti_platform_engine_remove
+};
+module_platform_driver(sti_platform_driver);
+
+MODULE_DESCRIPTION("audio soc platform driver for STI platforms");
+MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
+MODULE_LICENSE("GPL v2");
Asoc Platform driver that manages uniperipheral DAIs and associated PCM stream. Signed-off-by: Arnaud Pouliquen <arnaud.pouliquen@st.com> --- sound/soc/sti/sti_platform.c | 642 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 642 insertions(+) create mode 100644 sound/soc/sti/sti_platform.c