diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /sound/soc/sprd/sprd-pcm-compress.c | |
download | linux-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/sprd/sprd-pcm-compress.c')
-rw-r--r-- | sound/soc/sprd/sprd-pcm-compress.c | 671 |
1 files changed, 671 insertions, 0 deletions
diff --git a/sound/soc/sprd/sprd-pcm-compress.c b/sound/soc/sprd/sprd-pcm-compress.c new file mode 100644 index 000000000..6507c03cc --- /dev/null +++ b/sound/soc/sprd/sprd-pcm-compress.c @@ -0,0 +1,671 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2019 Spreadtrum Communications Inc. + +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/dma/sprd-dma.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/compress_driver.h> + +#include "sprd-pcm-dma.h" + +#define SPRD_COMPR_DMA_CHANS 2 + +/* Default values if userspace does not set */ +#define SPRD_COMPR_MIN_FRAGMENT_SIZE SZ_8K +#define SPRD_COMPR_MAX_FRAGMENT_SIZE SZ_128K +#define SPRD_COMPR_MIN_NUM_FRAGMENTS 4 +#define SPRD_COMPR_MAX_NUM_FRAGMENTS 64 + +/* DSP FIFO size */ +#define SPRD_COMPR_MCDT_EMPTY_WMK 0 +#define SPRD_COMPR_MCDT_FIFO_SIZE 512 + +/* Stage 0 IRAM buffer size definition */ +#define SPRD_COMPR_IRAM_BUF_SIZE SZ_32K +#define SPRD_COMPR_IRAM_INFO_SIZE (sizeof(struct sprd_compr_playinfo)) +#define SPRD_COMPR_IRAM_LINKLIST_SIZE (1024 - SPRD_COMPR_IRAM_INFO_SIZE) +#define SPRD_COMPR_IRAM_SIZE (SPRD_COMPR_IRAM_BUF_SIZE + \ + SPRD_COMPR_IRAM_INFO_SIZE + \ + SPRD_COMPR_IRAM_LINKLIST_SIZE) + +/* Stage 1 DDR buffer size definition */ +#define SPRD_COMPR_AREA_BUF_SIZE SZ_2M +#define SPRD_COMPR_AREA_LINKLIST_SIZE 1024 +#define SPRD_COMPR_AREA_SIZE (SPRD_COMPR_AREA_BUF_SIZE + \ + SPRD_COMPR_AREA_LINKLIST_SIZE) + +struct sprd_compr_dma { + struct dma_chan *chan; + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + dma_addr_t phys; + void *virt; + int trans_len; +}; + +/* + * The Spreadtrum Audio compress offload mode will use 2-stage DMA transfer to + * save power. That means we can request 2 dma channels, one for source channel, + * and another one for destination channel. Once the source channel's transaction + * is done, it will trigger the destination channel's transaction automatically + * by hardware signal. + * + * For 2-stage DMA transfer, we can allocate 2 buffers: IRAM buffer (always + * power-on) and DDR buffer. The source channel will transfer data from IRAM + * buffer to the DSP fifo to decoding/encoding, once IRAM buffer is empty by + * transferring done, the destination channel will start to transfer data from + * DDR buffer to IRAM buffer. + * + * Since the DSP fifo is only 512B, IRAM buffer is allocated by 32K, and DDR + * buffer is larger to 2M. That means only the IRAM 32k data is transferred + * done, we can wake up the AP system to transfer data from DDR to IRAM, and + * other time the AP system can be suspended to save power. + */ +struct sprd_compr_stream { + struct snd_compr_stream *cstream; + struct sprd_compr_ops *compr_ops; + struct sprd_compr_dma dma[SPRD_COMPR_DMA_CHANS]; + + /* DMA engine channel number */ + int num_channels; + + /* Stage 0 IRAM buffer */ + struct snd_dma_buffer iram_buffer; + /* Stage 1 DDR buffer */ + struct snd_dma_buffer compr_buffer; + + /* DSP play information IRAM buffer */ + dma_addr_t info_phys; + void *info_area; + int info_size; + + /* Data size copied to IRAM buffer */ + int copied_total; + /* Total received data size from userspace */ + int received_total; + /* Stage 0 IRAM buffer received data size */ + int received_stage0; + /* Stage 1 DDR buffer received data size */ + int received_stage1; + /* Stage 1 DDR buffer pointer */ + int stage1_pointer; +}; + +static int sprd_platform_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + int cmd); + +static void sprd_platform_compr_drain_notify(void *arg) +{ + struct snd_compr_stream *cstream = arg; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + + memset(stream->info_area, 0, sizeof(struct sprd_compr_playinfo)); + + snd_compr_drain_notify(cstream); +} + +static void sprd_platform_compr_dma_complete(void *data) +{ + struct snd_compr_stream *cstream = data; + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_dma *dma = &stream->dma[1]; + + /* Update data size copied to IRAM buffer */ + stream->copied_total += dma->trans_len; + if (stream->copied_total > stream->received_total) + stream->copied_total = stream->received_total; + + snd_compr_fragment_elapsed(cstream); +} + +static int sprd_platform_compr_dma_config(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params, + int channel) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + struct sprd_pcm_dma_params *dma_params = data->dma_params; + struct sprd_compr_dma *dma = &stream->dma[channel]; + struct dma_slave_config config = { }; + struct sprd_dma_linklist link = { }; + enum dma_transfer_direction dir; + struct scatterlist *sg, *sgt; + enum dma_slave_buswidth bus_width; + int period, period_cnt, sg_num = 2; + dma_addr_t src_addr, dst_addr; + unsigned long flags; + int ret, j; + + if (!dma_params) { + dev_err(dev, "no dma parameters setting\n"); + return -EINVAL; + } + + dma->chan = dma_request_slave_channel(dev, + dma_params->chan_name[channel]); + if (!dma->chan) { + dev_err(dev, "failed to request dma channel\n"); + return -ENODEV; + } + + sgt = sg = devm_kcalloc(dev, sg_num, sizeof(*sg), GFP_KERNEL); + if (!sg) { + ret = -ENOMEM; + goto sg_err; + } + + switch (channel) { + case 0: + bus_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + period = (SPRD_COMPR_MCDT_FIFO_SIZE - SPRD_COMPR_MCDT_EMPTY_WMK) * 4; + period_cnt = params->buffer.fragment_size / period; + src_addr = stream->iram_buffer.addr; + dst_addr = dma_params->dev_phys[channel]; + flags = SPRD_DMA_FLAGS(SPRD_DMA_SRC_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + case 1: + bus_width = DMA_SLAVE_BUSWIDTH_2_BYTES; + period = params->buffer.fragment_size; + period_cnt = params->buffer.fragments; + src_addr = stream->compr_buffer.addr; + dst_addr = stream->iram_buffer.addr; + flags = SPRD_DMA_FLAGS(SPRD_DMA_DST_CHN1, + SPRD_DMA_TRANS_DONE_TRG, + SPRD_DMA_FRAG_REQ, + SPRD_DMA_TRANS_INT); + break; + + default: + ret = -EINVAL; + goto config_err; + } + + dma->trans_len = period * period_cnt; + + config.src_maxburst = period; + config.src_addr_width = bus_width; + config.dst_addr_width = bus_width; + if (cstream->direction == SND_COMPRESS_PLAYBACK) { + config.src_addr = src_addr; + config.dst_addr = dst_addr; + dir = DMA_MEM_TO_DEV; + } else { + config.src_addr = dst_addr; + config.dst_addr = src_addr; + dir = DMA_DEV_TO_MEM; + } + + sg_init_table(sgt, sg_num); + for (j = 0; j < sg_num; j++, sgt++) { + sg_dma_len(sgt) = dma->trans_len; + sg_dma_address(sgt) = dst_addr; + } + + /* + * Configure the link-list address for the DMA engine link-list + * mode. + */ + link.virt_addr = (unsigned long)dma->virt; + link.phy_addr = dma->phys; + + ret = dmaengine_slave_config(dma->chan, &config); + if (ret) { + dev_err(dev, + "failed to set slave configuration: %d\n", ret); + goto config_err; + } + + /* + * We configure the DMA request mode, interrupt mode, channel + * mode and channel trigger mode by the flags. + */ + dma->desc = dma->chan->device->device_prep_slave_sg(dma->chan, sg, + sg_num, dir, + flags, &link); + if (!dma->desc) { + dev_err(dev, "failed to prepare slave sg\n"); + ret = -ENOMEM; + goto config_err; + } + + /* Only channel 1 transfer can wake up the AP system. */ + if (!params->no_wake_mode && channel == 1) { + dma->desc->callback = sprd_platform_compr_dma_complete; + dma->desc->callback_param = cstream; + } + + devm_kfree(dev, sg); + + return 0; + +config_err: + devm_kfree(dev, sg); +sg_err: + dma_release_channel(dma->chan); + return ret; +} + +static int sprd_platform_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct device *dev = component->dev; + struct sprd_compr_params compr_params = { }; + int ret; + + /* + * Configure the DMA engine 2-stage transfer mode. Channel 1 set as the + * destination channel, and channel 0 set as the source channel, that + * means once the source channel's transaction is done, it will trigger + * the destination channel's transaction automatically. + */ + ret = sprd_platform_compr_dma_config(component, cstream, params, 1); + if (ret) { + dev_err(dev, "failed to config stage 1 DMA: %d\n", ret); + return ret; + } + + ret = sprd_platform_compr_dma_config(component, cstream, params, 0); + if (ret) { + dev_err(dev, "failed to config stage 0 DMA: %d\n", ret); + goto config_err; + } + + compr_params.direction = cstream->direction; + compr_params.sample_rate = params->codec.sample_rate; + compr_params.channels = stream->num_channels; + compr_params.info_phys = stream->info_phys; + compr_params.info_size = stream->info_size; + compr_params.rate = params->codec.bit_rate; + compr_params.format = params->codec.id; + + ret = stream->compr_ops->set_params(cstream->direction, &compr_params); + if (ret) { + dev_err(dev, "failed to set parameters: %d\n", ret); + goto params_err; + } + + return 0; + +params_err: + dma_release_channel(stream->dma[0].chan); +config_err: + dma_release_channel(stream->dma[1].chan); + return ret; +} + +static int sprd_platform_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct snd_soc_pcm_runtime *rtd = cstream->private_data; + struct device *dev = component->dev; + struct sprd_compr_data *data = snd_soc_dai_get_drvdata(asoc_rtd_to_cpu(rtd, 0)); + struct sprd_compr_stream *stream; + struct sprd_compr_callback cb; + int stream_id = cstream->direction, ret; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + stream = devm_kzalloc(dev, sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->cstream = cstream; + stream->num_channels = 2; + stream->compr_ops = data->ops; + + /* + * Allocate the stage 0 IRAM buffer size, including the DMA 0 + * link-list size and play information of DSP address size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV_IRAM, dev, + SPRD_COMPR_IRAM_SIZE, &stream->iram_buffer); + if (ret < 0) + goto err_iram; + + /* Use to save link-list configuration for DMA 0. */ + stream->dma[0].virt = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE; + stream->dma[0].phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE; + + /* Use to update the current data offset of DSP. */ + stream->info_phys = stream->iram_buffer.addr + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_area = stream->iram_buffer.area + SPRD_COMPR_IRAM_SIZE + + SPRD_COMPR_IRAM_LINKLIST_SIZE; + stream->info_size = SPRD_COMPR_IRAM_INFO_SIZE; + + /* + * Allocate the stage 1 DDR buffer size, including the DMA 1 link-list + * size. + */ + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, + SPRD_COMPR_AREA_SIZE, &stream->compr_buffer); + if (ret < 0) + goto err_compr; + + /* Use to save link-list configuration for DMA 1. */ + stream->dma[1].virt = stream->compr_buffer.area + SPRD_COMPR_AREA_SIZE; + stream->dma[1].phys = stream->compr_buffer.addr + SPRD_COMPR_AREA_SIZE; + + cb.drain_notify = sprd_platform_compr_drain_notify; + cb.drain_data = cstream; + ret = stream->compr_ops->open(stream_id, &cb); + if (ret) { + dev_err(dev, "failed to open compress platform: %d\n", ret); + goto err_open; + } + + runtime->private_data = stream; + return 0; + +err_open: + snd_dma_free_pages(&stream->compr_buffer); +err_compr: + snd_dma_free_pages(&stream->iram_buffer); +err_iram: + devm_kfree(dev, stream); + + return ret; +} + +static int sprd_platform_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *cstream) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct device *dev = component->dev; + int stream_id = cstream->direction, i; + + for (i = 0; i < stream->num_channels; i++) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) { + dma_release_channel(dma->chan); + dma->chan = NULL; + } + } + + snd_dma_free_pages(&stream->compr_buffer); + snd_dma_free_pages(&stream->iram_buffer); + + stream->compr_ops->close(stream_id); + + devm_kfree(dev, stream); + return 0; +} + +static int sprd_platform_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + int cmd) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct device *dev = component->dev; + int channels = stream->num_channels, ret = 0, i; + int stream_id = cstream->direction; + + if (cstream->direction != SND_COMPRESS_PLAYBACK) { + dev_err(dev, "unsupported compress direction\n"); + return -EINVAL; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (!dma->desc) + continue; + + dma->cookie = dmaengine_submit(dma->desc); + ret = dma_submit_error(dma->cookie); + if (ret) { + dev_err(dev, "failed to submit request: %d\n", + ret); + return ret; + } + } + + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dma_async_issue_pending(dma->chan); + } + + ret = stream->compr_ops->start(stream_id); + break; + + case SNDRV_PCM_TRIGGER_STOP: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_terminate_async(dma->chan); + } + + stream->copied_total = 0; + stream->stage1_pointer = 0; + stream->received_total = 0; + stream->received_stage0 = 0; + stream->received_stage1 = 0; + + ret = stream->compr_ops->stop(stream_id); + break; + + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_pause(dma->chan); + } + + ret = stream->compr_ops->pause(stream_id); + break; + + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + for (i = channels - 1; i >= 0; i--) { + struct sprd_compr_dma *dma = &stream->dma[i]; + + if (dma->chan) + dmaengine_resume(dma->chan); + } + + ret = stream->compr_ops->pause_release(stream_id); + break; + + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + case SND_COMPR_TRIGGER_DRAIN: + ret = stream->compr_ops->drain(stream->received_total); + break; + + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int sprd_platform_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + struct sprd_compr_playinfo *info = + (struct sprd_compr_playinfo *)stream->info_area; + + tstamp->copied_total = stream->copied_total; + tstamp->pcm_io_frames = info->current_data_offset; + + return 0; +} + +static int sprd_platform_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + char __user *buf, size_t count) +{ + struct snd_compr_runtime *runtime = cstream->runtime; + struct sprd_compr_stream *stream = runtime->private_data; + int avail_bytes, data_count = count; + void *dst; + + /* + * We usually set fragment size as 32K, and the stage 0 IRAM buffer + * size is 32K too. So if now the received data size of the stage 0 + * IRAM buffer is less than 32K, that means we have some available + * spaces for the stage 0 IRAM buffer. + */ + if (stream->received_stage0 < runtime->fragment_size) { + avail_bytes = runtime->fragment_size - stream->received_stage0; + dst = stream->iram_buffer.area + stream->received_stage0; + + if (avail_bytes >= data_count) { + /* + * Copy data to the stage 0 IRAM buffer directly if + * spaces are enough. + */ + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->received_stage0 += data_count; + stream->copied_total += data_count; + goto copy_done; + } else { + /* + * If the data count is larger than the available spaces + * of the stage 0 IRAM buffer, we should copy one + * partial data to the stage 0 IRAM buffer, and copy + * the left to the stage 1 DDR buffer. + */ + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + data_count -= avail_bytes; + stream->received_stage0 += avail_bytes; + stream->copied_total += avail_bytes; + buf += avail_bytes; + } + } + + /* + * Copy data to the stage 1 DDR buffer if no spaces for the stage 0 IRAM + * buffer. + */ + dst = stream->compr_buffer.area + stream->stage1_pointer; + if (data_count < stream->compr_buffer.bytes - stream->stage1_pointer) { + if (copy_from_user(dst, buf, data_count)) + return -EFAULT; + + stream->stage1_pointer += data_count; + } else { + avail_bytes = stream->compr_buffer.bytes - stream->stage1_pointer; + + if (copy_from_user(dst, buf, avail_bytes)) + return -EFAULT; + + if (copy_from_user(stream->compr_buffer.area, buf + avail_bytes, + data_count - avail_bytes)) + return -EFAULT; + + stream->stage1_pointer = data_count - avail_bytes; + } + + stream->received_stage1 += data_count; + +copy_done: + /* Update the copied data size. */ + stream->received_total += count; + return count; +} + +static int sprd_platform_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_caps *caps) +{ + caps->direction = cstream->direction; + caps->min_fragment_size = SPRD_COMPR_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = SPRD_COMPR_MAX_FRAGMENT_SIZE; + caps->min_fragments = SPRD_COMPR_MIN_NUM_FRAGMENTS; + caps->max_fragments = SPRD_COMPR_MAX_NUM_FRAGMENTS; + caps->num_codecs = 2; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_AAC; + + return 0; +} + +static int +sprd_platform_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *cstream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + codec->num_descriptors = 2; + codec->descriptor[0].max_ch = 2; + codec->descriptor[0].bit_rate[0] = 320; + codec->descriptor[0].bit_rate[1] = 128; + codec->descriptor[0].num_bitrates = 2; + codec->descriptor[0].profiles = 0; + codec->descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO; + codec->descriptor[0].formats = 0; + break; + + case SND_AUDIOCODEC_AAC: + codec->num_descriptors = 2; + codec->descriptor[1].max_ch = 2; + codec->descriptor[1].bit_rate[0] = 320; + codec->descriptor[1].bit_rate[1] = 128; + codec->descriptor[1].num_bitrates = 2; + codec->descriptor[1].profiles = 0; + codec->descriptor[1].modes = 0; + codec->descriptor[1].formats = 0; + break; + + default: + return -EINVAL; + } + + return 0; +} + +const struct snd_compress_ops sprd_platform_compress_ops = { + .open = sprd_platform_compr_open, + .free = sprd_platform_compr_free, + .set_params = sprd_platform_compr_set_params, + .trigger = sprd_platform_compr_trigger, + .pointer = sprd_platform_compr_pointer, + .copy = sprd_platform_compr_copy, + .get_caps = sprd_platform_compr_get_caps, + .get_codec_caps = sprd_platform_compr_get_codec_caps, +}; + +MODULE_DESCRIPTION("Spreadtrum ASoC Compress Platform Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:compress-platform"); |