blob: e3b57278b43cf103492d882ce432e25692f5e279 [file] [log] [blame]
/*
* Freescale ASRC Memory to Memory (M2M) driver
*
* Copyright (C) 2014 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), &reg);
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) {
pair_err("%sput buffer size is too small: [%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;
}
init_completion(complete);
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, OUT);
if (ret) {
mxc_asrc_dma_umap(m2m);
return ret;
}
/* ...then output task*/
ret = fsl_asrc_process_buffer_pre(&m2m->complete[OUT], index, IN);
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), &reg);
*(reg24) = reg;
reg24++;
regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
*(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), &reg);
*(reg24) = reg;
reg24++;
regmap_read(asrc_priv->regmap, REG_ASRDO(index), &reg);
*(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, false, false);
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])
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 *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;
}
#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_m2m *m2m = pair->private;
struct fsl_asrc *asrc_priv = pair->asrc_priv;
enum asrc_pair_index index = pair->index;
init_completion(&m2m->complete[IN]);
init_completion(&m2m->complete[OUT]);
/* 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 (!pair) {
dev_err(dev, "failed to allocate m2m resource\n");
return -ENOMEM;
}
pair->private = m2m;
pair->asrc_priv = asrc_priv;
init_completion(&m2m->complete[IN]);
init_completion(&m2m->complete[OUT]);
spin_lock_init(&m2m->lock);
file->private_data = pair;
pm_runtime_get_sync(dev);
return 0;
}
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;
int i;
if (!pair)
goto out;
/* Make sure we have clear the pointer */
spin_lock_irqsave(&asrc_priv->lock, lock_flags);
for (i = 0; i < ASRC_PAIR_MAX_NUM; i++)
if (asrc_priv->pair[i] == pair)
asrc_priv->pair[i] = NULL;
spin_unlock_irqrestore(&asrc_priv->lock, 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;
out:
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);
}
}