18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  Device management routines
48c2ecf20Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/slab.h>
88c2ecf20Sopenharmony_ci#include <linux/time.h>
98c2ecf20Sopenharmony_ci#include <linux/export.h>
108c2ecf20Sopenharmony_ci#include <linux/errno.h>
118c2ecf20Sopenharmony_ci#include <sound/core.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci/**
148c2ecf20Sopenharmony_ci * snd_device_new - create an ALSA device component
158c2ecf20Sopenharmony_ci * @card: the card instance
168c2ecf20Sopenharmony_ci * @type: the device type, SNDRV_DEV_XXX
178c2ecf20Sopenharmony_ci * @device_data: the data pointer of this device
188c2ecf20Sopenharmony_ci * @ops: the operator table
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci * Creates a new device component for the given data pointer.
218c2ecf20Sopenharmony_ci * The device will be assigned to the card and managed together
228c2ecf20Sopenharmony_ci * by the card.
238c2ecf20Sopenharmony_ci *
248c2ecf20Sopenharmony_ci * The data pointer plays a role as the identifier, too, so the
258c2ecf20Sopenharmony_ci * pointer address must be unique and unchanged.
268c2ecf20Sopenharmony_ci *
278c2ecf20Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure.
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ciint snd_device_new(struct snd_card *card, enum snd_device_type type,
308c2ecf20Sopenharmony_ci		   void *device_data, const struct snd_device_ops *ops)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	struct snd_device *dev;
338c2ecf20Sopenharmony_ci	struct list_head *p;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card || !device_data || !ops))
368c2ecf20Sopenharmony_ci		return -ENXIO;
378c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
388c2ecf20Sopenharmony_ci	if (!dev)
398c2ecf20Sopenharmony_ci		return -ENOMEM;
408c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&dev->list);
418c2ecf20Sopenharmony_ci	dev->card = card;
428c2ecf20Sopenharmony_ci	dev->type = type;
438c2ecf20Sopenharmony_ci	dev->state = SNDRV_DEV_BUILD;
448c2ecf20Sopenharmony_ci	dev->device_data = device_data;
458c2ecf20Sopenharmony_ci	dev->ops = ops;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	/* insert the entry in an incrementally sorted list */
488c2ecf20Sopenharmony_ci	list_for_each_prev(p, &card->devices) {
498c2ecf20Sopenharmony_ci		struct snd_device *pdev = list_entry(p, struct snd_device, list);
508c2ecf20Sopenharmony_ci		if ((unsigned int)pdev->type <= (unsigned int)type)
518c2ecf20Sopenharmony_ci			break;
528c2ecf20Sopenharmony_ci	}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	list_add(&dev->list, p);
558c2ecf20Sopenharmony_ci	return 0;
568c2ecf20Sopenharmony_ci}
578c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_device_new);
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic void __snd_device_disconnect(struct snd_device *dev)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	if (dev->state == SNDRV_DEV_REGISTERED) {
628c2ecf20Sopenharmony_ci		if (dev->ops->dev_disconnect &&
638c2ecf20Sopenharmony_ci		    dev->ops->dev_disconnect(dev))
648c2ecf20Sopenharmony_ci			dev_err(dev->card->dev, "device disconnect failure\n");
658c2ecf20Sopenharmony_ci		dev->state = SNDRV_DEV_DISCONNECTED;
668c2ecf20Sopenharmony_ci	}
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic void __snd_device_free(struct snd_device *dev)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	/* unlink */
728c2ecf20Sopenharmony_ci	list_del(&dev->list);
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	__snd_device_disconnect(dev);
758c2ecf20Sopenharmony_ci	if (dev->ops->dev_free) {
768c2ecf20Sopenharmony_ci		if (dev->ops->dev_free(dev))
778c2ecf20Sopenharmony_ci			dev_err(dev->card->dev, "device free failure\n");
788c2ecf20Sopenharmony_ci	}
798c2ecf20Sopenharmony_ci	kfree(dev);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic struct snd_device *look_for_dev(struct snd_card *card, void *device_data)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci	struct snd_device *dev;
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	list_for_each_entry(dev, &card->devices, list)
878c2ecf20Sopenharmony_ci		if (dev->device_data == device_data)
888c2ecf20Sopenharmony_ci			return dev;
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_ci	return NULL;
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci/**
948c2ecf20Sopenharmony_ci * snd_device_disconnect - disconnect the device
958c2ecf20Sopenharmony_ci * @card: the card instance
968c2ecf20Sopenharmony_ci * @device_data: the data pointer to disconnect
978c2ecf20Sopenharmony_ci *
988c2ecf20Sopenharmony_ci * Turns the device into the disconnection state, invoking
998c2ecf20Sopenharmony_ci * dev_disconnect callback, if the device was already registered.
1008c2ecf20Sopenharmony_ci *
1018c2ecf20Sopenharmony_ci * Usually called from snd_card_disconnect().
1028c2ecf20Sopenharmony_ci *
1038c2ecf20Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure or if the
1048c2ecf20Sopenharmony_ci * device not found.
1058c2ecf20Sopenharmony_ci */
1068c2ecf20Sopenharmony_civoid snd_device_disconnect(struct snd_card *card, void *device_data)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	struct snd_device *dev;
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card || !device_data))
1118c2ecf20Sopenharmony_ci		return;
1128c2ecf20Sopenharmony_ci	dev = look_for_dev(card, device_data);
1138c2ecf20Sopenharmony_ci	if (dev)
1148c2ecf20Sopenharmony_ci		__snd_device_disconnect(dev);
1158c2ecf20Sopenharmony_ci	else
1168c2ecf20Sopenharmony_ci		dev_dbg(card->dev, "device disconnect %p (from %pS), not found\n",
1178c2ecf20Sopenharmony_ci			device_data, __builtin_return_address(0));
1188c2ecf20Sopenharmony_ci}
1198c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_device_disconnect);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci/**
1228c2ecf20Sopenharmony_ci * snd_device_free - release the device from the card
1238c2ecf20Sopenharmony_ci * @card: the card instance
1248c2ecf20Sopenharmony_ci * @device_data: the data pointer to release
1258c2ecf20Sopenharmony_ci *
1268c2ecf20Sopenharmony_ci * Removes the device from the list on the card and invokes the
1278c2ecf20Sopenharmony_ci * callbacks, dev_disconnect and dev_free, corresponding to the state.
1288c2ecf20Sopenharmony_ci * Then release the device.
1298c2ecf20Sopenharmony_ci */
1308c2ecf20Sopenharmony_civoid snd_device_free(struct snd_card *card, void *device_data)
1318c2ecf20Sopenharmony_ci{
1328c2ecf20Sopenharmony_ci	struct snd_device *dev;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card || !device_data))
1358c2ecf20Sopenharmony_ci		return;
1368c2ecf20Sopenharmony_ci	dev = look_for_dev(card, device_data);
1378c2ecf20Sopenharmony_ci	if (dev)
1388c2ecf20Sopenharmony_ci		__snd_device_free(dev);
1398c2ecf20Sopenharmony_ci	else
1408c2ecf20Sopenharmony_ci		dev_dbg(card->dev, "device free %p (from %pS), not found\n",
1418c2ecf20Sopenharmony_ci			device_data, __builtin_return_address(0));
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_device_free);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int __snd_device_register(struct snd_device *dev)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	if (dev->state == SNDRV_DEV_BUILD) {
1488c2ecf20Sopenharmony_ci		if (dev->ops->dev_register) {
1498c2ecf20Sopenharmony_ci			int err = dev->ops->dev_register(dev);
1508c2ecf20Sopenharmony_ci			if (err < 0)
1518c2ecf20Sopenharmony_ci				return err;
1528c2ecf20Sopenharmony_ci		}
1538c2ecf20Sopenharmony_ci		dev->state = SNDRV_DEV_REGISTERED;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	return 0;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci/**
1598c2ecf20Sopenharmony_ci * snd_device_register - register the device
1608c2ecf20Sopenharmony_ci * @card: the card instance
1618c2ecf20Sopenharmony_ci * @device_data: the data pointer to register
1628c2ecf20Sopenharmony_ci *
1638c2ecf20Sopenharmony_ci * Registers the device which was already created via
1648c2ecf20Sopenharmony_ci * snd_device_new().  Usually this is called from snd_card_register(),
1658c2ecf20Sopenharmony_ci * but it can be called later if any new devices are created after
1668c2ecf20Sopenharmony_ci * invocation of snd_card_register().
1678c2ecf20Sopenharmony_ci *
1688c2ecf20Sopenharmony_ci * Return: Zero if successful, or a negative error code on failure or if the
1698c2ecf20Sopenharmony_ci * device not found.
1708c2ecf20Sopenharmony_ci */
1718c2ecf20Sopenharmony_ciint snd_device_register(struct snd_card *card, void *device_data)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	struct snd_device *dev;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card || !device_data))
1768c2ecf20Sopenharmony_ci		return -ENXIO;
1778c2ecf20Sopenharmony_ci	dev = look_for_dev(card, device_data);
1788c2ecf20Sopenharmony_ci	if (dev)
1798c2ecf20Sopenharmony_ci		return __snd_device_register(dev);
1808c2ecf20Sopenharmony_ci	snd_BUG();
1818c2ecf20Sopenharmony_ci	return -ENXIO;
1828c2ecf20Sopenharmony_ci}
1838c2ecf20Sopenharmony_ciEXPORT_SYMBOL(snd_device_register);
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci/*
1868c2ecf20Sopenharmony_ci * register all the devices on the card.
1878c2ecf20Sopenharmony_ci * called from init.c
1888c2ecf20Sopenharmony_ci */
1898c2ecf20Sopenharmony_ciint snd_device_register_all(struct snd_card *card)
1908c2ecf20Sopenharmony_ci{
1918c2ecf20Sopenharmony_ci	struct snd_device *dev;
1928c2ecf20Sopenharmony_ci	int err;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card))
1958c2ecf20Sopenharmony_ci		return -ENXIO;
1968c2ecf20Sopenharmony_ci	list_for_each_entry(dev, &card->devices, list) {
1978c2ecf20Sopenharmony_ci		err = __snd_device_register(dev);
1988c2ecf20Sopenharmony_ci		if (err < 0)
1998c2ecf20Sopenharmony_ci			return err;
2008c2ecf20Sopenharmony_ci	}
2018c2ecf20Sopenharmony_ci	return 0;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci/*
2058c2ecf20Sopenharmony_ci * disconnect all the devices on the card.
2068c2ecf20Sopenharmony_ci * called from init.c
2078c2ecf20Sopenharmony_ci */
2088c2ecf20Sopenharmony_civoid snd_device_disconnect_all(struct snd_card *card)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	struct snd_device *dev;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card))
2138c2ecf20Sopenharmony_ci		return;
2148c2ecf20Sopenharmony_ci	list_for_each_entry_reverse(dev, &card->devices, list)
2158c2ecf20Sopenharmony_ci		__snd_device_disconnect(dev);
2168c2ecf20Sopenharmony_ci}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci/*
2198c2ecf20Sopenharmony_ci * release all the devices on the card.
2208c2ecf20Sopenharmony_ci * called from init.c
2218c2ecf20Sopenharmony_ci */
2228c2ecf20Sopenharmony_civoid snd_device_free_all(struct snd_card *card)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	struct snd_device *dev, *next;
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	if (snd_BUG_ON(!card))
2278c2ecf20Sopenharmony_ci		return;
2288c2ecf20Sopenharmony_ci	list_for_each_entry_safe_reverse(dev, next, &card->devices, list) {
2298c2ecf20Sopenharmony_ci		/* exception: free ctl and lowlevel stuff later */
2308c2ecf20Sopenharmony_ci		if (dev->type == SNDRV_DEV_CONTROL ||
2318c2ecf20Sopenharmony_ci		    dev->type == SNDRV_DEV_LOWLEVEL)
2328c2ecf20Sopenharmony_ci			continue;
2338c2ecf20Sopenharmony_ci		__snd_device_free(dev);
2348c2ecf20Sopenharmony_ci	}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	/* free all */
2378c2ecf20Sopenharmony_ci	list_for_each_entry_safe_reverse(dev, next, &card->devices, list)
2388c2ecf20Sopenharmony_ci		__snd_device_free(dev);
2398c2ecf20Sopenharmony_ci}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci/**
2428c2ecf20Sopenharmony_ci * snd_device_get_state - Get the current state of the given device
2438c2ecf20Sopenharmony_ci * @card: the card instance
2448c2ecf20Sopenharmony_ci * @device_data: the data pointer to release
2458c2ecf20Sopenharmony_ci *
2468c2ecf20Sopenharmony_ci * Returns the current state of the given device object.  For the valid
2478c2ecf20Sopenharmony_ci * device, either @SNDRV_DEV_BUILD, @SNDRV_DEV_REGISTERED or
2488c2ecf20Sopenharmony_ci * @SNDRV_DEV_DISCONNECTED is returned.
2498c2ecf20Sopenharmony_ci * Or for a non-existing device, -1 is returned as an error.
2508c2ecf20Sopenharmony_ci */
2518c2ecf20Sopenharmony_ciint snd_device_get_state(struct snd_card *card, void *device_data)
2528c2ecf20Sopenharmony_ci{
2538c2ecf20Sopenharmony_ci	struct snd_device *dev;
2548c2ecf20Sopenharmony_ci
2558c2ecf20Sopenharmony_ci	dev = look_for_dev(card, device_data);
2568c2ecf20Sopenharmony_ci	if (dev)
2578c2ecf20Sopenharmony_ci		return dev->state;
2588c2ecf20Sopenharmony_ci	return -1;
2598c2ecf20Sopenharmony_ci}
2608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_device_get_state);
261