aboutsummaryrefslogtreecommitdiff
path: root/sound/soc/fsl/imx-pcm-rpmsg.c
diff options
context:
space:
mode:
authorLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
committerLibravatar Linus Torvalds <torvalds@linux-foundation.org>2023-02-21 18:24:12 -0800
commit5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch)
treecc5c2d0a898769fd59549594fedb3ee6f84e59a0 /sound/soc/fsl/imx-pcm-rpmsg.c
downloadlinux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz
linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski: "Core: - Add dedicated kmem_cache for typical/small skb->head, avoid having to access struct page at kfree time, and improve memory use. - Introduce sysctl to set default RPS configuration for new netdevs. - Define Netlink protocol specification format which can be used to describe messages used by each family and auto-generate parsers. Add tools for generating kernel data structures and uAPI headers. - Expose all net/core sysctls inside netns. - Remove 4s sleep in netpoll if carrier is instantly detected on boot. - Add configurable limit of MDB entries per port, and port-vlan. - Continue populating drop reasons throughout the stack. - Retire a handful of legacy Qdiscs and classifiers. Protocols: - Support IPv4 big TCP (TSO frames larger than 64kB). - Add IP_LOCAL_PORT_RANGE socket option, to control local port range on socket by socket basis. - Track and report in procfs number of MPTCP sockets used. - Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path manager. - IPv6: don't check net.ipv6.route.max_size and rely on garbage collection to free memory (similarly to IPv4). - Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986). - ICMP: add per-rate limit counters. - Add support for user scanning requests in ieee802154. - Remove static WEP support. - Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate reporting. - WiFi 7 EHT channel puncturing support (client & AP). BPF: - Add a rbtree data structure following the "next-gen data structure" precedent set by recently added linked list, that is, by using kfunc + kptr instead of adding a new BPF map type. - Expose XDP hints via kfuncs with initial support for RX hash and timestamp metadata. - Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to better support decap on GRE tunnel devices not operating in collect metadata. - Improve x86 JIT's codegen for PROBE_MEM runtime error checks. - Remove the need for trace_printk_lock for bpf_trace_printk and bpf_trace_vprintk helpers. - Extend libbpf's bpf_tracing.h support for tracing arguments of kprobes/uprobes and syscall as a special case. - Significantly reduce the search time for module symbols by livepatch and BPF. - Enable cpumasks to be used as kptrs, which is useful for tracing programs tracking which tasks end up running on which CPUs in different time intervals. - Add support for BPF trampoline on s390x and riscv64. - Add capability to export the XDP features supported by the NIC. - Add __bpf_kfunc tag for marking kernel functions as kfuncs. - Add cgroup.memory=nobpf kernel parameter option to disable BPF memory accounting for container environments. Netfilter: - Remove the CLUSTERIP target. It has been marked as obsolete for years, and we still have WARN splats wrt races of the out-of-band /proc interface installed by this target. - Add 'destroy' commands to nf_tables. They are identical to the existing 'delete' commands, but do not return an error if the referenced object (set, chain, rule...) did not exist. Driver API: - Improve cpumask_local_spread() locality to help NICs set the right IRQ affinity on AMD platforms. - Separate C22 and C45 MDIO bus transactions more clearly. - Introduce new DCB table to control DSCP rewrite on egress. - Support configuration of Physical Layer Collision Avoidance (PLCA) Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of shared medium Ethernet. - Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing preemption of low priority frames by high priority frames. - Add support for controlling MACSec offload using netlink SET. - Rework devlink instance refcounts to allow registration and de-registration under the instance lock. Split the code into multiple files, drop some of the unnecessarily granular locks and factor out common parts of netlink operation handling. - Add TX frame aggregation parameters (for USB drivers). - Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning messages with notifications for debug. - Allow offloading of UDP NEW connections via act_ct. - Add support for per action HW stats in TC. - Support hardware miss to TC action (continue processing in SW from a specific point in the action chain). - Warn if old Wireless Extension user space interface is used with modern cfg80211/mac80211 drivers. Do not support Wireless Extensions for Wi-Fi 7 devices at all. Everyone should switch to using nl80211 interface instead. - Improve the CAN bit timing configuration. Use extack to return error messages directly to user space, update the SJW handling, including the definition of a new default value that will benefit CAN-FD controllers, by increasing their oscillator tolerance. New hardware / drivers: - Ethernet: - nVidia BlueField-3 support (control traffic driver) - Ethernet support for imx93 SoCs - Motorcomm yt8531 gigabit Ethernet PHY - onsemi NCN26000 10BASE-T1S PHY (with support for PLCA) - Microchip LAN8841 PHY (incl. cable diagnostics and PTP) - Amlogic gxl MDIO mux - WiFi: - RealTek RTL8188EU (rtl8xxxu) - Qualcomm Wi-Fi 7 devices (ath12k) - CAN: - Renesas R-Car V4H Drivers: - Bluetooth: - Set Per Platform Antenna Gain (PPAG) for Intel controllers. - Ethernet NICs: - Intel (1G, igc): - support TSN / Qbv / packet scheduling features of i226 model - Intel (100G, ice): - use GNSS subsystem instead of TTY - multi-buffer XDP support - extend support for GPIO pins to E823 devices - nVidia/Mellanox: - update the shared buffer configuration on PFC commands - implement PTP adjphase function for HW offset control - TC support for Geneve and GRE with VF tunnel offload - more efficient crypto key management method - multi-port eswitch support - Netronome/Corigine: - add DCB IEEE support - support IPsec offloading for NFP3800 - Freescale/NXP (enetc): - support XDP_REDIRECT for XDP non-linear buffers - improve reconfig, avoid link flap and waiting for idle - support MAC Merge layer - Other NICs: - sfc/ef100: add basic devlink support for ef100 - ionic: rx_push mode operation (writing descriptors via MMIO) - bnxt: use the auxiliary bus abstraction for RDMA - r8169: disable ASPM and reset bus in case of tx timeout - cpsw: support QSGMII mode for J721e CPSW9G - cpts: support pulse-per-second output - ngbe: add an mdio bus driver - usbnet: optimize usbnet_bh() by avoiding unnecessary queuing - r8152: handle devices with FW with NCM support - amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation - virtio-net: support multi buffer XDP - virtio/vsock: replace virtio_vsock_pkt with sk_buff - tsnep: XDP support - Ethernet high-speed switches: - nVidia/Mellanox (mlxsw): - add support for latency TLV (in FW control messages) - Microchip (sparx5): - separate explicit and implicit traffic forwarding rules, make the implicit rules always active - add support for egress DSCP rewrite - IS0 VCAP support (Ingress Classification) - IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS etc.) - ES2 VCAP support (Egress Access Control) - support for Per-Stream Filtering and Policing (802.1Q, 8.6.5.1) - Ethernet embedded switches: - Marvell (mv88e6xxx): - add MAB (port auth) offload support - enable PTP receive for mv88e6390 - NXP (ocelot): - support MAC Merge layer - support for the the vsc7512 internal copper phys - Microchip: - lan9303: convert to PHYLINK - lan966x: support TC flower filter statistics - lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x - lan937x: support Credit Based Shaper configuration - ksz9477: support Energy Efficient Ethernet - other: - qca8k: convert to regmap read/write API, use bulk operations - rswitch: Improve TX timestamp accuracy - Intel WiFi (iwlwifi): - EHT (Wi-Fi 7) rate reporting - STEP equalizer support: transfer some STEP (connection to radio on platforms with integrated wifi) related parameters from the BIOS to the firmware. - Qualcomm 802.11ax WiFi (ath11k): - IPQ5018 support - Fine Timing Measurement (FTM) responder role support - channel 177 support - MediaTek WiFi (mt76): - per-PHY LED support - mt7996: EHT (Wi-Fi 7) support - Wireless Ethernet Dispatch (WED) reset support - switch to using page pool allocator - RealTek WiFi (rtw89): - support new version of Bluetooth co-existance - Mobile: - rmnet: support TX aggregation" * tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits) page_pool: add a comment explaining the fragment counter usage net: ethtool: fix __ethtool_dev_mm_supported() implementation ethtool: pse-pd: Fix double word in comments xsk: add linux/vmalloc.h to xsk.c sefltests: netdevsim: wait for devlink instance after netns removal selftest: fib_tests: Always cleanup before exit net/mlx5e: Align IPsec ASO result memory to be as required by hardware net/mlx5e: TC, Set CT miss to the specific ct action instance net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG net/mlx5: Refactor tc miss handling to a single function net/mlx5: Kconfig: Make tc offload depend on tc skb extension net/sched: flower: Support hardware miss to tc action net/sched: flower: Move filter handle initialization earlier net/sched: cls_api: Support hardware miss to tc action net/sched: Rename user cookie and act cookie sfc: fix builds without CONFIG_RTC_LIB sfc: clean up some inconsistent indentings net/mlx4_en: Introduce flexible array to silence overflow warning net: lan966x: Fix possible deadlock inside PTP net/ulp: Remove redundant ->clone() test in inet_clone_ulp(). ...
Diffstat (limited to 'sound/soc/fsl/imx-pcm-rpmsg.c')
-rw-r--r--sound/soc/fsl/imx-pcm-rpmsg.c837
1 files changed, 837 insertions, 0 deletions
diff --git a/sound/soc/fsl/imx-pcm-rpmsg.c b/sound/soc/fsl/imx-pcm-rpmsg.c
new file mode 100644
index 000000000..2f310994f
--- /dev/null
+++ b/sound/soc/fsl/imx-pcm-rpmsg.c
@@ -0,0 +1,837 @@
+// 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 struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ 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;
+ int ret = 0;
+
+ 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 ret;
+}
+
+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 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);
+
+ imx_rpmsg_pcm_hardware.period_bytes_max =
+ imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;
+
+ snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_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 = asoc_substream_to_rtd(substream);
+ struct rpmsg_info *info = dev_get_drvdata(component->dev);
+ struct rpmsg_msg *msg;
+ int ret = 0;
+
+ /* 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 ret;
+}
+
+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 = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_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 = substream->private_data;
+ struct snd_soc_dai *cpu_dai = asoc_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 = asoc_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;
+
+ imx_rpmsg_pcm_hardware.buffer_bytes_max = rpmsg->buffer_size;
+ 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;
+ }
+
+ /* platform component name is used by machine driver to link with */
+ component->name = info->rpdev->id.name;
+
+#ifdef CONFIG_DEBUG_FS
+ component->debugfs_prefix = "rpmsg";
+#endif
+
+ return 0;
+
+fail:
+ if (info->rpmsg_wq)
+ destroy_workqueue(info->rpmsg_wq);
+
+ return ret;
+}
+
+static int 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);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+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;
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+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;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend,
+ imx_rpmsg_pcm_runtime_resume,
+ NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend,
+ imx_rpmsg_pcm_resume)
+};
+
+static struct platform_driver imx_pcm_rpmsg_driver = {
+ .probe = imx_rpmsg_pcm_probe,
+ .remove = imx_rpmsg_pcm_remove,
+ .driver = {
+ .name = IMX_PCM_DRV_NAME,
+ .pm = &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");