diff options
-rw-r--r-- | include/uapi/linux/mxc_asrc.h | 126 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_asrc.c | 11 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_asrc.h | 99 | ||||
-rw-r--r-- | sound/soc/fsl/fsl_asrc_m2m.c | 956 |
4 files changed, 1096 insertions, 96 deletions
diff --git a/include/uapi/linux/mxc_asrc.h b/include/uapi/linux/mxc_asrc.h new file mode 100644 index 000000000000..7a2b7dfcdc5b --- /dev/null +++ b/include/uapi/linux/mxc_asrc.h @@ -0,0 +1,126 @@ +/* + * Copyright 2008-2014 Freescale Semiconductor, Inc. All Rights Reserved. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * + * @file mxc_asrc.h + * + * @brief i.MX Asynchronous Sample Rate Converter + * + * @ingroup Audio + */ + +#ifndef __MXC_ASRC_UAPI_H__ +#define __MXC_ASRC_UAPI_H__ + +#define ASRC_IOC_MAGIC 'C' + +#define ASRC_REQ_PAIR _IOWR(ASRC_IOC_MAGIC, 0, struct asrc_req) +#define ASRC_CONFIG_PAIR _IOWR(ASRC_IOC_MAGIC, 1, struct asrc_config) +#define ASRC_RELEASE_PAIR _IOW(ASRC_IOC_MAGIC, 2, enum asrc_pair_index) +#define ASRC_CONVERT _IOW(ASRC_IOC_MAGIC, 3, struct asrc_convert_buffer) +#define ASRC_START_CONV _IOW(ASRC_IOC_MAGIC, 4, enum asrc_pair_index) +#define ASRC_STOP_CONV _IOW(ASRC_IOC_MAGIC, 5, enum asrc_pair_index) +#define ASRC_STATUS _IOW(ASRC_IOC_MAGIC, 6, struct asrc_status_flags) +#define ASRC_FLUSH _IOW(ASRC_IOC_MAGIC, 7, enum asrc_pair_index) + +enum asrc_pair_index { + ASRC_INVALID_PAIR = -1, + ASRC_PAIR_A = 0, + ASRC_PAIR_B = 1, + ASRC_PAIR_C = 2, +}; + +#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) + +enum asrc_inclk { + INCLK_NONE = 0x03, + INCLK_ESAI_RX = 0x00, + INCLK_SSI1_RX = 0x01, + INCLK_SSI2_RX = 0x02, + INCLK_SSI3_RX = 0x07, + INCLK_SPDIF_RX = 0x04, + INCLK_MLB_CLK = 0x05, + INCLK_PAD = 0x06, + INCLK_ESAI_TX = 0x08, + INCLK_SSI1_TX = 0x09, + INCLK_SSI2_TX = 0x0a, + INCLK_SSI3_TX = 0x0b, + INCLK_SPDIF_TX = 0x0c, + INCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_outclk { + OUTCLK_NONE = 0x03, + OUTCLK_ESAI_TX = 0x00, + OUTCLK_SSI1_TX = 0x01, + OUTCLK_SSI2_TX = 0x02, + OUTCLK_SSI3_TX = 0x07, + OUTCLK_SPDIF_TX = 0x04, + OUTCLK_MLB_CLK = 0x05, + OUTCLK_PAD = 0x06, + OUTCLK_ESAI_RX = 0x08, + OUTCLK_SSI1_RX = 0x09, + OUTCLK_SSI2_RX = 0x0a, + OUTCLK_SSI3_RX = 0x0b, + OUTCLK_SPDIF_RX = 0x0c, + OUTCLK_ASRCK1_CLK = 0x0f, +}; + +enum asrc_word_width { + ASRC_WIDTH_24_BIT = 0, + ASRC_WIDTH_16_BIT = 1, + ASRC_WIDTH_8_BIT = 2, +}; + +struct asrc_config { + enum asrc_pair_index pair; + unsigned int channel_num; + unsigned int buffer_num; + unsigned int dma_buffer_size; + unsigned int input_sample_rate; + unsigned int output_sample_rate; + enum asrc_word_width input_word_width; + enum asrc_word_width output_word_width; + enum asrc_inclk inclk; + enum asrc_outclk outclk; +}; + +struct asrc_req { + unsigned int chn_num; + enum asrc_pair_index index; +}; + +struct asrc_querybuf { + unsigned int buffer_index; + unsigned int input_length; + unsigned int output_length; + unsigned long input_offset; + unsigned long output_offset; +}; + +struct asrc_convert_buffer { + void *input_buffer_vaddr; + void *output_buffer_vaddr; + unsigned int input_buffer_length; + unsigned int output_buffer_length; +}; + +struct asrc_status_flags { + enum asrc_pair_index index; + unsigned int overload_error; +}; + +enum asrc_error_status { + ASRC_TASK_Q_OVERLOAD = 0x01, + ASRC_OUTPUT_TASK_OVERLOAD = 0x02, + ASRC_INPUT_TASK_OVERLOAD = 0x04, + ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, + ASRC_INPUT_BUFFER_UNDERRUN = 0x10, +}; +#endif/* __MXC_ASRC_UAPI_H__ */ diff --git a/sound/soc/fsl/fsl_asrc.c b/sound/soc/fsl/fsl_asrc.c index 806d39927318..77ab051754be 100644 --- a/sound/soc/fsl/fsl_asrc.c +++ b/sound/soc/fsl/fsl_asrc.c @@ -729,6 +729,8 @@ static const struct regmap_config fsl_asrc_regmap_config = { .cache_type = REGCACHE_FLAT, }; +#include "fsl_asrc_m2m.c" + /** * Initialize ASRC registers with a default configurations */ @@ -933,6 +935,12 @@ static int fsl_asrc_probe(struct platform_device *pdev) return ret; } + ret = fsl_asrc_m2m_init(asrc_priv); + if (ret) { + dev_err(&pdev->dev, "failed to init m2m device %d\n", ret); + return ret; + } + return 0; } @@ -994,6 +1002,8 @@ static int fsl_asrc_suspend(struct device *dev) { struct fsl_asrc *asrc_priv = dev_get_drvdata(dev); + fsl_asrc_m2m_suspend(asrc_priv); + regmap_read(asrc_priv->regmap, REG_ASRCFG, &asrc_priv->regcache_cfg); @@ -1043,6 +1053,7 @@ MODULE_DEVICE_TABLE(of, fsl_asrc_ids); static struct platform_driver fsl_asrc_driver = { .probe = fsl_asrc_probe, + .remove = fsl_asrc_m2m_remove, .driver = { .name = "fsl-asrc", .of_match_table = fsl_asrc_ids, diff --git a/sound/soc/fsl/fsl_asrc.h b/sound/soc/fsl/fsl_asrc.h index 0f163abe4ba3..25b35b6676d1 100644 --- a/sound/soc/fsl/fsl_asrc.h +++ b/sound/soc/fsl/fsl_asrc.h @@ -13,6 +13,8 @@ #ifndef _FSL_ASRC_H #define _FSL_ASRC_H +#include <uapi/linux/mxc_asrc.h> + #define IN 0 #define OUT 1 @@ -23,6 +25,7 @@ #define ASRC_FIFO_THRESHOLD_MAX 63 #define ASRC_DMA_BUFFER_SIZE (1024 * 48 * 4) #define ASRC_MAX_BUFFER_SIZE (1024 * 48) +#define ASRC_OUTPUT_LAST_SAMPLE_MAX 32 #define ASRC_OUTPUT_LAST_SAMPLE 8 #define IDEAL_RATIO_RATE 1000000 @@ -286,104 +289,8 @@ #define ASRMCR1i_OW16_MASK (1 << ASRMCR1i_OW16_SHIFT) #define ASRMCR1i_OW16(v) ((v) << ASRMCR1i_OW16_SHIFT) - -enum asrc_pair_index { - ASRC_INVALID_PAIR = -1, - ASRC_PAIR_A = 0, - ASRC_PAIR_B = 1, - ASRC_PAIR_C = 2, -}; - -#define ASRC_PAIR_MAX_NUM (ASRC_PAIR_C + 1) - -enum asrc_inclk { - INCLK_NONE = 0x03, - INCLK_ESAI_RX = 0x00, - INCLK_SSI1_RX = 0x01, - INCLK_SSI2_RX = 0x02, - INCLK_SSI3_RX = 0x07, - INCLK_SPDIF_RX = 0x04, - INCLK_MLB_CLK = 0x05, - INCLK_PAD = 0x06, - INCLK_ESAI_TX = 0x08, - INCLK_SSI1_TX = 0x09, - INCLK_SSI2_TX = 0x0a, - INCLK_SSI3_TX = 0x0b, - INCLK_SPDIF_TX = 0x0c, - INCLK_ASRCK1_CLK = 0x0f, -}; - -enum asrc_outclk { - OUTCLK_NONE = 0x03, - OUTCLK_ESAI_TX = 0x00, - OUTCLK_SSI1_TX = 0x01, - OUTCLK_SSI2_TX = 0x02, - OUTCLK_SSI3_TX = 0x07, - OUTCLK_SPDIF_TX = 0x04, - OUTCLK_MLB_CLK = 0x05, - OUTCLK_PAD = 0x06, - OUTCLK_ESAI_RX = 0x08, - OUTCLK_SSI1_RX = 0x09, - OUTCLK_SSI2_RX = 0x0a, - OUTCLK_SSI3_RX = 0x0b, - OUTCLK_SPDIF_RX = 0x0c, - OUTCLK_ASRCK1_CLK = 0x0f, -}; - #define ASRC_CLK_MAX_NUM 16 -enum asrc_word_width { - ASRC_WIDTH_24_BIT = 0, - ASRC_WIDTH_16_BIT = 1, - ASRC_WIDTH_8_BIT = 2, -}; - -struct asrc_config { - enum asrc_pair_index pair; - unsigned int channel_num; - unsigned int buffer_num; - unsigned int dma_buffer_size; - unsigned int input_sample_rate; - unsigned int output_sample_rate; - enum asrc_word_width input_word_width; - enum asrc_word_width output_word_width; - enum asrc_inclk inclk; - enum asrc_outclk outclk; -}; - -struct asrc_req { - unsigned int chn_num; - enum asrc_pair_index index; -}; - -struct asrc_querybuf { - unsigned int buffer_index; - unsigned int input_length; - unsigned int output_length; - unsigned long input_offset; - unsigned long output_offset; -}; - -struct asrc_convert_buffer { - void *input_buffer_vaddr; - void *output_buffer_vaddr; - unsigned int input_buffer_length; - unsigned int output_buffer_length; -}; - -struct asrc_status_flags { - enum asrc_pair_index index; - unsigned int overload_error; -}; - -enum asrc_error_status { - ASRC_TASK_Q_OVERLOAD = 0x01, - ASRC_OUTPUT_TASK_OVERLOAD = 0x02, - ASRC_INPUT_TASK_OVERLOAD = 0x04, - ASRC_OUTPUT_BUFFER_OVERFLOW = 0x08, - ASRC_INPUT_BUFFER_UNDERRUN = 0x10, -}; - struct dma_block { dma_addr_t dma_paddr; void *dma_vaddr; diff --git a/sound/soc/fsl/fsl_asrc_m2m.c b/sound/soc/fsl/fsl_asrc_m2m.c new file mode 100644 index 000000000000..916e33e017d6 --- /dev/null +++ b/sound/soc/fsl/fsl_asrc_m2m.c @@ -0,0 +1,956 @@ +/* + * Freescale ASRC Memory to Memory (M2M) driver + * + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#define FSL_ASRC_INPUTFIFO_WML 0x4 +#define FSL_ASRC_OUTPUTFIFO_WML 0x2 + +#define DIR_STR(dir) dir == IN ? "in" : "out" + +struct fsl_asrc_m2m { + struct fsl_asrc_pair *pair; + struct completion complete[2]; + struct dma_block dma_block[2]; + unsigned int pair_hold; + unsigned int asrc_active; + unsigned int sg_nodes[2]; + struct scatterlist sg[2][4]; + + enum asrc_word_width word_width[2]; + unsigned int rate[2]; + unsigned int last_period_size; + u32 watermark[2]; + spinlock_t lock; +}; + +static struct miscdevice asrc_miscdev = { + .name = "mxc_asrc", + .minor = MISC_DYNAMIC_MINOR, +}; + +static void fsl_asrc_get_status(struct fsl_asrc_pair *pair, + struct asrc_status_flags *flags) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned long lock_flags; + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + + flags->overload_error = pair->error; + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); +} + +#define ASRC_xPUT_DMA_CALLBACK(dir) \ + ((dir == IN) ? fsl_asrc_input_dma_callback : fsl_asrc_output_dma_callback) + +static void fsl_asrc_input_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[IN]); +} + +static void fsl_asrc_output_dma_callback(void *data) +{ + struct fsl_asrc_pair *pair = (struct fsl_asrc_pair *)data; + struct fsl_asrc_m2m *m2m = pair->private; + + complete(&m2m->complete[OUT]); +} + +static unsigned int fsl_asrc_get_output_FIFO_size(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + u32 val; + + regmap_read(asrc_priv->regmap, REG_ASRFST(index), &val); + + val &= ASRFSTi_OUTPUT_FIFO_MASK; + + return val >> ASRFSTi_OUTPUT_FIFO_SHIFT; +} + +static void fsl_asrc_read_last_FIFO(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct dma_block *output = &m2m->dma_block[OUT]; + u32 i, reg, size, t_size = 0; + u32 *reg24 = NULL; + u16 *reg16 = NULL; + + if (m2m->word_width[OUT] == ASRC_WIDTH_24_BIT) + reg24 = output->dma_vaddr + output->length; + else + reg16 = output->dma_vaddr + output->length; + +retry: + size = fsl_asrc_get_output_FIFO_size(pair); + + for (i = 0; i < size * pair->channels; i++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + if (reg24) { + *(reg24) = reg; + reg24++; + } else { + *(reg16) = (u16)reg; + reg16++; + } + } + t_size += size; + + if (size) + goto retry; + + if (t_size > m2m->last_period_size) + t_size = m2m->last_period_size; + + if (reg24) + output->length += t_size * pair->channels * 4; + else + output->length += t_size * pair->channels * 2; +} + +static int fsl_allocate_dma_buf(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct dma_block *input = &m2m->dma_block[IN]; + struct dma_block *output = &m2m->dma_block[OUT]; + enum asrc_pair_index index = pair->index; + + input->dma_vaddr = kzalloc(input->length, GFP_KERNEL); + if (!input->dma_vaddr) { + pair_err("failed to allocate input DMA buffer\n"); + return -ENOMEM; + } + input->dma_paddr = virt_to_dma(NULL, input->dma_vaddr); + + output->dma_vaddr = kzalloc(output->length, GFP_KERNEL); + if (!output->dma_vaddr) { + pair_err("failed to allocate output DMA buffer\n"); + goto exit; + } + output->dma_paddr = virt_to_dma(NULL, output->dma_vaddr); + + return 0; + +exit: + kfree(input->dma_vaddr); + + return -ENOMEM; +} + +static int fsl_asrc_dmaconfig(struct fsl_asrc_pair *pair, struct dma_chan *chan, + u32 dma_addr, void *buf_addr, u32 buf_len, + bool dir, enum asrc_word_width word_width) +{ + struct dma_async_tx_descriptor *desc = pair->desc[dir]; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + unsigned int sg_nent = m2m->sg_nodes[dir]; + enum asrc_pair_index index = pair->index; + struct scatterlist *sg = m2m->sg[dir]; + struct dma_slave_config slave_config; + enum dma_slave_buswidth buswidth; + int ret, i; + + switch (word_width) { + case ASRC_WIDTH_16_BIT: + buswidth = DMA_SLAVE_BUSWIDTH_2_BYTES; + break; + case ASRC_WIDTH_24_BIT: + buswidth = DMA_SLAVE_BUSWIDTH_4_BYTES; + break; + default: + pair_err("invalid word width\n"); + return -EINVAL; + } + + if (dir == IN) { + slave_config.direction = DMA_MEM_TO_DEV; + slave_config.dst_addr = dma_addr; + slave_config.dst_addr_width = buswidth; + slave_config.dst_maxburst = + m2m->watermark[IN] * pair->channels; + } else { + slave_config.direction = DMA_DEV_TO_MEM; + slave_config.src_addr = dma_addr; + slave_config.src_addr_width = buswidth; + slave_config.src_maxburst = + m2m->watermark[OUT] * pair->channels; + } + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) { + pair_err("failed to config dmaengine for %sput task: %d\n", + DIR_STR(dir), ret); + return -EINVAL; + } + + sg_init_table(sg, sg_nent); + switch (sg_nent) { + case 1: + sg_init_one(sg, buf_addr, buf_len); + break; + case 2: + case 3: + case 4: + for (i = 0; i < (sg_nent - 1); i++) + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + ASRC_MAX_BUFFER_SIZE); + + sg_set_buf(&sg[i], buf_addr + i * ASRC_MAX_BUFFER_SIZE, + buf_len - ASRC_MAX_BUFFER_SIZE * i); + break; + default: + pair_err("invalid input DMA nodes number: %d\n", sg_nent); + return -EINVAL; + } + + ret = dma_map_sg(NULL, sg, sg_nent, slave_config.direction); + if (ret != sg_nent) { + pair_err("failed to map DMA sg for %sput task\n", DIR_STR(dir)); + return -EINVAL; + } + + desc = dmaengine_prep_slave_sg(chan, sg, sg_nent, + slave_config.direction, DMA_PREP_INTERRUPT); + if (!desc) { + pair_err("failed to prepare dmaengine for %sput task\n", + DIR_STR(dir)); + return -EINVAL; + } + + pair->desc[dir] = desc; + pair->desc[dir]->callback = ASRC_xPUT_DMA_CALLBACK(dir); + + desc->callback = ASRC_xPUT_DMA_CALLBACK(dir); + desc->callback_param = pair; + + return 0; +} + +static int fsl_asrc_prepare_io_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf, bool dir) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + unsigned int *dma_len = &m2m->dma_block[dir].length; + enum asrc_word_width width = m2m->word_width[dir]; + void *dma_vaddr = m2m->dma_block[dir].dma_vaddr; + struct dma_chan *dma_chan = pair->dma_chan[dir]; + unsigned int buf_len, wm = m2m->watermark[dir]; + unsigned int *sg_nodes = &m2m->sg_nodes[dir]; + unsigned int last_period_size = m2m->last_period_size; + enum asrc_pair_index index = pair->index; + u32 word_size, fifo_addr; + void __user *buf_vaddr; + + /* Clean the DMA buffer */ + memset(dma_vaddr, 0, ASRC_DMA_BUFFER_SIZE); + + if (dir == IN) { + buf_vaddr = (void __user *)pbuf->input_buffer_vaddr; + buf_len = pbuf->input_buffer_length; + } else { + buf_vaddr = (void __user *)pbuf->output_buffer_vaddr; + buf_len = pbuf->output_buffer_length; + } + + if (width == ASRC_WIDTH_24_BIT) + word_size = 4; + else + word_size = 2; + + if (buf_len < word_size * pair->channels * wm || + buf_len > ASRC_DMA_BUFFER_SIZE || + (dir == OUT && buf_len < word_size * pair->channels * last_period_size)) { + pair_err("%sput buffer size is error: [%d]\n", + DIR_STR(dir), buf_len); + return -EINVAL; + } + + /* Copy origin data into input buffer */ + if (dir == IN && copy_from_user(dma_vaddr, buf_vaddr, buf_len)) + return -EFAULT; + + *dma_len = buf_len; + if (dir == OUT) + *dma_len -= last_period_size * word_size * pair->channels; + + *sg_nodes = *dma_len / ASRC_MAX_BUFFER_SIZE + 1; + + fifo_addr = asrc_priv->paddr + REG_ASRDx(dir, index); + + return fsl_asrc_dmaconfig(pair, dma_chan, fifo_addr, dma_vaddr, + *dma_len, dir, width); +} + +static int fsl_asrc_prepare_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + int ret; + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, IN); + if (ret) { + pair_err("failed to prepare input buffer: %d\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_io_buffer(pair, pbuf, OUT); + if (ret) { + pair_err("failed to prepare output buffer: %d\n", ret); + return ret; + } + + return 0; +} + +int fsl_asrc_process_buffer_pre(struct completion *complete, + enum asrc_pair_index index, bool dir) +{ + if (!wait_for_completion_interruptible_timeout(complete, 10 * HZ)) { + pr_err("%sput DMA task timeout\n", DIR_STR(dir)); + return -ETIME; + } else if (signal_pending(current)) { + pr_err("%sput task forcibly aborted\n", DIR_STR(dir)); + return -EBUSY; + } + + return 0; +} + +#define mxc_asrc_dma_umap(m2m) \ + do { \ + dma_unmap_sg(NULL, m2m->sg[IN], m2m->sg_nodes[IN], \ + DMA_MEM_TO_DEV); \ + dma_unmap_sg(NULL, m2m->sg[OUT], m2m->sg_nodes[OUT], \ + DMA_DEV_TO_MEM); \ + } while (0) + +int fsl_asrc_process_buffer(struct fsl_asrc_pair *pair, + struct asrc_convert_buffer *pbuf) +{ + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + unsigned long lock_flags; + int ret; + + /* Check input task first */ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[IN], index, IN); + if (ret) { + mxc_asrc_dma_umap(m2m); + return ret; + } + + /* ...then output task*/ + ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, OUT); + if (ret) { + mxc_asrc_dma_umap(m2m); + return ret; + } + + mxc_asrc_dma_umap(m2m); + + /* Fetch the remaining data */ + spin_lock_irqsave(&m2m->lock, lock_flags); + if (!m2m->pair_hold) { + spin_unlock_irqrestore(&m2m->lock, lock_flags); + return -EFAULT; + } + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + fsl_asrc_read_last_FIFO(pair); + + /* Update final lengths after getting last FIFO */ + pbuf->input_buffer_length = m2m->dma_block[IN].length; + pbuf->output_buffer_length = m2m->dma_block[OUT].length; + + if (copy_to_user((void __user *)pbuf->output_buffer_vaddr, + m2m->dma_block[OUT].dma_vaddr, + m2m->dma_block[OUT].length)) + return -EFAULT; + + return 0; +} + +#ifdef ASRC_POLLING_WITHOUT_DMA +/* THIS FUNCTION ONLY EXISTS FOR DEBUGGING AND ONLY SUPPORTS TWO CHANNELS */ +static void fsl_asrc_polling_debug(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 *in24 = m2m->dma_block[IN].dma_vaddr; + u32 dma_len = m2m->dma_block[IN].length / (pair->channels * 4); + u32 *reg24 = m2m->dma_block[OUT].dma_vaddr; + u32 size, i, j, t_size, reg; + + t_size = 0; + + for (i = 0; i < dma_len; ) { + for (j = 0; j < 2; j++) { + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + regmap_write(asrc_priv->regmap, REG_ASRDx(index), *in24); + in24++; + i++; + } + udelay(50); + udelay(50 * m2m->rate[OUT] / m2m->rate[IN]); + + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + } + + mdelay(1); + size = fsl_asrc_get_output_FIFO_size(index); + for (j = 0; j < size; j++) { + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + regmap_read(asrc_priv->regmap, REG_ASRDO(index), ®); + *(reg24) = reg; + reg24++; + } + t_size += size; + + m2m->dma_block[OUT].length = t_size * pair->channels * 4; + + complete(&m2m->complete[OUT]); + complete(&m2m->complete[IN]); +} +#else +static void fsl_asrc_submit_dma(struct fsl_asrc_pair *pair) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index = pair->index; + u32 size = fsl_asrc_get_output_FIFO_size(pair); + int i; + + /* Read all data in OUTPUT FIFO */ + while (size) { + u32 val; + for (i = 0; i < size * pair->channels; i++) + regmap_read(asrc_priv->regmap, REG_ASRDO(index), &val); + /* Fetch the data every 100us */ + udelay(100); + + size = fsl_asrc_get_output_FIFO_size(pair); + } + + /* Submit DMA request */ + dmaengine_submit(pair->desc[IN]); + dma_async_issue_pending(pair->desc[IN]->chan); + + dmaengine_submit(pair->desc[OUT]); + dma_async_issue_pending(pair->desc[OUT]->chan); + + /* + * Clear DMA request during the stall state of ASRC: + * During STALL state, the remaining in input fifo would never be + * smaller than the input threshold while the output fifo would not + * be bigger than output one. Thus the DMA request would be cleared. + */ + fsl_asrc_set_watermarks(pair, ASRC_FIFO_THRESHOLD_MIN, + ASRC_FIFO_THRESHOLD_MAX); + + /* Update the real input threshold to raise DMA request */ + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); +} +#endif /* ASRC_POLLING_WITHOUT_DMA */ + +static long fsl_asrc_ioctl_req_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_req req; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&req, user, sizeof(req)); + if (ret) { + dev_err(dev, "failed to get req from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_request_pair(req.chn_num, pair); + if (ret) { + dev_err(dev, "failed to request pair: %ld\n", ret); + return ret; + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 1; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + pair->channels = req.chn_num; + + req.index = pair->index; + + ret = copy_to_user(user, &req, sizeof(req)); + if (ret) { + dev_err(dev, "failed to send req to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_config_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + struct device *dev = &asrc_priv->pdev->dev; + struct asrc_config config; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&config, user, sizeof(config)); + if (ret) { + dev_err(dev, "failed to get config from user space: %ld\n", ret); + return ret; + } + + index = config.pair; + + pair->config = &config; + ret = fsl_asrc_config_pair(pair); + if (ret) { + pair_err("failed to config pair: %ld\n", ret); + return ret; + } + + m2m->watermark[IN] = FSL_ASRC_INPUTFIFO_WML; + m2m->watermark[OUT] = FSL_ASRC_OUTPUTFIFO_WML; + + fsl_asrc_set_watermarks(pair, m2m->watermark[IN], m2m->watermark[OUT]); + + m2m->dma_block[IN].length = ASRC_DMA_BUFFER_SIZE; + m2m->dma_block[OUT].length = ASRC_DMA_BUFFER_SIZE; + + m2m->word_width[IN] = config.input_word_width; + m2m->word_width[OUT] = config.output_word_width; + + m2m->rate[IN] = config.input_sample_rate; + m2m->rate[OUT] = config.output_sample_rate; + + if (m2m->rate[OUT] >= m2m->rate[IN] * 8) + m2m->last_period_size = (m2m->rate[OUT] / m2m->rate[IN]) * 5; + else if (m2m->rate[OUT] > m2m->rate[IN]) + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE_MAX; + else + m2m->last_period_size = ASRC_OUTPUT_LAST_SAMPLE; + + ret = fsl_allocate_dma_buf(pair); + if (ret) { + pair_err("failed to allocate DMA buffer: %ld\n", ret); + return ret; + } + + /* Request DMA channel for both input and output */ + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + ret = copy_to_user(user, &config, sizeof(config)); + if (ret) { + pair_err("failed to send config to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_release_pair(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index; + unsigned long lock_flags; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + /* index might be not valid due to some application failure. */ + if (index < 0) + return -EINVAL; + + m2m->asrc_active = 0; + + spin_lock_irqsave(&m2m->lock, lock_flags); + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + fsl_asrc_release_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_convert(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_convert_buffer buf; + long ret; + + ret = copy_from_user(&buf, user, sizeof(buf)); + if (ret) { + pair_err("failed to get buf from user space: %ld\n", ret); + return ret; + } + + ret = fsl_asrc_prepare_buffer(pair, &buf); + if (ret) { + pair_err("failed to prepare buffer: %ld\n", ret); + return ret; + } + + init_completion(&m2m->complete[IN]); + init_completion(&m2m->complete[OUT]); + +#ifdef ASRC_POLLING_WITHOUT_DMA + fsl_asrc_polling_debug(pair); +#else + fsl_asrc_submit_dma(pair); +#endif + + ret = fsl_asrc_process_buffer(pair, &buf); + if (ret) { + pair_err("failed to process buffer: %ld\n", ret); + return ret; + } + + ret = copy_to_user(user, &buf, sizeof(buf)); + if (ret) { + pair_err("failed to send buf to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_start_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + m2m->asrc_active = 1; + fsl_asrc_start_pair(pair); + + return 0; +} + +static long fsl_asrc_ioctl_stop_conv(struct fsl_asrc_pair *pair, + void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct fsl_asrc_m2m *m2m = pair->private; + enum asrc_pair_index index; + long ret; + + ret = copy_from_user(&index, user, sizeof(index)); + if (ret) { + pair_err("failed to get index from user space: %ld\n", ret); + return ret; + } + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + m2m->asrc_active = 0; + + return 0; +} + +static long fsl_asrc_ioctl_status(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + struct asrc_status_flags flags; + long ret; + + ret = copy_from_user(&flags, user, sizeof(flags)); + if (ret) { + pair_err("failed to get flags from user space: %ld\n", ret); + return ret; + } + + fsl_asrc_get_status(pair, &flags); + + ret = copy_to_user(user, &flags, sizeof(flags)); + if (ret) { + pair_err("failed to send flags to user space: %ld\n", ret); + return ret; + } + + return 0; +} + +static long fsl_asrc_ioctl_flush(struct fsl_asrc_pair *pair, void __user *user) +{ + struct fsl_asrc *asrc_priv = pair->asrc_priv; + enum asrc_pair_index index = pair->index; + + /* Release DMA and request again */ + dma_release_channel(pair->dma_chan[IN]); + dma_release_channel(pair->dma_chan[OUT]); + + pair->dma_chan[IN] = fsl_asrc_get_dma_channel(pair, IN); + if (pair->dma_chan[IN] == NULL) { + pair_err("failed to request input task DMA channel\n"); + return -EBUSY; + } + + pair->dma_chan[OUT] = fsl_asrc_get_dma_channel(pair, OUT); + if (pair->dma_chan[OUT] == NULL) { + pair_err("failed to request output task DMA channel\n"); + return -EBUSY; + } + + return 0; +} + +static long fsl_asrc_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + void __user *user = (void __user *)arg; + long ret = 0; + + switch (cmd) { + case ASRC_REQ_PAIR: + ret = fsl_asrc_ioctl_req_pair(pair, user); + break; + case ASRC_CONFIG_PAIR: + ret = fsl_asrc_ioctl_config_pair(pair, user); + break; + case ASRC_RELEASE_PAIR: + ret = fsl_asrc_ioctl_release_pair(pair, user); + break; + case ASRC_CONVERT: + ret = fsl_asrc_ioctl_convert(pair, user); + break; + case ASRC_START_CONV: + ret = fsl_asrc_ioctl_start_conv(pair, user); + break; + case ASRC_STOP_CONV: + ret = fsl_asrc_ioctl_stop_conv(pair, user); + break; + case ASRC_STATUS: + ret = fsl_asrc_ioctl_status(pair, user); + break; + case ASRC_FLUSH: + ret = fsl_asrc_ioctl_flush(pair, user); + break; + default: + dev_err(&asrc_priv->pdev->dev, "invalid ioctl cmd!\n"); + break; + } + + return ret; +} + +static int fsl_asrc_open(struct inode *inode, struct file *file) +{ + struct fsl_asrc *asrc_priv = dev_get_drvdata(asrc_miscdev.this_device); + struct device *dev = &asrc_priv->pdev->dev; + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + int ret; + + ret = signal_pending(current); + if (ret) { + dev_err(dev, "current process has a signal pending\n"); + return ret; + } + + pair = kzalloc(sizeof(struct fsl_asrc_pair), GFP_KERNEL); + if (!pair) { + dev_err(dev, "failed to allocate pair\n"); + return -ENOMEM; + } + + m2m = kzalloc(sizeof(struct fsl_asrc_m2m), GFP_KERNEL); + if (!m2m) { + dev_err(dev, "failed to allocate m2m resource\n"); + ret = -ENOMEM; + goto out; + } + + pair->private = m2m; + pair->asrc_priv = asrc_priv; + + spin_lock_init(&m2m->lock); + + file->private_data = pair; + + pm_runtime_get_sync(dev); + + return 0; +out: + kfree(pair); + + return ret; +} + +static int fsl_asrc_close(struct inode *inode, struct file *file) +{ + struct fsl_asrc_pair *pair = file->private_data; + struct fsl_asrc_m2m *m2m = pair->private; + struct fsl_asrc *asrc_priv = pair->asrc_priv; + struct device *dev = &asrc_priv->pdev->dev; + unsigned long lock_flags; + + if (m2m->asrc_active) { + m2m->asrc_active = 0; + + dmaengine_terminate_all(pair->dma_chan[IN]); + dmaengine_terminate_all(pair->dma_chan[OUT]); + + fsl_asrc_stop_pair(pair); + fsl_asrc_input_dma_callback((void *)pair); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_lock_irqsave(&m2m->lock, lock_flags); + if (m2m->pair_hold) { + m2m->pair_hold = 0; + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + if (pair->dma_chan[IN]) + dma_release_channel(pair->dma_chan[IN]); + if (pair->dma_chan[OUT]) + dma_release_channel(pair->dma_chan[OUT]); + + kfree(m2m->dma_block[IN].dma_vaddr); + kfree(m2m->dma_block[OUT].dma_vaddr); + + fsl_asrc_release_pair(pair); + } else + spin_unlock_irqrestore(&m2m->lock, lock_flags); + + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + kfree(m2m); + kfree(pair); + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + file->private_data = NULL; + + pm_runtime_put_sync(dev); + + return 0; +} + +static const struct file_operations asrc_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = fsl_asrc_ioctl, + .open = fsl_asrc_open, + .release = fsl_asrc_close, +}; + +static int fsl_asrc_m2m_init(struct fsl_asrc *asrc_priv) +{ + struct device *dev = &asrc_priv->pdev->dev; + int ret; + + asrc_miscdev.fops = &asrc_fops, + ret = misc_register(&asrc_miscdev); + if (ret) { + dev_err(dev, "failed to register char device %d\n", ret); + return ret; + } + dev_set_drvdata(asrc_miscdev.this_device, asrc_priv); + + return 0; +} + +static int fsl_asrc_m2m_remove(struct platform_device *pdev) +{ + misc_deregister(&asrc_miscdev); + return 0; +} + +static void fsl_asrc_m2m_suspend(struct fsl_asrc *asrc_priv) +{ + struct fsl_asrc_pair *pair; + struct fsl_asrc_m2m *m2m; + unsigned long lock_flags; + int i; + + for (i = 0; i < ASRC_PAIR_MAX_NUM; i++) { + spin_lock_irqsave(&asrc_priv->lock, lock_flags); + pair = asrc_priv->pair[i]; + if (!pair || !pair->private) { + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + continue; + } + m2m = pair->private; + + if (!completion_done(&m2m->complete[IN])) { + if (pair->dma_chan[IN]) + dmaengine_terminate_all(pair->dma_chan[IN]); + fsl_asrc_input_dma_callback((void *)pair); + } + if (!completion_done(&m2m->complete[OUT])) { + if (pair->dma_chan[OUT]) + dmaengine_terminate_all(pair->dma_chan[OUT]); + fsl_asrc_output_dma_callback((void *)pair); + } + + spin_unlock_irqrestore(&asrc_priv->lock, lock_flags); + } +} |