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 <linux/module.h> 762306a36Sopenharmony_ci#include <linux/moduleparam.h> 862306a36Sopenharmony_ci#include <linux/virtio_config.h> 962306a36Sopenharmony_ci#include <sound/initval.h> 1062306a36Sopenharmony_ci#include <uapi/linux/virtio_ids.h> 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include "virtio_card.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ciu32 virtsnd_msg_timeout_ms = MSEC_PER_SEC; 1562306a36Sopenharmony_cimodule_param_named(msg_timeout_ms, virtsnd_msg_timeout_ms, uint, 0644); 1662306a36Sopenharmony_ciMODULE_PARM_DESC(msg_timeout_ms, "Message completion timeout in milliseconds"); 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_cistatic void virtsnd_remove(struct virtio_device *vdev); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/** 2162306a36Sopenharmony_ci * virtsnd_event_send() - Add an event to the event queue. 2262306a36Sopenharmony_ci * @vqueue: Underlying event virtqueue. 2362306a36Sopenharmony_ci * @event: Event. 2462306a36Sopenharmony_ci * @notify: Indicates whether or not to send a notification to the device. 2562306a36Sopenharmony_ci * @gfp: Kernel flags for memory allocation. 2662306a36Sopenharmony_ci * 2762306a36Sopenharmony_ci * Context: Any context. 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_cistatic void virtsnd_event_send(struct virtqueue *vqueue, 3062306a36Sopenharmony_ci struct virtio_snd_event *event, bool notify, 3162306a36Sopenharmony_ci gfp_t gfp) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct scatterlist sg; 3462306a36Sopenharmony_ci struct scatterlist *psgs[1] = { &sg }; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* reset event content */ 3762306a36Sopenharmony_ci memset(event, 0, sizeof(*event)); 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci sg_init_one(&sg, event, sizeof(*event)); 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci if (virtqueue_add_sgs(vqueue, psgs, 0, 1, event, gfp) || !notify) 4262306a36Sopenharmony_ci return; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (virtqueue_kick_prepare(vqueue)) 4562306a36Sopenharmony_ci virtqueue_notify(vqueue); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci/** 4962306a36Sopenharmony_ci * virtsnd_event_dispatch() - Dispatch an event from the device side. 5062306a36Sopenharmony_ci * @snd: VirtIO sound device. 5162306a36Sopenharmony_ci * @event: VirtIO sound event. 5262306a36Sopenharmony_ci * 5362306a36Sopenharmony_ci * Context: Any context. 5462306a36Sopenharmony_ci */ 5562306a36Sopenharmony_cistatic void virtsnd_event_dispatch(struct virtio_snd *snd, 5662306a36Sopenharmony_ci struct virtio_snd_event *event) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci switch (le32_to_cpu(event->hdr.code)) { 5962306a36Sopenharmony_ci case VIRTIO_SND_EVT_JACK_CONNECTED: 6062306a36Sopenharmony_ci case VIRTIO_SND_EVT_JACK_DISCONNECTED: 6162306a36Sopenharmony_ci virtsnd_jack_event(snd, event); 6262306a36Sopenharmony_ci break; 6362306a36Sopenharmony_ci case VIRTIO_SND_EVT_PCM_PERIOD_ELAPSED: 6462306a36Sopenharmony_ci case VIRTIO_SND_EVT_PCM_XRUN: 6562306a36Sopenharmony_ci virtsnd_pcm_event(snd, event); 6662306a36Sopenharmony_ci break; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci/** 7162306a36Sopenharmony_ci * virtsnd_event_notify_cb() - Dispatch all reported events from the event queue. 7262306a36Sopenharmony_ci * @vqueue: Underlying event virtqueue. 7362306a36Sopenharmony_ci * 7462306a36Sopenharmony_ci * This callback function is called upon a vring interrupt request from the 7562306a36Sopenharmony_ci * device. 7662306a36Sopenharmony_ci * 7762306a36Sopenharmony_ci * Context: Interrupt context. 7862306a36Sopenharmony_ci */ 7962306a36Sopenharmony_cistatic void virtsnd_event_notify_cb(struct virtqueue *vqueue) 8062306a36Sopenharmony_ci{ 8162306a36Sopenharmony_ci struct virtio_snd *snd = vqueue->vdev->priv; 8262306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_event_queue(snd); 8362306a36Sopenharmony_ci struct virtio_snd_event *event; 8462306a36Sopenharmony_ci u32 length; 8562306a36Sopenharmony_ci unsigned long flags; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci spin_lock_irqsave(&queue->lock, flags); 8862306a36Sopenharmony_ci do { 8962306a36Sopenharmony_ci virtqueue_disable_cb(vqueue); 9062306a36Sopenharmony_ci while ((event = virtqueue_get_buf(vqueue, &length))) { 9162306a36Sopenharmony_ci virtsnd_event_dispatch(snd, event); 9262306a36Sopenharmony_ci virtsnd_event_send(vqueue, event, true, GFP_ATOMIC); 9362306a36Sopenharmony_ci } 9462306a36Sopenharmony_ci if (unlikely(virtqueue_is_broken(vqueue))) 9562306a36Sopenharmony_ci break; 9662306a36Sopenharmony_ci } while (!virtqueue_enable_cb(vqueue)); 9762306a36Sopenharmony_ci spin_unlock_irqrestore(&queue->lock, flags); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/** 10162306a36Sopenharmony_ci * virtsnd_find_vqs() - Enumerate and initialize all virtqueues. 10262306a36Sopenharmony_ci * @snd: VirtIO sound device. 10362306a36Sopenharmony_ci * 10462306a36Sopenharmony_ci * After calling this function, the event queue is disabled. 10562306a36Sopenharmony_ci * 10662306a36Sopenharmony_ci * Context: Any context. 10762306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 10862306a36Sopenharmony_ci */ 10962306a36Sopenharmony_cistatic int virtsnd_find_vqs(struct virtio_snd *snd) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 11262306a36Sopenharmony_ci static vq_callback_t *callbacks[VIRTIO_SND_VQ_MAX] = { 11362306a36Sopenharmony_ci [VIRTIO_SND_VQ_CONTROL] = virtsnd_ctl_notify_cb, 11462306a36Sopenharmony_ci [VIRTIO_SND_VQ_EVENT] = virtsnd_event_notify_cb, 11562306a36Sopenharmony_ci [VIRTIO_SND_VQ_TX] = virtsnd_pcm_tx_notify_cb, 11662306a36Sopenharmony_ci [VIRTIO_SND_VQ_RX] = virtsnd_pcm_rx_notify_cb 11762306a36Sopenharmony_ci }; 11862306a36Sopenharmony_ci static const char *names[VIRTIO_SND_VQ_MAX] = { 11962306a36Sopenharmony_ci [VIRTIO_SND_VQ_CONTROL] = "virtsnd-ctl", 12062306a36Sopenharmony_ci [VIRTIO_SND_VQ_EVENT] = "virtsnd-event", 12162306a36Sopenharmony_ci [VIRTIO_SND_VQ_TX] = "virtsnd-tx", 12262306a36Sopenharmony_ci [VIRTIO_SND_VQ_RX] = "virtsnd-rx" 12362306a36Sopenharmony_ci }; 12462306a36Sopenharmony_ci struct virtqueue *vqs[VIRTIO_SND_VQ_MAX] = { 0 }; 12562306a36Sopenharmony_ci unsigned int i; 12662306a36Sopenharmony_ci unsigned int n; 12762306a36Sopenharmony_ci int rc; 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci rc = virtio_find_vqs(vdev, VIRTIO_SND_VQ_MAX, vqs, callbacks, names, 13062306a36Sopenharmony_ci NULL); 13162306a36Sopenharmony_ci if (rc) { 13262306a36Sopenharmony_ci dev_err(&vdev->dev, "failed to initialize virtqueues\n"); 13362306a36Sopenharmony_ci return rc; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) 13762306a36Sopenharmony_ci snd->queues[i].vqueue = vqs[i]; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Allocate events and populate the event queue */ 14062306a36Sopenharmony_ci virtqueue_disable_cb(vqs[VIRTIO_SND_VQ_EVENT]); 14162306a36Sopenharmony_ci 14262306a36Sopenharmony_ci n = virtqueue_get_vring_size(vqs[VIRTIO_SND_VQ_EVENT]); 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci snd->event_msgs = kmalloc_array(n, sizeof(*snd->event_msgs), 14562306a36Sopenharmony_ci GFP_KERNEL); 14662306a36Sopenharmony_ci if (!snd->event_msgs) 14762306a36Sopenharmony_ci return -ENOMEM; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci for (i = 0; i < n; ++i) 15062306a36Sopenharmony_ci virtsnd_event_send(vqs[VIRTIO_SND_VQ_EVENT], 15162306a36Sopenharmony_ci &snd->event_msgs[i], false, GFP_KERNEL); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci return 0; 15462306a36Sopenharmony_ci} 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci/** 15762306a36Sopenharmony_ci * virtsnd_enable_event_vq() - Enable the event virtqueue. 15862306a36Sopenharmony_ci * @snd: VirtIO sound device. 15962306a36Sopenharmony_ci * 16062306a36Sopenharmony_ci * Context: Any context. 16162306a36Sopenharmony_ci */ 16262306a36Sopenharmony_cistatic void virtsnd_enable_event_vq(struct virtio_snd *snd) 16362306a36Sopenharmony_ci{ 16462306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_event_queue(snd); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci if (!virtqueue_enable_cb(queue->vqueue)) 16762306a36Sopenharmony_ci virtsnd_event_notify_cb(queue->vqueue); 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci/** 17162306a36Sopenharmony_ci * virtsnd_disable_event_vq() - Disable the event virtqueue. 17262306a36Sopenharmony_ci * @snd: VirtIO sound device. 17362306a36Sopenharmony_ci * 17462306a36Sopenharmony_ci * Context: Any context. 17562306a36Sopenharmony_ci */ 17662306a36Sopenharmony_cistatic void virtsnd_disable_event_vq(struct virtio_snd *snd) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci struct virtio_snd_queue *queue = virtsnd_event_queue(snd); 17962306a36Sopenharmony_ci struct virtio_snd_event *event; 18062306a36Sopenharmony_ci u32 length; 18162306a36Sopenharmony_ci unsigned long flags; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci if (queue->vqueue) { 18462306a36Sopenharmony_ci spin_lock_irqsave(&queue->lock, flags); 18562306a36Sopenharmony_ci virtqueue_disable_cb(queue->vqueue); 18662306a36Sopenharmony_ci while ((event = virtqueue_get_buf(queue->vqueue, &length))) 18762306a36Sopenharmony_ci virtsnd_event_dispatch(snd, event); 18862306a36Sopenharmony_ci spin_unlock_irqrestore(&queue->lock, flags); 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci/** 19362306a36Sopenharmony_ci * virtsnd_build_devs() - Read configuration and build ALSA devices. 19462306a36Sopenharmony_ci * @snd: VirtIO sound device. 19562306a36Sopenharmony_ci * 19662306a36Sopenharmony_ci * Context: Any context that permits to sleep. 19762306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 19862306a36Sopenharmony_ci */ 19962306a36Sopenharmony_cistatic int virtsnd_build_devs(struct virtio_snd *snd) 20062306a36Sopenharmony_ci{ 20162306a36Sopenharmony_ci struct virtio_device *vdev = snd->vdev; 20262306a36Sopenharmony_ci struct device *dev = &vdev->dev; 20362306a36Sopenharmony_ci int rc; 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci rc = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, 20662306a36Sopenharmony_ci THIS_MODULE, 0, &snd->card); 20762306a36Sopenharmony_ci if (rc < 0) 20862306a36Sopenharmony_ci return rc; 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_ci snd->card->private_data = snd; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci strscpy(snd->card->driver, VIRTIO_SND_CARD_DRIVER, 21362306a36Sopenharmony_ci sizeof(snd->card->driver)); 21462306a36Sopenharmony_ci strscpy(snd->card->shortname, VIRTIO_SND_CARD_NAME, 21562306a36Sopenharmony_ci sizeof(snd->card->shortname)); 21662306a36Sopenharmony_ci if (dev->parent->bus) 21762306a36Sopenharmony_ci snprintf(snd->card->longname, sizeof(snd->card->longname), 21862306a36Sopenharmony_ci VIRTIO_SND_CARD_NAME " at %s/%s/%s", 21962306a36Sopenharmony_ci dev->parent->bus->name, dev_name(dev->parent), 22062306a36Sopenharmony_ci dev_name(dev)); 22162306a36Sopenharmony_ci else 22262306a36Sopenharmony_ci snprintf(snd->card->longname, sizeof(snd->card->longname), 22362306a36Sopenharmony_ci VIRTIO_SND_CARD_NAME " at %s/%s", 22462306a36Sopenharmony_ci dev_name(dev->parent), dev_name(dev)); 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci rc = virtsnd_jack_parse_cfg(snd); 22762306a36Sopenharmony_ci if (rc) 22862306a36Sopenharmony_ci return rc; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci rc = virtsnd_pcm_parse_cfg(snd); 23162306a36Sopenharmony_ci if (rc) 23262306a36Sopenharmony_ci return rc; 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci rc = virtsnd_chmap_parse_cfg(snd); 23562306a36Sopenharmony_ci if (rc) 23662306a36Sopenharmony_ci return rc; 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci if (snd->njacks) { 23962306a36Sopenharmony_ci rc = virtsnd_jack_build_devs(snd); 24062306a36Sopenharmony_ci if (rc) 24162306a36Sopenharmony_ci return rc; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci if (snd->nsubstreams) { 24562306a36Sopenharmony_ci rc = virtsnd_pcm_build_devs(snd); 24662306a36Sopenharmony_ci if (rc) 24762306a36Sopenharmony_ci return rc; 24862306a36Sopenharmony_ci } 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci if (snd->nchmaps) { 25162306a36Sopenharmony_ci rc = virtsnd_chmap_build_devs(snd); 25262306a36Sopenharmony_ci if (rc) 25362306a36Sopenharmony_ci return rc; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return snd_card_register(snd->card); 25762306a36Sopenharmony_ci} 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci/** 26062306a36Sopenharmony_ci * virtsnd_validate() - Validate if the device can be started. 26162306a36Sopenharmony_ci * @vdev: VirtIO parent device. 26262306a36Sopenharmony_ci * 26362306a36Sopenharmony_ci * Context: Any context. 26462306a36Sopenharmony_ci * Return: 0 on success, -EINVAL on failure. 26562306a36Sopenharmony_ci */ 26662306a36Sopenharmony_cistatic int virtsnd_validate(struct virtio_device *vdev) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci if (!vdev->config->get) { 26962306a36Sopenharmony_ci dev_err(&vdev->dev, "configuration access disabled\n"); 27062306a36Sopenharmony_ci return -EINVAL; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) { 27462306a36Sopenharmony_ci dev_err(&vdev->dev, 27562306a36Sopenharmony_ci "device does not comply with spec version 1.x\n"); 27662306a36Sopenharmony_ci return -EINVAL; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (!virtsnd_msg_timeout_ms) { 28062306a36Sopenharmony_ci dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n"); 28162306a36Sopenharmony_ci return -EINVAL; 28262306a36Sopenharmony_ci } 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci if (virtsnd_pcm_validate(vdev)) 28562306a36Sopenharmony_ci return -EINVAL; 28662306a36Sopenharmony_ci 28762306a36Sopenharmony_ci return 0; 28862306a36Sopenharmony_ci} 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci/** 29162306a36Sopenharmony_ci * virtsnd_probe() - Create and initialize the device. 29262306a36Sopenharmony_ci * @vdev: VirtIO parent device. 29362306a36Sopenharmony_ci * 29462306a36Sopenharmony_ci * Context: Any context that permits to sleep. 29562306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 29662306a36Sopenharmony_ci */ 29762306a36Sopenharmony_cistatic int virtsnd_probe(struct virtio_device *vdev) 29862306a36Sopenharmony_ci{ 29962306a36Sopenharmony_ci struct virtio_snd *snd; 30062306a36Sopenharmony_ci unsigned int i; 30162306a36Sopenharmony_ci int rc; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci snd = devm_kzalloc(&vdev->dev, sizeof(*snd), GFP_KERNEL); 30462306a36Sopenharmony_ci if (!snd) 30562306a36Sopenharmony_ci return -ENOMEM; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci snd->vdev = vdev; 30862306a36Sopenharmony_ci INIT_LIST_HEAD(&snd->ctl_msgs); 30962306a36Sopenharmony_ci INIT_LIST_HEAD(&snd->pcm_list); 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci vdev->priv = snd; 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_ci for (i = 0; i < VIRTIO_SND_VQ_MAX; ++i) 31462306a36Sopenharmony_ci spin_lock_init(&snd->queues[i].lock); 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci rc = virtsnd_find_vqs(snd); 31762306a36Sopenharmony_ci if (rc) 31862306a36Sopenharmony_ci goto on_exit; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci virtio_device_ready(vdev); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci rc = virtsnd_build_devs(snd); 32362306a36Sopenharmony_ci if (rc) 32462306a36Sopenharmony_ci goto on_exit; 32562306a36Sopenharmony_ci 32662306a36Sopenharmony_ci virtsnd_enable_event_vq(snd); 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_cion_exit: 32962306a36Sopenharmony_ci if (rc) 33062306a36Sopenharmony_ci virtsnd_remove(vdev); 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci return rc; 33362306a36Sopenharmony_ci} 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci/** 33662306a36Sopenharmony_ci * virtsnd_remove() - Remove VirtIO and ALSA devices. 33762306a36Sopenharmony_ci * @vdev: VirtIO parent device. 33862306a36Sopenharmony_ci * 33962306a36Sopenharmony_ci * Context: Any context that permits to sleep. 34062306a36Sopenharmony_ci */ 34162306a36Sopenharmony_cistatic void virtsnd_remove(struct virtio_device *vdev) 34262306a36Sopenharmony_ci{ 34362306a36Sopenharmony_ci struct virtio_snd *snd = vdev->priv; 34462306a36Sopenharmony_ci unsigned int i; 34562306a36Sopenharmony_ci 34662306a36Sopenharmony_ci virtsnd_disable_event_vq(snd); 34762306a36Sopenharmony_ci virtsnd_ctl_msg_cancel_all(snd); 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci if (snd->card) 35062306a36Sopenharmony_ci snd_card_free(snd->card); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci vdev->config->del_vqs(vdev); 35362306a36Sopenharmony_ci virtio_reset_device(vdev); 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci for (i = 0; snd->substreams && i < snd->nsubstreams; ++i) { 35662306a36Sopenharmony_ci struct virtio_pcm_substream *vss = &snd->substreams[i]; 35762306a36Sopenharmony_ci 35862306a36Sopenharmony_ci cancel_work_sync(&vss->elapsed_period); 35962306a36Sopenharmony_ci virtsnd_pcm_msg_free(vss); 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci kfree(snd->event_msgs); 36362306a36Sopenharmony_ci} 36462306a36Sopenharmony_ci 36562306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 36662306a36Sopenharmony_ci/** 36762306a36Sopenharmony_ci * virtsnd_freeze() - Suspend device. 36862306a36Sopenharmony_ci * @vdev: VirtIO parent device. 36962306a36Sopenharmony_ci * 37062306a36Sopenharmony_ci * Context: Any context. 37162306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 37262306a36Sopenharmony_ci */ 37362306a36Sopenharmony_cistatic int virtsnd_freeze(struct virtio_device *vdev) 37462306a36Sopenharmony_ci{ 37562306a36Sopenharmony_ci struct virtio_snd *snd = vdev->priv; 37662306a36Sopenharmony_ci unsigned int i; 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci virtsnd_disable_event_vq(snd); 37962306a36Sopenharmony_ci virtsnd_ctl_msg_cancel_all(snd); 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci vdev->config->del_vqs(vdev); 38262306a36Sopenharmony_ci virtio_reset_device(vdev); 38362306a36Sopenharmony_ci 38462306a36Sopenharmony_ci for (i = 0; i < snd->nsubstreams; ++i) 38562306a36Sopenharmony_ci cancel_work_sync(&snd->substreams[i].elapsed_period); 38662306a36Sopenharmony_ci 38762306a36Sopenharmony_ci kfree(snd->event_msgs); 38862306a36Sopenharmony_ci snd->event_msgs = NULL; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_ci return 0; 39162306a36Sopenharmony_ci} 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci/** 39462306a36Sopenharmony_ci * virtsnd_restore() - Resume device. 39562306a36Sopenharmony_ci * @vdev: VirtIO parent device. 39662306a36Sopenharmony_ci * 39762306a36Sopenharmony_ci * Context: Any context. 39862306a36Sopenharmony_ci * Return: 0 on success, -errno on failure. 39962306a36Sopenharmony_ci */ 40062306a36Sopenharmony_cistatic int virtsnd_restore(struct virtio_device *vdev) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci struct virtio_snd *snd = vdev->priv; 40362306a36Sopenharmony_ci int rc; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci rc = virtsnd_find_vqs(snd); 40662306a36Sopenharmony_ci if (rc) 40762306a36Sopenharmony_ci return rc; 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci virtio_device_ready(vdev); 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci virtsnd_enable_event_vq(snd); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci return 0; 41462306a36Sopenharmony_ci} 41562306a36Sopenharmony_ci#endif /* CONFIG_PM_SLEEP */ 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic const struct virtio_device_id id_table[] = { 41862306a36Sopenharmony_ci { VIRTIO_ID_SOUND, VIRTIO_DEV_ANY_ID }, 41962306a36Sopenharmony_ci { 0 }, 42062306a36Sopenharmony_ci}; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_cistatic struct virtio_driver virtsnd_driver = { 42362306a36Sopenharmony_ci .driver.name = KBUILD_MODNAME, 42462306a36Sopenharmony_ci .driver.owner = THIS_MODULE, 42562306a36Sopenharmony_ci .id_table = id_table, 42662306a36Sopenharmony_ci .validate = virtsnd_validate, 42762306a36Sopenharmony_ci .probe = virtsnd_probe, 42862306a36Sopenharmony_ci .remove = virtsnd_remove, 42962306a36Sopenharmony_ci#ifdef CONFIG_PM_SLEEP 43062306a36Sopenharmony_ci .freeze = virtsnd_freeze, 43162306a36Sopenharmony_ci .restore = virtsnd_restore, 43262306a36Sopenharmony_ci#endif 43362306a36Sopenharmony_ci}; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cimodule_virtio_driver(virtsnd_driver); 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ciMODULE_DEVICE_TABLE(virtio, id_table); 43862306a36Sopenharmony_ciMODULE_DESCRIPTION("Virtio sound card driver"); 43962306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 440