162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * virtio-snd: Virtio sound device
462306a36Sopenharmony_ci * Copyright (C) 2021 OpenSynergy GmbH
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci#include <sound/pcm_params.h>
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "virtio_card.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/*
1162306a36Sopenharmony_ci * I/O messages lifetime
1262306a36Sopenharmony_ci * ---------------------
1362306a36Sopenharmony_ci *
1462306a36Sopenharmony_ci * Allocation:
1562306a36Sopenharmony_ci *   Messages are initially allocated in the ops->hw_params() after the size and
1662306a36Sopenharmony_ci *   number of periods have been successfully negotiated.
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci * Freeing:
1962306a36Sopenharmony_ci *   Messages can be safely freed after the queue has been successfully flushed
2062306a36Sopenharmony_ci *   (RELEASE command in the ops->sync_stop()) and the ops->hw_free() has been
2162306a36Sopenharmony_ci *   called.
2262306a36Sopenharmony_ci *
2362306a36Sopenharmony_ci *   When the substream stops, the ops->sync_stop() waits until the device has
2462306a36Sopenharmony_ci *   completed all pending messages. This wait can be interrupted either by a
2562306a36Sopenharmony_ci *   signal or due to a timeout. In this case, the device can still access
2662306a36Sopenharmony_ci *   messages even after calling ops->hw_free(). It can also issue an interrupt,
2762306a36Sopenharmony_ci *   and the interrupt handler will also try to access message structures.
2862306a36Sopenharmony_ci *
2962306a36Sopenharmony_ci *   Therefore, freeing of already allocated messages occurs:
3062306a36Sopenharmony_ci *
3162306a36Sopenharmony_ci *   - in ops->hw_params(), if this operator was called several times in a row,
3262306a36Sopenharmony_ci *     or if ops->hw_free() failed to free messages previously;
3362306a36Sopenharmony_ci *
3462306a36Sopenharmony_ci *   - in ops->hw_free(), if the queue has been successfully flushed;
3562306a36Sopenharmony_ci *
3662306a36Sopenharmony_ci *   - in dev->release().
3762306a36Sopenharmony_ci */
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* Map for converting ALSA format to VirtIO format. */
4062306a36Sopenharmony_cistruct virtsnd_a2v_format {
4162306a36Sopenharmony_ci	snd_pcm_format_t alsa_bit;
4262306a36Sopenharmony_ci	unsigned int vio_bit;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cistatic const struct virtsnd_a2v_format g_a2v_format_map[] = {
4662306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_IMA_ADPCM, VIRTIO_SND_PCM_FMT_IMA_ADPCM },
4762306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_MU_LAW, VIRTIO_SND_PCM_FMT_MU_LAW },
4862306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_A_LAW, VIRTIO_SND_PCM_FMT_A_LAW },
4962306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S8, VIRTIO_SND_PCM_FMT_S8 },
5062306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U8, VIRTIO_SND_PCM_FMT_U8 },
5162306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S16_LE, VIRTIO_SND_PCM_FMT_S16 },
5262306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U16_LE, VIRTIO_SND_PCM_FMT_U16 },
5362306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S18_3LE, VIRTIO_SND_PCM_FMT_S18_3 },
5462306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U18_3LE, VIRTIO_SND_PCM_FMT_U18_3 },
5562306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S20_3LE, VIRTIO_SND_PCM_FMT_S20_3 },
5662306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U20_3LE, VIRTIO_SND_PCM_FMT_U20_3 },
5762306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S24_3LE, VIRTIO_SND_PCM_FMT_S24_3 },
5862306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U24_3LE, VIRTIO_SND_PCM_FMT_U24_3 },
5962306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S20_LE, VIRTIO_SND_PCM_FMT_S20 },
6062306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U20_LE, VIRTIO_SND_PCM_FMT_U20 },
6162306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S24_LE, VIRTIO_SND_PCM_FMT_S24 },
6262306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U24_LE, VIRTIO_SND_PCM_FMT_U24 },
6362306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_S32_LE, VIRTIO_SND_PCM_FMT_S32 },
6462306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_U32_LE, VIRTIO_SND_PCM_FMT_U32 },
6562306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_FLOAT_LE, VIRTIO_SND_PCM_FMT_FLOAT },
6662306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_FLOAT64_LE, VIRTIO_SND_PCM_FMT_FLOAT64 },
6762306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_DSD_U8, VIRTIO_SND_PCM_FMT_DSD_U8 },
6862306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_DSD_U16_LE, VIRTIO_SND_PCM_FMT_DSD_U16 },
6962306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_DSD_U32_LE, VIRTIO_SND_PCM_FMT_DSD_U32 },
7062306a36Sopenharmony_ci	{ SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE,
7162306a36Sopenharmony_ci	  VIRTIO_SND_PCM_FMT_IEC958_SUBFRAME }
7262306a36Sopenharmony_ci};
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci/* Map for converting ALSA frame rate to VirtIO frame rate. */
7562306a36Sopenharmony_cistruct virtsnd_a2v_rate {
7662306a36Sopenharmony_ci	unsigned int rate;
7762306a36Sopenharmony_ci	unsigned int vio_bit;
7862306a36Sopenharmony_ci};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic const struct virtsnd_a2v_rate g_a2v_rate_map[] = {
8162306a36Sopenharmony_ci	{ 5512, VIRTIO_SND_PCM_RATE_5512 },
8262306a36Sopenharmony_ci	{ 8000, VIRTIO_SND_PCM_RATE_8000 },
8362306a36Sopenharmony_ci	{ 11025, VIRTIO_SND_PCM_RATE_11025 },
8462306a36Sopenharmony_ci	{ 16000, VIRTIO_SND_PCM_RATE_16000 },
8562306a36Sopenharmony_ci	{ 22050, VIRTIO_SND_PCM_RATE_22050 },
8662306a36Sopenharmony_ci	{ 32000, VIRTIO_SND_PCM_RATE_32000 },
8762306a36Sopenharmony_ci	{ 44100, VIRTIO_SND_PCM_RATE_44100 },
8862306a36Sopenharmony_ci	{ 48000, VIRTIO_SND_PCM_RATE_48000 },
8962306a36Sopenharmony_ci	{ 64000, VIRTIO_SND_PCM_RATE_64000 },
9062306a36Sopenharmony_ci	{ 88200, VIRTIO_SND_PCM_RATE_88200 },
9162306a36Sopenharmony_ci	{ 96000, VIRTIO_SND_PCM_RATE_96000 },
9262306a36Sopenharmony_ci	{ 176400, VIRTIO_SND_PCM_RATE_176400 },
9362306a36Sopenharmony_ci	{ 192000, VIRTIO_SND_PCM_RATE_192000 }
9462306a36Sopenharmony_ci};
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/**
9962306a36Sopenharmony_ci * virtsnd_pcm_open() - Open the PCM substream.
10062306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
10162306a36Sopenharmony_ci *
10262306a36Sopenharmony_ci * Context: Process context.
10362306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
10462306a36Sopenharmony_ci */
10562306a36Sopenharmony_cistatic int virtsnd_pcm_open(struct snd_pcm_substream *substream)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct virtio_pcm *vpcm = snd_pcm_substream_chip(substream);
10862306a36Sopenharmony_ci	struct virtio_pcm_stream *vs = &vpcm->streams[substream->stream];
10962306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = vs->substreams[substream->number];
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	substream->runtime->hw = vss->hw;
11262306a36Sopenharmony_ci	substream->private_data = vss;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	snd_pcm_hw_constraint_integer(substream->runtime,
11562306a36Sopenharmony_ci				      SNDRV_PCM_HW_PARAM_PERIODS);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	vss->stopped = !!virtsnd_pcm_msg_pending_num(vss);
11862306a36Sopenharmony_ci	vss->suspended = false;
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	/*
12162306a36Sopenharmony_ci	 * If the substream has already been used, then the I/O queue may be in
12262306a36Sopenharmony_ci	 * an invalid state. Just in case, we do a check and try to return the
12362306a36Sopenharmony_ci	 * queue to its original state, if necessary.
12462306a36Sopenharmony_ci	 */
12562306a36Sopenharmony_ci	return virtsnd_pcm_sync_stop(substream);
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci/**
12962306a36Sopenharmony_ci * virtsnd_pcm_close() - Close the PCM substream.
13062306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
13162306a36Sopenharmony_ci *
13262306a36Sopenharmony_ci * Context: Process context.
13362306a36Sopenharmony_ci * Return: 0.
13462306a36Sopenharmony_ci */
13562306a36Sopenharmony_cistatic int virtsnd_pcm_close(struct snd_pcm_substream *substream)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci/**
14162306a36Sopenharmony_ci * virtsnd_pcm_dev_set_params() - Set the parameters of the PCM substream on
14262306a36Sopenharmony_ci *                                the device side.
14362306a36Sopenharmony_ci * @vss: VirtIO PCM substream.
14462306a36Sopenharmony_ci * @buffer_bytes: Size of the hardware buffer.
14562306a36Sopenharmony_ci * @period_bytes: Size of the hardware period.
14662306a36Sopenharmony_ci * @channels: Selected number of channels.
14762306a36Sopenharmony_ci * @format: Selected sample format (SNDRV_PCM_FORMAT_XXX).
14862306a36Sopenharmony_ci * @rate: Selected frame rate.
14962306a36Sopenharmony_ci *
15062306a36Sopenharmony_ci * Context: Any context that permits to sleep.
15162306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
15262306a36Sopenharmony_ci */
15362306a36Sopenharmony_cistatic int virtsnd_pcm_dev_set_params(struct virtio_pcm_substream *vss,
15462306a36Sopenharmony_ci				      unsigned int buffer_bytes,
15562306a36Sopenharmony_ci				      unsigned int period_bytes,
15662306a36Sopenharmony_ci				      unsigned int channels,
15762306a36Sopenharmony_ci				      snd_pcm_format_t format,
15862306a36Sopenharmony_ci				      unsigned int rate)
15962306a36Sopenharmony_ci{
16062306a36Sopenharmony_ci	struct virtio_snd_msg *msg;
16162306a36Sopenharmony_ci	struct virtio_snd_pcm_set_params *request;
16262306a36Sopenharmony_ci	unsigned int i;
16362306a36Sopenharmony_ci	int vformat = -1;
16462306a36Sopenharmony_ci	int vrate = -1;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(g_a2v_format_map); ++i)
16762306a36Sopenharmony_ci		if (g_a2v_format_map[i].alsa_bit == format) {
16862306a36Sopenharmony_ci			vformat = g_a2v_format_map[i].vio_bit;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci			break;
17162306a36Sopenharmony_ci		}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(g_a2v_rate_map); ++i)
17462306a36Sopenharmony_ci		if (g_a2v_rate_map[i].rate == rate) {
17562306a36Sopenharmony_ci			vrate = g_a2v_rate_map[i].vio_bit;
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci			break;
17862306a36Sopenharmony_ci		}
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	if (vformat == -1 || vrate == -1)
18162306a36Sopenharmony_ci		return -EINVAL;
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_ci	msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_SET_PARAMS,
18462306a36Sopenharmony_ci					GFP_KERNEL);
18562306a36Sopenharmony_ci	if (!msg)
18662306a36Sopenharmony_ci		return -ENOMEM;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	request = virtsnd_ctl_msg_request(msg);
18962306a36Sopenharmony_ci	request->buffer_bytes = cpu_to_le32(buffer_bytes);
19062306a36Sopenharmony_ci	request->period_bytes = cpu_to_le32(period_bytes);
19162306a36Sopenharmony_ci	request->channels = channels;
19262306a36Sopenharmony_ci	request->format = vformat;
19362306a36Sopenharmony_ci	request->rate = vrate;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if (vss->features & (1U << VIRTIO_SND_PCM_F_MSG_POLLING))
19662306a36Sopenharmony_ci		request->features |=
19762306a36Sopenharmony_ci			cpu_to_le32(1U << VIRTIO_SND_PCM_F_MSG_POLLING);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	if (vss->features & (1U << VIRTIO_SND_PCM_F_EVT_XRUNS))
20062306a36Sopenharmony_ci		request->features |=
20162306a36Sopenharmony_ci			cpu_to_le32(1U << VIRTIO_SND_PCM_F_EVT_XRUNS);
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return virtsnd_ctl_msg_send_sync(vss->snd, msg);
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci/**
20762306a36Sopenharmony_ci * virtsnd_pcm_hw_params() - Set the parameters of the PCM substream.
20862306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
20962306a36Sopenharmony_ci * @hw_params: Hardware parameters.
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * Context: Process context.
21262306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
21362306a36Sopenharmony_ci */
21462306a36Sopenharmony_cistatic int virtsnd_pcm_hw_params(struct snd_pcm_substream *substream,
21562306a36Sopenharmony_ci				 struct snd_pcm_hw_params *hw_params)
21662306a36Sopenharmony_ci{
21762306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
21862306a36Sopenharmony_ci	struct virtio_device *vdev = vss->snd->vdev;
21962306a36Sopenharmony_ci	int rc;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	if (virtsnd_pcm_msg_pending_num(vss)) {
22262306a36Sopenharmony_ci		dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n",
22362306a36Sopenharmony_ci			vss->sid);
22462306a36Sopenharmony_ci		return -EBADFD;
22562306a36Sopenharmony_ci	}
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	rc = virtsnd_pcm_dev_set_params(vss, params_buffer_bytes(hw_params),
22862306a36Sopenharmony_ci					params_period_bytes(hw_params),
22962306a36Sopenharmony_ci					params_channels(hw_params),
23062306a36Sopenharmony_ci					params_format(hw_params),
23162306a36Sopenharmony_ci					params_rate(hw_params));
23262306a36Sopenharmony_ci	if (rc)
23362306a36Sopenharmony_ci		return rc;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	/*
23662306a36Sopenharmony_ci	 * Free previously allocated messages if ops->hw_params() is called
23762306a36Sopenharmony_ci	 * several times in a row, or if ops->hw_free() failed to free messages.
23862306a36Sopenharmony_ci	 */
23962306a36Sopenharmony_ci	virtsnd_pcm_msg_free(vss);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	return virtsnd_pcm_msg_alloc(vss, params_periods(hw_params),
24262306a36Sopenharmony_ci				     params_period_bytes(hw_params));
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci/**
24662306a36Sopenharmony_ci * virtsnd_pcm_hw_free() - Reset the parameters of the PCM substream.
24762306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
24862306a36Sopenharmony_ci *
24962306a36Sopenharmony_ci * Context: Process context.
25062306a36Sopenharmony_ci * Return: 0
25162306a36Sopenharmony_ci */
25262306a36Sopenharmony_cistatic int virtsnd_pcm_hw_free(struct snd_pcm_substream *substream)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	/* If the queue is flushed, we can safely free the messages here. */
25762306a36Sopenharmony_ci	if (!virtsnd_pcm_msg_pending_num(vss))
25862306a36Sopenharmony_ci		virtsnd_pcm_msg_free(vss);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	return 0;
26162306a36Sopenharmony_ci}
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci/**
26462306a36Sopenharmony_ci * virtsnd_pcm_prepare() - Prepare the PCM substream.
26562306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
26662306a36Sopenharmony_ci *
26762306a36Sopenharmony_ci * Context: Process context.
26862306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
26962306a36Sopenharmony_ci */
27062306a36Sopenharmony_cistatic int virtsnd_pcm_prepare(struct snd_pcm_substream *substream)
27162306a36Sopenharmony_ci{
27262306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
27362306a36Sopenharmony_ci	struct virtio_device *vdev = vss->snd->vdev;
27462306a36Sopenharmony_ci	struct virtio_snd_msg *msg;
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	if (!vss->suspended) {
27762306a36Sopenharmony_ci		if (virtsnd_pcm_msg_pending_num(vss)) {
27862306a36Sopenharmony_ci			dev_err(&vdev->dev, "SID %u: invalid I/O queue state\n",
27962306a36Sopenharmony_ci				vss->sid);
28062306a36Sopenharmony_ci			return -EBADFD;
28162306a36Sopenharmony_ci		}
28262306a36Sopenharmony_ci
28362306a36Sopenharmony_ci		vss->buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
28462306a36Sopenharmony_ci		vss->hw_ptr = 0;
28562306a36Sopenharmony_ci		vss->msg_last_enqueued = -1;
28662306a36Sopenharmony_ci	} else {
28762306a36Sopenharmony_ci		struct snd_pcm_runtime *runtime = substream->runtime;
28862306a36Sopenharmony_ci		unsigned int buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
28962306a36Sopenharmony_ci		unsigned int period_bytes = snd_pcm_lib_period_bytes(substream);
29062306a36Sopenharmony_ci		int rc;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci		rc = virtsnd_pcm_dev_set_params(vss, buffer_bytes, period_bytes,
29362306a36Sopenharmony_ci						runtime->channels,
29462306a36Sopenharmony_ci						runtime->format, runtime->rate);
29562306a36Sopenharmony_ci		if (rc)
29662306a36Sopenharmony_ci			return rc;
29762306a36Sopenharmony_ci	}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	vss->xfer_xrun = false;
30062306a36Sopenharmony_ci	vss->suspended = false;
30162306a36Sopenharmony_ci	vss->msg_count = 0;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_PREPARE,
30462306a36Sopenharmony_ci					GFP_KERNEL);
30562306a36Sopenharmony_ci	if (!msg)
30662306a36Sopenharmony_ci		return -ENOMEM;
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	return virtsnd_ctl_msg_send_sync(vss->snd, msg);
30962306a36Sopenharmony_ci}
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci/**
31262306a36Sopenharmony_ci * virtsnd_pcm_trigger() - Process command for the PCM substream.
31362306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
31462306a36Sopenharmony_ci * @command: Substream command (SNDRV_PCM_TRIGGER_XXX).
31562306a36Sopenharmony_ci *
31662306a36Sopenharmony_ci * Context: Any context. Takes and releases the VirtIO substream spinlock.
31762306a36Sopenharmony_ci *          May take and release the tx/rx queue spinlock.
31862306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
31962306a36Sopenharmony_ci */
32062306a36Sopenharmony_cistatic int virtsnd_pcm_trigger(struct snd_pcm_substream *substream, int command)
32162306a36Sopenharmony_ci{
32262306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
32362306a36Sopenharmony_ci	struct virtio_snd *snd = vss->snd;
32462306a36Sopenharmony_ci	struct virtio_snd_queue *queue;
32562306a36Sopenharmony_ci	struct virtio_snd_msg *msg;
32662306a36Sopenharmony_ci	unsigned long flags;
32762306a36Sopenharmony_ci	int rc;
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci	switch (command) {
33062306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_START:
33162306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
33262306a36Sopenharmony_ci		queue = virtsnd_pcm_queue(vss);
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		spin_lock_irqsave(&queue->lock, flags);
33562306a36Sopenharmony_ci		spin_lock(&vss->lock);
33662306a36Sopenharmony_ci		rc = virtsnd_pcm_msg_send(vss);
33762306a36Sopenharmony_ci		if (!rc)
33862306a36Sopenharmony_ci			vss->xfer_enabled = true;
33962306a36Sopenharmony_ci		spin_unlock(&vss->lock);
34062306a36Sopenharmony_ci		spin_unlock_irqrestore(&queue->lock, flags);
34162306a36Sopenharmony_ci		if (rc)
34262306a36Sopenharmony_ci			return rc;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci		msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_START,
34562306a36Sopenharmony_ci						GFP_KERNEL);
34662306a36Sopenharmony_ci		if (!msg) {
34762306a36Sopenharmony_ci			spin_lock_irqsave(&vss->lock, flags);
34862306a36Sopenharmony_ci			vss->xfer_enabled = false;
34962306a36Sopenharmony_ci			spin_unlock_irqrestore(&vss->lock, flags);
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci			return -ENOMEM;
35262306a36Sopenharmony_ci		}
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci		return virtsnd_ctl_msg_send_sync(snd, msg);
35562306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_SUSPEND:
35662306a36Sopenharmony_ci		vss->suspended = true;
35762306a36Sopenharmony_ci		fallthrough;
35862306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_STOP:
35962306a36Sopenharmony_ci		vss->stopped = true;
36062306a36Sopenharmony_ci		fallthrough;
36162306a36Sopenharmony_ci	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
36262306a36Sopenharmony_ci		spin_lock_irqsave(&vss->lock, flags);
36362306a36Sopenharmony_ci		vss->xfer_enabled = false;
36462306a36Sopenharmony_ci		spin_unlock_irqrestore(&vss->lock, flags);
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_ci		msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_STOP,
36762306a36Sopenharmony_ci						GFP_KERNEL);
36862306a36Sopenharmony_ci		if (!msg)
36962306a36Sopenharmony_ci			return -ENOMEM;
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci		return virtsnd_ctl_msg_send_sync(snd, msg);
37262306a36Sopenharmony_ci	default:
37362306a36Sopenharmony_ci		return -EINVAL;
37462306a36Sopenharmony_ci	}
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci/**
37862306a36Sopenharmony_ci * virtsnd_pcm_sync_stop() - Synchronous PCM substream stop.
37962306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
38062306a36Sopenharmony_ci *
38162306a36Sopenharmony_ci * The function can be called both from the upper level or from the driver
38262306a36Sopenharmony_ci * itself.
38362306a36Sopenharmony_ci *
38462306a36Sopenharmony_ci * Context: Process context. Takes and releases the VirtIO substream spinlock.
38562306a36Sopenharmony_ci * Return: 0 on success, -errno on failure.
38662306a36Sopenharmony_ci */
38762306a36Sopenharmony_cistatic int virtsnd_pcm_sync_stop(struct snd_pcm_substream *substream)
38862306a36Sopenharmony_ci{
38962306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
39062306a36Sopenharmony_ci	struct virtio_snd *snd = vss->snd;
39162306a36Sopenharmony_ci	struct virtio_snd_msg *msg;
39262306a36Sopenharmony_ci	unsigned int js = msecs_to_jiffies(virtsnd_msg_timeout_ms);
39362306a36Sopenharmony_ci	int rc;
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_ci	cancel_work_sync(&vss->elapsed_period);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	if (!vss->stopped)
39862306a36Sopenharmony_ci		return 0;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	msg = virtsnd_pcm_ctl_msg_alloc(vss, VIRTIO_SND_R_PCM_RELEASE,
40162306a36Sopenharmony_ci					GFP_KERNEL);
40262306a36Sopenharmony_ci	if (!msg)
40362306a36Sopenharmony_ci		return -ENOMEM;
40462306a36Sopenharmony_ci
40562306a36Sopenharmony_ci	rc = virtsnd_ctl_msg_send_sync(snd, msg);
40662306a36Sopenharmony_ci	if (rc)
40762306a36Sopenharmony_ci		return rc;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	/*
41062306a36Sopenharmony_ci	 * The spec states that upon receipt of the RELEASE command "the device
41162306a36Sopenharmony_ci	 * MUST complete all pending I/O messages for the specified stream ID".
41262306a36Sopenharmony_ci	 * Thus, we consider the absence of I/O messages in the queue as an
41362306a36Sopenharmony_ci	 * indication that the substream has been released.
41462306a36Sopenharmony_ci	 */
41562306a36Sopenharmony_ci	rc = wait_event_interruptible_timeout(vss->msg_empty,
41662306a36Sopenharmony_ci					      !virtsnd_pcm_msg_pending_num(vss),
41762306a36Sopenharmony_ci					      js);
41862306a36Sopenharmony_ci	if (rc <= 0) {
41962306a36Sopenharmony_ci		dev_warn(&snd->vdev->dev, "SID %u: failed to flush I/O queue\n",
42062306a36Sopenharmony_ci			 vss->sid);
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci		return !rc ? -ETIMEDOUT : rc;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci	vss->stopped = false;
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	return 0;
42862306a36Sopenharmony_ci}
42962306a36Sopenharmony_ci
43062306a36Sopenharmony_ci/**
43162306a36Sopenharmony_ci * virtsnd_pcm_pointer() - Get the current hardware position for the PCM
43262306a36Sopenharmony_ci *                         substream.
43362306a36Sopenharmony_ci * @substream: Kernel ALSA substream.
43462306a36Sopenharmony_ci *
43562306a36Sopenharmony_ci * Context: Any context. Takes and releases the VirtIO substream spinlock.
43662306a36Sopenharmony_ci * Return: Hardware position in frames inside [0 ... buffer_size) range.
43762306a36Sopenharmony_ci */
43862306a36Sopenharmony_cistatic snd_pcm_uframes_t
43962306a36Sopenharmony_civirtsnd_pcm_pointer(struct snd_pcm_substream *substream)
44062306a36Sopenharmony_ci{
44162306a36Sopenharmony_ci	struct virtio_pcm_substream *vss = snd_pcm_substream_chip(substream);
44262306a36Sopenharmony_ci	snd_pcm_uframes_t hw_ptr = SNDRV_PCM_POS_XRUN;
44362306a36Sopenharmony_ci	unsigned long flags;
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	spin_lock_irqsave(&vss->lock, flags);
44662306a36Sopenharmony_ci	if (!vss->xfer_xrun)
44762306a36Sopenharmony_ci		hw_ptr = bytes_to_frames(substream->runtime, vss->hw_ptr);
44862306a36Sopenharmony_ci	spin_unlock_irqrestore(&vss->lock, flags);
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	return hw_ptr;
45162306a36Sopenharmony_ci}
45262306a36Sopenharmony_ci
45362306a36Sopenharmony_ci/* PCM substream operators map. */
45462306a36Sopenharmony_ciconst struct snd_pcm_ops virtsnd_pcm_ops = {
45562306a36Sopenharmony_ci	.open = virtsnd_pcm_open,
45662306a36Sopenharmony_ci	.close = virtsnd_pcm_close,
45762306a36Sopenharmony_ci	.ioctl = snd_pcm_lib_ioctl,
45862306a36Sopenharmony_ci	.hw_params = virtsnd_pcm_hw_params,
45962306a36Sopenharmony_ci	.hw_free = virtsnd_pcm_hw_free,
46062306a36Sopenharmony_ci	.prepare = virtsnd_pcm_prepare,
46162306a36Sopenharmony_ci	.trigger = virtsnd_pcm_trigger,
46262306a36Sopenharmony_ci	.sync_stop = virtsnd_pcm_sync_stop,
46362306a36Sopenharmony_ci	.pointer = virtsnd_pcm_pointer,
46462306a36Sopenharmony_ci};
465