18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  hdac-ext-bus.c - HD-audio extended core bus functions.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2014-2015 Intel Corp
68c2ecf20Sopenharmony_ci *  Author: Jeeja KP <jeeja.kp@intel.com>
78c2ecf20Sopenharmony_ci *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
108c2ecf20Sopenharmony_ci */
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/io.h>
158c2ecf20Sopenharmony_ci#include <sound/hdaudio_ext.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("HDA extended core");
188c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci/**
218c2ecf20Sopenharmony_ci * snd_hdac_ext_bus_init - initialize a HD-audio extended bus
228c2ecf20Sopenharmony_ci * @bus: the pointer to HDAC bus object
238c2ecf20Sopenharmony_ci * @dev: device pointer
248c2ecf20Sopenharmony_ci * @ops: bus verb operators
258c2ecf20Sopenharmony_ci * @ext_ops: operators used for ASoC HDA codec drivers
268c2ecf20Sopenharmony_ci *
278c2ecf20Sopenharmony_ci * Returns 0 if successful, or a negative error code.
288c2ecf20Sopenharmony_ci */
298c2ecf20Sopenharmony_ciint snd_hdac_ext_bus_init(struct hdac_bus *bus, struct device *dev,
308c2ecf20Sopenharmony_ci			const struct hdac_bus_ops *ops,
318c2ecf20Sopenharmony_ci			const struct hdac_ext_bus_ops *ext_ops)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	int ret;
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci	ret = snd_hdac_bus_init(bus, dev, ops);
368c2ecf20Sopenharmony_ci	if (ret < 0)
378c2ecf20Sopenharmony_ci		return ret;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	bus->ext_ops = ext_ops;
408c2ecf20Sopenharmony_ci	/* FIXME:
418c2ecf20Sopenharmony_ci	 * Currently only one bus is supported, if there is device with more
428c2ecf20Sopenharmony_ci	 * buses, bus->idx should be greater than 0, but there needs to be a
438c2ecf20Sopenharmony_ci	 * reliable way to always assign same number.
448c2ecf20Sopenharmony_ci	 */
458c2ecf20Sopenharmony_ci	bus->idx = 0;
468c2ecf20Sopenharmony_ci	bus->cmd_dma_state = true;
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci	return 0;
498c2ecf20Sopenharmony_ci}
508c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_ext_bus_init);
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/**
538c2ecf20Sopenharmony_ci * snd_hdac_ext_bus_exit - clean up a HD-audio extended bus
548c2ecf20Sopenharmony_ci * @bus: the pointer to HDAC bus object
558c2ecf20Sopenharmony_ci */
568c2ecf20Sopenharmony_civoid snd_hdac_ext_bus_exit(struct hdac_bus *bus)
578c2ecf20Sopenharmony_ci{
588c2ecf20Sopenharmony_ci	snd_hdac_bus_exit(bus);
598c2ecf20Sopenharmony_ci	WARN_ON(!list_empty(&bus->hlink_list));
608c2ecf20Sopenharmony_ci}
618c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_ext_bus_exit);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic void default_release(struct device *dev)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	snd_hdac_ext_bus_device_exit(dev_to_hdac_dev(dev));
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci/**
698c2ecf20Sopenharmony_ci * snd_hdac_ext_bus_device_init - initialize the HDA extended codec base device
708c2ecf20Sopenharmony_ci * @bus: hdac bus to attach to
718c2ecf20Sopenharmony_ci * @addr: codec address
728c2ecf20Sopenharmony_ci * @hdev: hdac device to init
738c2ecf20Sopenharmony_ci * @type: codec type (HDAC_DEV_*) to use for this device
748c2ecf20Sopenharmony_ci *
758c2ecf20Sopenharmony_ci * Returns zero for success or a negative error code.
768c2ecf20Sopenharmony_ci */
778c2ecf20Sopenharmony_ciint snd_hdac_ext_bus_device_init(struct hdac_bus *bus, int addr,
788c2ecf20Sopenharmony_ci				 struct hdac_device *hdev, int type)
798c2ecf20Sopenharmony_ci{
808c2ecf20Sopenharmony_ci	char name[15];
818c2ecf20Sopenharmony_ci	int ret;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	hdev->bus = bus;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	snprintf(name, sizeof(name), "ehdaudio%dD%d", bus->idx, addr);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	ret  = snd_hdac_device_init(hdev, bus, name, addr);
888c2ecf20Sopenharmony_ci	if (ret < 0) {
898c2ecf20Sopenharmony_ci		dev_err(bus->dev, "device init failed for hdac device\n");
908c2ecf20Sopenharmony_ci		return ret;
918c2ecf20Sopenharmony_ci	}
928c2ecf20Sopenharmony_ci	hdev->type = type;
938c2ecf20Sopenharmony_ci	hdev->dev.release = default_release;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	ret = snd_hdac_device_register(hdev);
968c2ecf20Sopenharmony_ci	if (ret) {
978c2ecf20Sopenharmony_ci		dev_err(bus->dev, "failed to register hdac device\n");
988c2ecf20Sopenharmony_ci		snd_hdac_ext_bus_device_exit(hdev);
998c2ecf20Sopenharmony_ci		return ret;
1008c2ecf20Sopenharmony_ci	}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	return 0;
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_init);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci/**
1078c2ecf20Sopenharmony_ci * snd_hdac_ext_bus_device_exit - clean up a HD-audio extended codec base device
1088c2ecf20Sopenharmony_ci * @hdev: hdac device to clean up
1098c2ecf20Sopenharmony_ci */
1108c2ecf20Sopenharmony_civoid snd_hdac_ext_bus_device_exit(struct hdac_device *hdev)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	snd_hdac_device_exit(hdev);
1138c2ecf20Sopenharmony_ci}
1148c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_exit);
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci/**
1178c2ecf20Sopenharmony_ci * snd_hdac_ext_bus_device_remove - remove HD-audio extended codec base devices
1188c2ecf20Sopenharmony_ci *
1198c2ecf20Sopenharmony_ci * @bus: the pointer to HDAC bus object
1208c2ecf20Sopenharmony_ci */
1218c2ecf20Sopenharmony_civoid snd_hdac_ext_bus_device_remove(struct hdac_bus *bus)
1228c2ecf20Sopenharmony_ci{
1238c2ecf20Sopenharmony_ci	struct hdac_device *codec, *__codec;
1248c2ecf20Sopenharmony_ci	/*
1258c2ecf20Sopenharmony_ci	 * we need to remove all the codec devices objects created in the
1268c2ecf20Sopenharmony_ci	 * snd_hdac_ext_bus_device_init
1278c2ecf20Sopenharmony_ci	 */
1288c2ecf20Sopenharmony_ci	list_for_each_entry_safe(codec, __codec, &bus->codec_list, list) {
1298c2ecf20Sopenharmony_ci		snd_hdac_device_unregister(codec);
1308c2ecf20Sopenharmony_ci		put_device(&codec->dev);
1318c2ecf20Sopenharmony_ci	}
1328c2ecf20Sopenharmony_ci}
1338c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hdac_ext_bus_device_remove);
1348c2ecf20Sopenharmony_ci#define dev_to_hdac(dev) (container_of((dev), \
1358c2ecf20Sopenharmony_ci			struct hdac_device, dev))
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic inline struct hdac_driver *get_hdrv(struct device *dev)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	struct hdac_driver *hdrv = drv_to_hdac_driver(dev->driver);
1408c2ecf20Sopenharmony_ci	return hdrv;
1418c2ecf20Sopenharmony_ci}
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_cistatic inline struct hdac_device *get_hdev(struct device *dev)
1448c2ecf20Sopenharmony_ci{
1458c2ecf20Sopenharmony_ci	struct hdac_device *hdev = dev_to_hdac_dev(dev);
1468c2ecf20Sopenharmony_ci	return hdev;
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic int hda_ext_drv_probe(struct device *dev)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	return (get_hdrv(dev))->probe(get_hdev(dev));
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic int hdac_ext_drv_remove(struct device *dev)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	return (get_hdrv(dev))->remove(get_hdev(dev));
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_cistatic void hdac_ext_drv_shutdown(struct device *dev)
1608c2ecf20Sopenharmony_ci{
1618c2ecf20Sopenharmony_ci	return (get_hdrv(dev))->shutdown(get_hdev(dev));
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci/**
1658c2ecf20Sopenharmony_ci * snd_hda_ext_driver_register - register a driver for ext hda devices
1668c2ecf20Sopenharmony_ci *
1678c2ecf20Sopenharmony_ci * @drv: ext hda driver structure
1688c2ecf20Sopenharmony_ci */
1698c2ecf20Sopenharmony_ciint snd_hda_ext_driver_register(struct hdac_driver *drv)
1708c2ecf20Sopenharmony_ci{
1718c2ecf20Sopenharmony_ci	drv->type = HDA_DEV_ASOC;
1728c2ecf20Sopenharmony_ci	drv->driver.bus = &snd_hda_bus_type;
1738c2ecf20Sopenharmony_ci	/* we use default match */
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	if (drv->probe)
1768c2ecf20Sopenharmony_ci		drv->driver.probe = hda_ext_drv_probe;
1778c2ecf20Sopenharmony_ci	if (drv->remove)
1788c2ecf20Sopenharmony_ci		drv->driver.remove = hdac_ext_drv_remove;
1798c2ecf20Sopenharmony_ci	if (drv->shutdown)
1808c2ecf20Sopenharmony_ci		drv->driver.shutdown = hdac_ext_drv_shutdown;
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_ci	return driver_register(&drv->driver);
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hda_ext_driver_register);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci/**
1878c2ecf20Sopenharmony_ci * snd_hda_ext_driver_unregister - unregister a driver for ext hda devices
1888c2ecf20Sopenharmony_ci *
1898c2ecf20Sopenharmony_ci * @drv: ext hda driver structure
1908c2ecf20Sopenharmony_ci */
1918c2ecf20Sopenharmony_civoid snd_hda_ext_driver_unregister(struct hdac_driver *drv)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	driver_unregister(&drv->driver);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_hda_ext_driver_unregister);
196