|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | // Copyright 2017-2021 NXP | 
|  |  | 
|  | #include <linux/dma-mapping.h> | 
|  | #include <linux/slab.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/rpmsg.h> | 
|  | #include <sound/core.h> | 
|  | #include <sound/pcm.h> | 
|  | #include <sound/pcm_params.h> | 
|  | #include <sound/dmaengine_pcm.h> | 
|  | #include <sound/soc.h> | 
|  |  | 
|  | #include "imx-pcm.h" | 
|  | #include "fsl_rpmsg.h" | 
|  | #include "imx-pcm-rpmsg.h" | 
|  |  | 
|  | static const struct snd_pcm_hardware imx_rpmsg_pcm_hardware = { | 
|  | .info = SNDRV_PCM_INFO_INTERLEAVED | | 
|  | SNDRV_PCM_INFO_BLOCK_TRANSFER | | 
|  | SNDRV_PCM_INFO_BATCH | | 
|  | SNDRV_PCM_INFO_MMAP | | 
|  | SNDRV_PCM_INFO_MMAP_VALID | | 
|  | SNDRV_PCM_INFO_NO_PERIOD_WAKEUP | | 
|  | SNDRV_PCM_INFO_PAUSE | | 
|  | SNDRV_PCM_INFO_RESUME, | 
|  | .buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE, | 
|  | .period_bytes_min = 512, | 
|  | .period_bytes_max = 65536, | 
|  | .periods_min = 2, | 
|  | .periods_max = 6000, | 
|  | .fifo_size = 0, | 
|  | }; | 
|  |  | 
|  | static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg, | 
|  | struct rpmsg_info *info) | 
|  | { | 
|  | struct rpmsg_device *rpdev = info->rpdev; | 
|  | int ret = 0; | 
|  |  | 
|  | mutex_lock(&info->msg_lock); | 
|  | if (!rpdev) { | 
|  | dev_err(info->dev, "rpmsg channel not ready\n"); | 
|  | mutex_unlock(&info->msg_lock); | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | dev_dbg(&rpdev->dev, "send cmd %d\n", msg->s_msg.header.cmd); | 
|  |  | 
|  | if (!(msg->s_msg.header.type == MSG_TYPE_C)) | 
|  | reinit_completion(&info->cmd_complete); | 
|  |  | 
|  | ret = rpmsg_send(rpdev->ept, (void *)&msg->s_msg, | 
|  | sizeof(struct rpmsg_s_msg)); | 
|  | if (ret) { | 
|  | dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret); | 
|  | mutex_unlock(&info->msg_lock); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* No receive msg for TYPE_C command */ | 
|  | if (msg->s_msg.header.type == MSG_TYPE_C) { | 
|  | mutex_unlock(&info->msg_lock); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* wait response from rpmsg */ | 
|  | ret = wait_for_completion_timeout(&info->cmd_complete, | 
|  | msecs_to_jiffies(RPMSG_TIMEOUT)); | 
|  | if (!ret) { | 
|  | dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n", | 
|  | msg->s_msg.header.cmd); | 
|  | mutex_unlock(&info->msg_lock); | 
|  | return -ETIMEDOUT; | 
|  | } | 
|  |  | 
|  | memcpy(&msg->r_msg, &info->r_msg, sizeof(struct rpmsg_r_msg)); | 
|  | memcpy(&info->msg[msg->r_msg.header.cmd].r_msg, | 
|  | &msg->r_msg, sizeof(struct rpmsg_r_msg)); | 
|  |  | 
|  | /* | 
|  | * Reset the buffer pointer to be zero, actully we have | 
|  | * set the buffer pointer to be zero in imx_rpmsg_terminate_all | 
|  | * But if there is timer task queued in queue, after it is | 
|  | * executed the buffer pointer will be changed, so need to | 
|  | * reset it again with TERMINATE command. | 
|  | */ | 
|  | switch (msg->s_msg.header.cmd) { | 
|  | case TX_TERMINATE: | 
|  | info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; | 
|  | break; | 
|  | case RX_TERMINATE: | 
|  | info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; | 
|  | break; | 
|  | default: | 
|  | break; | 
|  | } | 
|  |  | 
|  | dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd, | 
|  | info->r_msg.param.resp); | 
|  |  | 
|  | mutex_unlock(&info->msg_lock); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream, | 
|  | struct rpmsg_msg *msg, | 
|  | struct rpmsg_info *info) | 
|  | { | 
|  | unsigned long flags; | 
|  | int ret = 0; | 
|  |  | 
|  | /* | 
|  | * Queue the work to workqueue. | 
|  | * If the queue is full, drop the message. | 
|  | */ | 
|  | spin_lock_irqsave(&info->wq_lock, flags); | 
|  | if (info->work_write_index != info->work_read_index) { | 
|  | int index = info->work_write_index; | 
|  |  | 
|  | memcpy(&info->work_list[index].msg, msg, | 
|  | sizeof(struct rpmsg_s_msg)); | 
|  |  | 
|  | queue_work(info->rpmsg_wq, &info->work_list[index].work); | 
|  | info->work_write_index++; | 
|  | info->work_write_index %= WORK_MAX_NUM; | 
|  | } else { | 
|  | info->msg_drop_count[substream->stream]++; | 
|  | ret = -EPIPE; | 
|  | } | 
|  | spin_unlock_irqrestore(&info->wq_lock, flags); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_hw_params(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream, | 
|  | struct snd_pcm_hw_params *params) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_HW_PARAM]; | 
|  | msg->s_msg.header.cmd = TX_HW_PARAM; | 
|  | } else { | 
|  | msg = &info->msg[RX_HW_PARAM]; | 
|  | msg->s_msg.header.cmd = RX_HW_PARAM; | 
|  | } | 
|  |  | 
|  | msg->s_msg.param.rate = params_rate(params); | 
|  |  | 
|  | switch (params_format(params)) { | 
|  | case SNDRV_PCM_FORMAT_S16_LE: | 
|  | msg->s_msg.param.format   = RPMSG_S16_LE; | 
|  | break; | 
|  | case SNDRV_PCM_FORMAT_S24_LE: | 
|  | msg->s_msg.param.format   = RPMSG_S24_LE; | 
|  | break; | 
|  | case SNDRV_PCM_FORMAT_DSD_U16_LE: | 
|  | msg->s_msg.param.format   = RPMSG_DSD_U16_LE; | 
|  | break; | 
|  | case SNDRV_PCM_FORMAT_DSD_U32_LE: | 
|  | msg->s_msg.param.format   = RPMSG_DSD_U32_LE; | 
|  | break; | 
|  | default: | 
|  | msg->s_msg.param.format   = RPMSG_S32_LE; | 
|  | break; | 
|  | } | 
|  |  | 
|  | switch (params_channels(params)) { | 
|  | case 1: | 
|  | msg->s_msg.param.channels = RPMSG_CH_LEFT; | 
|  | break; | 
|  | case 2: | 
|  | msg->s_msg.param.channels = RPMSG_CH_STEREO; | 
|  | break; | 
|  | default: | 
|  | msg->s_msg.param.channels = params_channels(params); | 
|  | break; | 
|  | } | 
|  |  | 
|  | info->send_message(msg, info); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  | unsigned int pos = 0; | 
|  | int buffer_tail = 0; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | 
|  | msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  | else | 
|  | msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  |  | 
|  | buffer_tail = msg->r_msg.param.buffer_tail; | 
|  | pos = buffer_tail * snd_pcm_lib_period_bytes(substream); | 
|  |  | 
|  | return bytes_to_frames(substream->runtime, pos); | 
|  | } | 
|  |  | 
|  | static void imx_rpmsg_timer_callback(struct timer_list *t) | 
|  | { | 
|  | struct stream_timer  *stream_timer = | 
|  | from_timer(stream_timer, t, timer); | 
|  | struct snd_pcm_substream *substream = stream_timer->substream; | 
|  | struct rpmsg_info *info = stream_timer->info; | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  | msg->s_msg.header.cmd = TX_PERIOD_DONE; | 
|  | } else { | 
|  | msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  | msg->s_msg.header.cmd = RX_PERIOD_DONE; | 
|  | } | 
|  |  | 
|  | imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_open(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); | 
|  | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | 
|  | struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); | 
|  | struct snd_pcm_hardware pcm_hardware; | 
|  | struct rpmsg_msg *msg; | 
|  | int ret = 0; | 
|  | int cmd; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_OPEN]; | 
|  | msg->s_msg.header.cmd = TX_OPEN; | 
|  |  | 
|  | /* reinitialize buffer counter*/ | 
|  | cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM; | 
|  | info->msg[cmd].s_msg.param.buffer_tail = 0; | 
|  | info->msg[cmd].r_msg.param.buffer_tail = 0; | 
|  | info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; | 
|  |  | 
|  | } else { | 
|  | msg = &info->msg[RX_OPEN]; | 
|  | msg->s_msg.header.cmd = RX_OPEN; | 
|  |  | 
|  | /* reinitialize buffer counter*/ | 
|  | cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM; | 
|  | info->msg[cmd].s_msg.param.buffer_tail = 0; | 
|  | info->msg[cmd].r_msg.param.buffer_tail = 0; | 
|  | info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; | 
|  | } | 
|  |  | 
|  | info->send_message(msg, info); | 
|  |  | 
|  | pcm_hardware = imx_rpmsg_pcm_hardware; | 
|  | pcm_hardware.buffer_bytes_max = rpmsg->buffer_size; | 
|  | pcm_hardware.period_bytes_max = pcm_hardware.buffer_bytes_max / 2; | 
|  |  | 
|  | snd_soc_set_runtime_hwparams(substream, &pcm_hardware); | 
|  |  | 
|  | ret = snd_pcm_hw_constraint_integer(substream->runtime, | 
|  | SNDRV_PCM_HW_PARAM_PERIODS); | 
|  | if (ret < 0) | 
|  | return ret; | 
|  |  | 
|  | info->msg_drop_count[substream->stream] = 0; | 
|  |  | 
|  | /* Create timer*/ | 
|  | info->stream_timer[substream->stream].info = info; | 
|  | info->stream_timer[substream->stream].substream = substream; | 
|  | timer_setup(&info->stream_timer[substream->stream].timer, | 
|  | imx_rpmsg_timer_callback, 0); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_close(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | /* Flush work in workqueue to make TX_CLOSE is the last message */ | 
|  | flush_workqueue(info->rpmsg_wq); | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_CLOSE]; | 
|  | msg->s_msg.header.cmd = TX_CLOSE; | 
|  | } else { | 
|  | msg = &info->msg[RX_CLOSE]; | 
|  | msg->s_msg.header.cmd = RX_CLOSE; | 
|  | } | 
|  |  | 
|  | info->send_message(msg, info); | 
|  |  | 
|  | del_timer(&info->stream_timer[substream->stream].timer); | 
|  |  | 
|  | rtd->dai_link->ignore_suspend = 0; | 
|  |  | 
|  | if (info->msg_drop_count[substream->stream]) | 
|  | dev_warn(rtd->dev, "Msg is dropped!, number is %d\n", | 
|  | info->msg_drop_count[substream->stream]); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_prepare(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); | 
|  | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | 
|  | struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); | 
|  |  | 
|  | /* | 
|  | * NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts | 
|  | * four conditions to determine the lpa is enabled. | 
|  | */ | 
|  | if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED || | 
|  | runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) && | 
|  | rpmsg->enable_lpa) { | 
|  | /* | 
|  | * Ignore suspend operation in low power mode | 
|  | * M core will continue playback music on A core suspend. | 
|  | */ | 
|  | rtd->dai_link->ignore_suspend = 1; | 
|  | rpmsg->force_lpa = 1; | 
|  | } else { | 
|  | rpmsg->force_lpa = 0; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void imx_rpmsg_pcm_dma_complete(void *arg) | 
|  | { | 
|  | struct snd_pcm_substream *substream = arg; | 
|  |  | 
|  | snd_pcm_period_elapsed(substream); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_prepare_and_submit(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_BUFFER]; | 
|  | msg->s_msg.header.cmd = TX_BUFFER; | 
|  | } else { | 
|  | msg = &info->msg[RX_BUFFER]; | 
|  | msg->s_msg.header.cmd = RX_BUFFER; | 
|  | } | 
|  |  | 
|  | /* Send buffer address and buffer size */ | 
|  | msg->s_msg.param.buffer_addr = substream->runtime->dma_addr; | 
|  | msg->s_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream); | 
|  | msg->s_msg.param.period_size = snd_pcm_lib_period_bytes(substream); | 
|  | msg->s_msg.param.buffer_tail = 0; | 
|  |  | 
|  | info->num_period[substream->stream] = msg->s_msg.param.buffer_size / | 
|  | msg->s_msg.param.period_size; | 
|  |  | 
|  | info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete; | 
|  | info->callback_param[substream->stream] = substream; | 
|  |  | 
|  | return imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_async_issue_pending(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_START]; | 
|  | msg->s_msg.header.cmd = TX_START; | 
|  | } else { | 
|  | msg = &info->msg[RX_START]; | 
|  | msg->s_msg.header.cmd = RX_START; | 
|  | } | 
|  |  | 
|  | return imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_restart(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_RESTART]; | 
|  | msg->s_msg.header.cmd = TX_RESTART; | 
|  | } else { | 
|  | msg = &info->msg[RX_RESTART]; | 
|  | msg->s_msg.header.cmd = RX_RESTART; | 
|  | } | 
|  |  | 
|  | return imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pause(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_PAUSE]; | 
|  | msg->s_msg.header.cmd = TX_PAUSE; | 
|  | } else { | 
|  | msg = &info->msg[RX_PAUSE]; | 
|  | msg->s_msg.header.cmd = RX_PAUSE; | 
|  | } | 
|  |  | 
|  | return imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_terminate_all(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | struct rpmsg_msg *msg; | 
|  | int cmd; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_TERMINATE]; | 
|  | msg->s_msg.header.cmd = TX_TERMINATE; | 
|  | /* Clear buffer count*/ | 
|  | cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM; | 
|  | info->msg[cmd].s_msg.param.buffer_tail = 0; | 
|  | info->msg[cmd].r_msg.param.buffer_tail = 0; | 
|  | info->msg[TX_POINTER].r_msg.param.buffer_offset = 0; | 
|  | } else { | 
|  | msg = &info->msg[RX_TERMINATE]; | 
|  | msg->s_msg.header.cmd = RX_TERMINATE; | 
|  | /* Clear buffer count*/ | 
|  | cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM; | 
|  | info->msg[cmd].s_msg.param.buffer_tail = 0; | 
|  | info->msg[cmd].r_msg.param.buffer_tail = 0; | 
|  | info->msg[RX_POINTER].r_msg.param.buffer_offset = 0; | 
|  | } | 
|  |  | 
|  | del_timer(&info->stream_timer[substream->stream].timer); | 
|  |  | 
|  | return imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_trigger(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream, int cmd) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); | 
|  | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | 
|  | struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); | 
|  | int ret = 0; | 
|  |  | 
|  | switch (cmd) { | 
|  | case SNDRV_PCM_TRIGGER_START: | 
|  | ret = imx_rpmsg_prepare_and_submit(component, substream); | 
|  | if (ret) | 
|  | return ret; | 
|  | ret = imx_rpmsg_async_issue_pending(component, substream); | 
|  | break; | 
|  | case SNDRV_PCM_TRIGGER_RESUME: | 
|  | if (rpmsg->force_lpa) | 
|  | break; | 
|  | fallthrough; | 
|  | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: | 
|  | ret = imx_rpmsg_restart(component, substream); | 
|  | break; | 
|  | case SNDRV_PCM_TRIGGER_SUSPEND: | 
|  | if (!rpmsg->force_lpa) { | 
|  | if (runtime->info & SNDRV_PCM_INFO_PAUSE) | 
|  | ret = imx_rpmsg_pause(component, substream); | 
|  | else | 
|  | ret = imx_rpmsg_terminate_all(component, substream); | 
|  | } | 
|  | break; | 
|  | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: | 
|  | ret = imx_rpmsg_pause(component, substream); | 
|  | break; | 
|  | case SNDRV_PCM_TRIGGER_STOP: | 
|  | ret = imx_rpmsg_terminate_all(component, substream); | 
|  | break; | 
|  | default: | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * imx_rpmsg_pcm_ack | 
|  | * | 
|  | * Send the period index to M core through rpmsg, but not send | 
|  | * all the period index to M core, reduce some unnessesary msg | 
|  | * to reduce the pressure of rpmsg bandwidth. | 
|  | */ | 
|  | static int imx_rpmsg_pcm_ack(struct snd_soc_component *component, | 
|  | struct snd_pcm_substream *substream) | 
|  | { | 
|  | struct snd_pcm_runtime *runtime = substream->runtime; | 
|  | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); | 
|  | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | 
|  | struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); | 
|  | struct rpmsg_info *info = dev_get_drvdata(component->dev); | 
|  | snd_pcm_uframes_t period_size = runtime->period_size; | 
|  | snd_pcm_sframes_t avail; | 
|  | struct timer_list *timer; | 
|  | struct rpmsg_msg *msg; | 
|  | unsigned long flags; | 
|  | int buffer_tail = 0; | 
|  | int written_num; | 
|  |  | 
|  | if (!rpmsg->force_lpa) | 
|  | return 0; | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | 
|  | msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  | msg->s_msg.header.cmd = TX_PERIOD_DONE; | 
|  | } else { | 
|  | msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
|  | msg->s_msg.header.cmd = RX_PERIOD_DONE; | 
|  | } | 
|  |  | 
|  | msg->s_msg.header.type = MSG_TYPE_C; | 
|  |  | 
|  | buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) % | 
|  | snd_pcm_lib_buffer_bytes(substream)); | 
|  | buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream); | 
|  |  | 
|  | /* There is update for period index */ | 
|  | if (buffer_tail != msg->s_msg.param.buffer_tail) { | 
|  | written_num = buffer_tail - msg->s_msg.param.buffer_tail; | 
|  | if (written_num < 0) | 
|  | written_num += runtime->periods; | 
|  |  | 
|  | msg->s_msg.param.buffer_tail = buffer_tail; | 
|  |  | 
|  | /* The notification message is updated to latest */ | 
|  | spin_lock_irqsave(&info->lock[substream->stream], flags); | 
|  | memcpy(&info->notify[substream->stream], msg, | 
|  | sizeof(struct rpmsg_s_msg)); | 
|  | info->notify_updated[substream->stream] = true; | 
|  | spin_unlock_irqrestore(&info->lock[substream->stream], flags); | 
|  |  | 
|  | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) | 
|  | avail = snd_pcm_playback_hw_avail(runtime); | 
|  | else | 
|  | avail = snd_pcm_capture_hw_avail(runtime); | 
|  |  | 
|  | timer = &info->stream_timer[substream->stream].timer; | 
|  | /* | 
|  | * If the data in the buffer is less than one period before | 
|  | * this fill, which means the data may not enough on M | 
|  | * core side, we need to send message immediately to let | 
|  | * M core know the pointer is updated. | 
|  | * if there is more than one period data in the buffer before | 
|  | * this fill, which means the data is enough on M core side, | 
|  | * we can delay one period (using timer) to send the message | 
|  | * for reduce the message number in workqueue, because the | 
|  | * pointer may be updated by ack function later, we can | 
|  | * send latest pointer to M core side. | 
|  | */ | 
|  | if ((avail - written_num * period_size) <= period_size) { | 
|  | imx_rpmsg_insert_workqueue(substream, msg, info); | 
|  | } else if (rpmsg->force_lpa && !timer_pending(timer)) { | 
|  | int time_msec; | 
|  |  | 
|  | time_msec = (int)(runtime->period_size * 1000 / runtime->rate); | 
|  | mod_timer(timer, jiffies + msecs_to_jiffies(time_msec)); | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_new(struct snd_soc_component *component, | 
|  | struct snd_soc_pcm_runtime *rtd) | 
|  | { | 
|  | struct snd_card *card = rtd->card->snd_card; | 
|  | struct snd_pcm *pcm = rtd->pcm; | 
|  | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | 
|  | struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev); | 
|  | int ret; | 
|  |  | 
|  | ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32)); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC, | 
|  | pcm->card->dev, rpmsg->buffer_size); | 
|  | } | 
|  |  | 
|  | static const struct snd_soc_component_driver imx_rpmsg_soc_component = { | 
|  | .name		= IMX_PCM_DRV_NAME, | 
|  | .pcm_construct	= imx_rpmsg_pcm_new, | 
|  | .open		= imx_rpmsg_pcm_open, | 
|  | .close		= imx_rpmsg_pcm_close, | 
|  | .hw_params	= imx_rpmsg_pcm_hw_params, | 
|  | .trigger	= imx_rpmsg_pcm_trigger, | 
|  | .pointer	= imx_rpmsg_pcm_pointer, | 
|  | .ack		= imx_rpmsg_pcm_ack, | 
|  | .prepare	= imx_rpmsg_pcm_prepare, | 
|  | }; | 
|  |  | 
|  | static void imx_rpmsg_pcm_work(struct work_struct *work) | 
|  | { | 
|  | struct work_of_rpmsg *work_of_rpmsg; | 
|  | bool is_notification = false; | 
|  | struct rpmsg_info *info; | 
|  | struct rpmsg_msg msg; | 
|  | unsigned long flags; | 
|  |  | 
|  | work_of_rpmsg = container_of(work, struct work_of_rpmsg, work); | 
|  | info = work_of_rpmsg->info; | 
|  |  | 
|  | /* | 
|  | * Every work in the work queue, first we check if there | 
|  | * is update for period is filled, because there may be not | 
|  | * enough data in M core side, need to let M core know | 
|  | * data is updated immediately. | 
|  | */ | 
|  | spin_lock_irqsave(&info->lock[TX], flags); | 
|  | if (info->notify_updated[TX]) { | 
|  | memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg)); | 
|  | info->notify_updated[TX] = false; | 
|  | spin_unlock_irqrestore(&info->lock[TX], flags); | 
|  | info->send_message(&msg, info); | 
|  | } else { | 
|  | spin_unlock_irqrestore(&info->lock[TX], flags); | 
|  | } | 
|  |  | 
|  | spin_lock_irqsave(&info->lock[RX], flags); | 
|  | if (info->notify_updated[RX]) { | 
|  | memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg)); | 
|  | info->notify_updated[RX] = false; | 
|  | spin_unlock_irqrestore(&info->lock[RX], flags); | 
|  | info->send_message(&msg, info); | 
|  | } else { | 
|  | spin_unlock_irqrestore(&info->lock[RX], flags); | 
|  | } | 
|  |  | 
|  | /* Skip the notification message for it has been processed above */ | 
|  | if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C && | 
|  | (work_of_rpmsg->msg.s_msg.header.cmd == TX_PERIOD_DONE || | 
|  | work_of_rpmsg->msg.s_msg.header.cmd == RX_PERIOD_DONE)) | 
|  | is_notification = true; | 
|  |  | 
|  | if (!is_notification) | 
|  | info->send_message(&work_of_rpmsg->msg, info); | 
|  |  | 
|  | /* update read index */ | 
|  | spin_lock_irqsave(&info->wq_lock, flags); | 
|  | info->work_read_index++; | 
|  | info->work_read_index %= WORK_MAX_NUM; | 
|  | spin_unlock_irqrestore(&info->wq_lock, flags); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_probe(struct platform_device *pdev) | 
|  | { | 
|  | struct snd_soc_component *component; | 
|  | struct rpmsg_info *info; | 
|  | int ret, i; | 
|  |  | 
|  | info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); | 
|  | if (!info) | 
|  | return -ENOMEM; | 
|  |  | 
|  | platform_set_drvdata(pdev, info); | 
|  |  | 
|  | info->rpdev = container_of(pdev->dev.parent, struct rpmsg_device, dev); | 
|  | info->dev = &pdev->dev; | 
|  | /* Setup work queue */ | 
|  | info->rpmsg_wq = alloc_ordered_workqueue(info->rpdev->id.name, | 
|  | WQ_HIGHPRI | | 
|  | WQ_UNBOUND | | 
|  | WQ_FREEZABLE); | 
|  | if (!info->rpmsg_wq) { | 
|  | dev_err(&pdev->dev, "workqueue create failed\n"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | /* Write index initialize 1, make it differ with the read index */ | 
|  | info->work_write_index = 1; | 
|  | info->send_message = imx_rpmsg_pcm_send_message; | 
|  |  | 
|  | for (i = 0; i < WORK_MAX_NUM; i++) { | 
|  | INIT_WORK(&info->work_list[i].work, imx_rpmsg_pcm_work); | 
|  | info->work_list[i].info = info; | 
|  | } | 
|  |  | 
|  | /* Initialize msg */ | 
|  | for (i = 0; i < MSG_MAX_NUM; i++) { | 
|  | info->msg[i].s_msg.header.cate  = IMX_RPMSG_AUDIO; | 
|  | info->msg[i].s_msg.header.major = IMX_RMPSG_MAJOR; | 
|  | info->msg[i].s_msg.header.minor = IMX_RMPSG_MINOR; | 
|  | info->msg[i].s_msg.header.type  = MSG_TYPE_A; | 
|  | info->msg[i].s_msg.param.audioindex = 0; | 
|  | } | 
|  |  | 
|  | init_completion(&info->cmd_complete); | 
|  | mutex_init(&info->msg_lock); | 
|  | spin_lock_init(&info->lock[TX]); | 
|  | spin_lock_init(&info->lock[RX]); | 
|  | spin_lock_init(&info->wq_lock); | 
|  |  | 
|  | ret = devm_snd_soc_register_component(&pdev->dev, | 
|  | &imx_rpmsg_soc_component, | 
|  | NULL, 0); | 
|  | if (ret) | 
|  | goto fail; | 
|  |  | 
|  | component = snd_soc_lookup_component(&pdev->dev, NULL); | 
|  | if (!component) { | 
|  | ret = -EINVAL; | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_DEBUG_FS | 
|  | component->debugfs_prefix = "rpmsg"; | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | fail: | 
|  | if (info->rpmsg_wq) | 
|  | destroy_workqueue(info->rpmsg_wq); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void imx_rpmsg_pcm_remove(struct platform_device *pdev) | 
|  | { | 
|  | struct rpmsg_info *info = platform_get_drvdata(pdev); | 
|  |  | 
|  | if (info->rpmsg_wq) | 
|  | destroy_workqueue(info->rpmsg_wq); | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_runtime_resume(struct device *dev) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(dev); | 
|  |  | 
|  | cpu_latency_qos_add_request(&info->pm_qos_req, 0); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_runtime_suspend(struct device *dev) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(dev); | 
|  |  | 
|  | cpu_latency_qos_remove_request(&info->pm_qos_req); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_suspend(struct device *dev) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(dev); | 
|  | struct rpmsg_msg *rpmsg_tx; | 
|  | struct rpmsg_msg *rpmsg_rx; | 
|  |  | 
|  | rpmsg_tx = &info->msg[TX_SUSPEND]; | 
|  | rpmsg_rx = &info->msg[RX_SUSPEND]; | 
|  |  | 
|  | rpmsg_tx->s_msg.header.cmd = TX_SUSPEND; | 
|  | info->send_message(rpmsg_tx, info); | 
|  |  | 
|  | rpmsg_rx->s_msg.header.cmd = RX_SUSPEND; | 
|  | info->send_message(rpmsg_rx, info); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int imx_rpmsg_pcm_resume(struct device *dev) | 
|  | { | 
|  | struct rpmsg_info *info = dev_get_drvdata(dev); | 
|  | struct rpmsg_msg *rpmsg_tx; | 
|  | struct rpmsg_msg *rpmsg_rx; | 
|  |  | 
|  | rpmsg_tx = &info->msg[TX_RESUME]; | 
|  | rpmsg_rx = &info->msg[RX_RESUME]; | 
|  |  | 
|  | rpmsg_tx->s_msg.header.cmd = TX_RESUME; | 
|  | info->send_message(rpmsg_tx, info); | 
|  |  | 
|  | rpmsg_rx->s_msg.header.cmd = RX_RESUME; | 
|  | info->send_message(rpmsg_rx, info); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = { | 
|  | RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend, | 
|  | imx_rpmsg_pcm_runtime_resume, NULL) | 
|  | SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend, imx_rpmsg_pcm_resume) | 
|  | }; | 
|  |  | 
|  | static const struct platform_device_id imx_rpmsg_pcm_id_table[] = { | 
|  | { .name	= "rpmsg-audio-channel" }, | 
|  | { .name	= "rpmsg-micfil-channel" }, | 
|  | { }, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(platform, imx_rpmsg_pcm_id_table); | 
|  |  | 
|  | static struct platform_driver imx_pcm_rpmsg_driver = { | 
|  | .probe  = imx_rpmsg_pcm_probe, | 
|  | .remove = imx_rpmsg_pcm_remove, | 
|  | .id_table = imx_rpmsg_pcm_id_table, | 
|  | .driver = { | 
|  | .name = IMX_PCM_DRV_NAME, | 
|  | .pm = pm_ptr(&imx_rpmsg_pcm_pm_ops), | 
|  | }, | 
|  | }; | 
|  | module_platform_driver(imx_pcm_rpmsg_driver); | 
|  |  | 
|  | MODULE_DESCRIPTION("Freescale SoC Audio RPMSG PCM interface"); | 
|  | MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>"); | 
|  | MODULE_ALIAS("platform:" IMX_PCM_DRV_NAME); | 
|  | MODULE_LICENSE("GPL v2"); |