blob: 25d4471c65433d46d98ca997693bb69cedae5690 [file] [log] [blame]
/*
* Copyright (c) 2016, Intel Corporation
* All rights reserved.
*
* 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 the 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.
*
* Author: Liam Girdwood <liam.r.girdwood@linux.intel.com>
* Keyon Jie <yang.jie@linux.intel.com>
*/
#include <stdint.h>
#include <stddef.h>
#include <errno.h>
#include <sof/sof.h>
#include <sof/lock.h>
#include <sof/list.h>
#include <sof/stream.h>
#include <sof/alloc.h>
#include <sof/ipc.h>
#include <sof/debug.h>
#include <platform/platform.h>
#include <sof/audio/component.h>
#include <sof/audio/pipeline.h>
#include <sof/audio/buffer.h>
/*
* Components, buffers and pipelines all use the same set of monotonic ID
* numbers passed in by the host. They are stored in different lists, hence
* more than 1 list may need to be searched for the corresponding component.
*/
struct ipc_comp_dev *ipc_get_comp(struct ipc *ipc, uint32_t id)
{
struct ipc_comp_dev *icd;
struct list_item *clist;
list_for_item(clist, &ipc->comp_list) {
icd = container_of(clist, struct ipc_comp_dev, list);
switch (icd->type) {
case COMP_TYPE_COMPONENT:
if (icd->cd->comp.id == id)
return icd;
break;
case COMP_TYPE_BUFFER:
if (icd->cb->ipc_buffer.comp.id == id)
return icd;
break;
case COMP_TYPE_PIPELINE:
if (icd->pipeline->ipc_pipe.comp_id == id)
return icd;
break;
default:
break;
}
}
return NULL;
}
int ipc_get_posn_offset(struct ipc *ipc, struct pipeline *pipe)
{
int i;
uint32_t posn_size = sizeof(struct sof_ipc_stream_posn);
for (i = 0; i < PLATFORM_MAX_STREAMS; i++) {
if (ipc->posn_map[i] == pipe)
return pipe->posn_offset;
}
for (i = 0; i < PLATFORM_MAX_STREAMS; i++) {
if (ipc->posn_map[i] == NULL) {
ipc->posn_map[i] = pipe;
pipe->posn_offset = i * posn_size;
return pipe->posn_offset;
}
}
return -EINVAL;
}
int ipc_comp_new(struct ipc *ipc, struct sof_ipc_comp *comp)
{
struct comp_dev *cd;
struct ipc_comp_dev *icd;
int ret = 0;
/* check whether component already exists */
icd = ipc_get_comp(ipc, comp->id);
if (icd != NULL) {
trace_ipc_error("eCe");
trace_error_value(comp->id);
return -EINVAL;
}
/* create component */
cd = comp_new(comp);
if (cd == NULL) {
trace_ipc_error("eCn");
return -EINVAL;
}
/* allocate the IPC component container */
icd = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
sizeof(struct ipc_comp_dev));
if (icd == NULL) {
trace_ipc_error("eCm");
rfree(cd);
return -ENOMEM;
}
icd->cd = cd;
icd->type = COMP_TYPE_COMPONENT;
/* add new component to the list */
list_item_append(&icd->list, &ipc->comp_list);
return ret;
}
int ipc_comp_free(struct ipc *ipc, uint32_t comp_id)
{
struct ipc_comp_dev *icd;
/* check whether component exists */
icd = ipc_get_comp(ipc, comp_id);
if (icd == NULL)
return -ENODEV;
/* free component and remove from list */
comp_free(icd->cd);
list_item_del(&icd->list);
rfree(icd);
return 0;
}
int ipc_buffer_new(struct ipc *ipc, struct sof_ipc_buffer *desc)
{
struct ipc_comp_dev *ibd;
struct comp_buffer *buffer;
int ret = 0;
/* check whether buffer already exists */
ibd = ipc_get_comp(ipc, desc->comp.id);
if (ibd != NULL) {
trace_ipc_error("eBe");
trace_error_value(desc->comp.id);
return -EINVAL;
}
/* register buffer with pipeline */
buffer = buffer_new(desc);
if (buffer == NULL) {
trace_ipc_error("eBn");
rfree(ibd);
return -ENOMEM;
}
ibd = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
sizeof(struct ipc_comp_dev));
if (ibd == NULL) {
rfree(buffer);
return -ENOMEM;
}
ibd->cb = buffer;
ibd->type = COMP_TYPE_BUFFER;
/* add new buffer to the list */
list_item_append(&ibd->list, &ipc->comp_list);
return ret;
}
int ipc_buffer_free(struct ipc *ipc, uint32_t buffer_id)
{
struct ipc_comp_dev *ibd;
/* check whether buffer exists */
ibd = ipc_get_comp(ipc, buffer_id);
if (ibd == NULL)
return -ENODEV;
/* free buffer and remove from list */
buffer_free(ibd->cb);
list_item_del(&ibd->list);
rfree(ibd);
return 0;
}
int ipc_comp_connect(struct ipc *ipc,
struct sof_ipc_pipe_comp_connect *connect)
{
struct ipc_comp_dev *icd_source;
struct ipc_comp_dev *icd_sink;
/* check whether the components already exist */
icd_source = ipc_get_comp(ipc, connect->source_id);
if (icd_source == NULL) {
trace_ipc_error("eCr");
trace_error_value(connect->source_id);
return -EINVAL;
}
icd_sink = ipc_get_comp(ipc, connect->sink_id);
if (icd_sink == NULL) {
trace_ipc_error("eCn");
trace_error_value(connect->sink_id);
return -EINVAL;
}
/* check source and sink types */
if (icd_source->type == COMP_TYPE_BUFFER &&
icd_sink->type == COMP_TYPE_COMPONENT)
return pipeline_buffer_connect(icd_source->pipeline,
icd_source->cb, icd_sink->cd);
else if (icd_source->type == COMP_TYPE_COMPONENT &&
icd_sink->type == COMP_TYPE_BUFFER)
return pipeline_comp_connect(icd_source->pipeline,
icd_source->cd, icd_sink->cb);
else {
trace_ipc_error("eCt");
trace_error_value(connect->source_id);
trace_error_value(connect->sink_id);
return -EINVAL;
}
}
int ipc_pipeline_new(struct ipc *ipc,
struct sof_ipc_pipe_new *pipe_desc)
{
struct ipc_comp_dev *ipc_pipe;
struct pipeline *pipe;
struct ipc_comp_dev *icd;
/* check whether the pipeline already exists */
ipc_pipe = ipc_get_comp(ipc, pipe_desc->comp_id);
if (ipc_pipe != NULL) {
trace_ipc_error("ePi");
trace_error_value(pipe_desc->comp_id);
return -EINVAL;
}
/* find the scheduling component */
icd = ipc_get_comp(ipc, pipe_desc->sched_id);
if (icd == NULL) {
trace_ipc_error("ePs");
trace_error_value(pipe_desc->sched_id);
return -EINVAL;
}
if (icd->type != COMP_TYPE_COMPONENT) {
trace_ipc_error("ePc");
return -EINVAL;
}
/* create the pipeline */
pipe = pipeline_new(pipe_desc, icd->cd);
if (pipe == NULL) {
trace_ipc_error("ePn");
return -ENOMEM;
}
/* allocate the IPC pipeline container */
ipc_pipe = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM,
sizeof(struct ipc_comp_dev));
if (ipc_pipe == NULL) {
pipeline_free(pipe);
return -ENOMEM;
}
ipc_pipe->pipeline = pipe;
ipc_pipe->type = COMP_TYPE_PIPELINE;
/* add new pipeline to the list */
list_item_append(&ipc_pipe->list, &ipc->comp_list);
return 0;
}
int ipc_pipeline_free(struct ipc *ipc, uint32_t comp_id)
{
struct ipc_comp_dev *ipc_pipe;
int ret;
/* check whether pipeline exists */
ipc_pipe = ipc_get_comp(ipc, comp_id);
if (ipc_pipe == NULL)
return -ENODEV;
/* free buffer and remove from list */
ret = pipeline_free(ipc_pipe->pipeline);
if (ret < 0) {
trace_ipc_error("ePf");
return ret;
}
list_item_del(&ipc_pipe->list);
rfree(ipc_pipe);
return 0;
}
int ipc_pipeline_complete(struct ipc *ipc, uint32_t comp_id)
{
struct ipc_comp_dev *ipc_pipe;
/* check whether pipeline exists */
ipc_pipe = ipc_get_comp(ipc, comp_id);
if (ipc_pipe == NULL)
return -EINVAL;
/* free buffer and remove from list */
return pipeline_complete(ipc_pipe->pipeline);
}
int ipc_comp_dai_config(struct ipc *ipc, struct sof_ipc_dai_config *config)
{
struct sof_ipc_comp_dai *dai;
struct ipc_comp_dev *icd;
struct list_item *clist;
struct comp_dev *dev;
int ret = 0;
/* for each component */
list_for_item(clist, &ipc->comp_list) {
icd = container_of(clist, struct ipc_comp_dev, list);
switch (icd->type) {
case COMP_TYPE_COMPONENT:
/* make sure we only config DAI comps */
switch (icd->cd->comp.type) {
case SOF_COMP_DAI:
case SOF_COMP_SG_DAI:
dev = icd->cd;
dai = (struct sof_ipc_comp_dai *)&dev->comp;
/*
* set config if comp dai_index matches
* config dai_index.
*/
if (dai->dai_index == config->dai_index &&
dai->type == config->type) {
ret = comp_dai_config(dev, config);
if (ret < 0) {
trace_ipc_error("eCD");
return ret;
}
}
break;
default:
break;
}
break;
/* ignore non components */
default:
break;
}
}
return ret;
}
#ifdef CONFIG_HOST_PTABLE
/*
* Parse the host page tables and create the audio DMA SG configuration
* for host audio DMA buffer. This involves creating a dma_sg_elem for each
* page table entry and adding each elem to a list in struct dma_sg_config.
*/
int ipc_parse_page_descriptors(uint8_t *page_table,
struct sof_ipc_host_buffer *ring,
struct list_item *elem_list,
uint32_t direction)
{
int i;
uint32_t idx;
uint32_t phy_addr;
struct dma_sg_elem *e;
/* the ring size may be not multiple of the page size, the last
* page may be not full used. The used size should be in range
* of (ring->pages - 1, ring->pages] * PAGES.
*/
if ((ring->size <= HOST_PAGE_SIZE * (ring->pages - 1)) ||
(ring->size > HOST_PAGE_SIZE * ring->pages)) {
/* error buffer size */
trace_ipc_error("eBs");
return -EINVAL;
}
for (i = 0; i < ring->pages; i++) {
idx = (((i << 2) + i)) >> 1;
phy_addr = page_table[idx] | (page_table[idx + 1] << 8)
| (page_table[idx + 2] << 16);
if (i & 0x1)
phy_addr <<= 8;
else
phy_addr <<= 12;
phy_addr &= 0xfffff000;
/* allocate new host DMA elem and add it to our list */
e = rzalloc(RZONE_RUNTIME, SOF_MEM_CAPS_RAM, sizeof(*e));
if (!e)
return -ENOMEM;
if (direction == SOF_IPC_STREAM_PLAYBACK)
e->src = phy_addr;
else
e->dest = phy_addr;
/* the last page may be not full used */
if (i == (ring->pages - 1))
e->size = ring->size - HOST_PAGE_SIZE * i;
else
e->size = HOST_PAGE_SIZE;
list_item_append(&e->list, elem_list);
}
return 0;
}
static void dma_complete(void *data, uint32_t type, struct dma_sg_elem *next)
{
completion_t *complete = data;
if (type == DMA_IRQ_TYPE_LLIST)
wait_completed(complete);
}
/*
* Copy the audio buffer page tables from the host to the DSP max of 4K.
*/
int ipc_get_page_descriptors(struct dma *dmac, uint8_t *page_table,
struct sof_ipc_host_buffer *ring)
{
struct dma_sg_config config;
struct dma_sg_elem elem;
completion_t complete;
int chan;
int ret = 0;
/* get DMA channel from DMAC */
chan = dma_channel_get(dmac, 0);
if (chan < 0) {
trace_ipc_error("ePC");
return chan;
}
/* set up DMA configuration */
config.direction = DMA_DIR_HMEM_TO_LMEM;
config.src_width = sizeof(uint32_t);
config.dest_width = sizeof(uint32_t);
config.cyclic = 0;
list_init(&config.elem_list);
/* set up DMA descriptor */
elem.dest = (uint32_t)page_table;
elem.src = ring->phy_addr;
/* source buffer size is always PAGE_SIZE bytes */
/* 20 bits for each page, round up to 32 */
elem.size = (ring->pages * 5 * 16 + 31) / 32;
list_item_prepend(&elem.list, &config.elem_list);
ret = dma_set_config(dmac, chan, &config);
if (ret < 0) {
trace_ipc_error("ePs");
goto out;
}
/* set up callback */
dma_set_cb(dmac, chan, DMA_IRQ_TYPE_LLIST, dma_complete, &complete);
wait_init(&complete);
/* start the copy of page table to DSP */
ret = dma_start(dmac, chan);
if (ret < 0) {
trace_ipc_error("ePt");
goto out;
}
/* wait for DMA to complete */
complete.timeout = PLATFORM_HOST_DMA_TIMEOUT;
ret = wait_for_completion_timeout(&complete);
/* compressed page tables now in buffer at _ipc->page_table */
out:
dma_channel_put(dmac, chan);
return ret;
}
#endif
int ipc_init(struct sof *sof)
{
int i;
trace_ipc("IPI");
/* init ipc data */
sof->ipc = rzalloc(RZONE_SYS, SOF_MEM_CAPS_RAM, sizeof(*sof->ipc));
sof->ipc->comp_data = rzalloc(RZONE_SYS, SOF_MEM_CAPS_RAM,
SOF_IPC_MSG_MAX_SIZE);
sof->ipc->dmat = sof->dmat;
for (i = 0; i < PLATFORM_MAX_STREAMS; i++)
sof->ipc->posn_map[i] = NULL;
list_init(&sof->ipc->comp_list);
return platform_ipc_init(sof->ipc);
}