[alsa-devel] [RFC 06/14] SoundWire: Add register/unregister APIs
Hardik Shah
hardik.t.shah at intel.com
Fri Oct 21 14:41:04 CEST 2016
This patch adds following SoundWire bus driver APIs.
1. Register SoundWire Master device and driver.
2. Register SoundWire Slave driver.
3. Register Slave device capabilities.
Signed-off-by: Hardik Shah <hardik.t.shah at intel.com>
Signed-off-by: Sanyog Kale <sanyog.r.kale at intel.com>
Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart at linux.intel.com>
---
sound/Kconfig | 2 +
sound/Makefile | 1 +
sound/sdw/Kconfig | 6 +
sound/sdw/Makefile | 1 +
sound/sdw/sdw.c | 886 ++++++++++++++++++++++++++++++++++++++++++++++++++
sound/sdw/sdw_priv.h | 102 ++++++
6 files changed, 998 insertions(+)
create mode 100644 sound/sdw/Kconfig
create mode 100644 sound/sdw/Makefile
create mode 100644 sound/sdw/sdw.c
create mode 100644 sound/sdw/sdw_priv.h
diff --git a/sound/Kconfig b/sound/Kconfig
index 5a240e0..9f67cf7 100644
--- a/sound/Kconfig
+++ b/sound/Kconfig
@@ -108,6 +108,8 @@ source "sound/parisc/Kconfig"
source "sound/soc/Kconfig"
+source "sound/sdw/Kconfig"
+
endif # SND
menuconfig SOUND_PRIME
diff --git a/sound/Makefile b/sound/Makefile
index c41bdf5..914101e 100644
--- a/sound/Makefile
+++ b/sound/Makefile
@@ -3,6 +3,7 @@
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
+obj-$(CONFIG_SOUND_SDW) += sdw/
obj-$(CONFIG_DMASOUND) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/
diff --git a/sound/sdw/Kconfig b/sound/sdw/Kconfig
new file mode 100644
index 0000000..052079f
--- /dev/null
+++ b/sound/sdw/Kconfig
@@ -0,0 +1,6 @@
+config SOUND_SDW
+ tristate
+ help
+ SoundWire interface is typically used for transporting data
+ related to audio functions. Concerned drivers should "select"
+ this.
diff --git a/sound/sdw/Makefile b/sound/sdw/Makefile
new file mode 100644
index 0000000..6ed1881
--- /dev/null
+++ b/sound/sdw/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_SOUND_SDW) += sdw.o
diff --git a/sound/sdw/sdw.c b/sound/sdw/sdw.c
new file mode 100644
index 0000000..d4e79b8a
--- /dev/null
+++ b/sound/sdw/sdw.c
@@ -0,0 +1,886 @@
+/*
+ * sdw.c - SoundWire bus driver implementation.
+ *
+ * Author: Hardik Shah <hardik.t.shah at intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
+#include <sound/sdw_bus.h>
+#include <sound/sdw_master.h>
+#include <sound/sdw_slave.h>
+
+#include "sdw_priv.h"
+
+/*
+ * Global SoundWire core instance contains list of Masters registered, core
+ * lock and SoundWire stream tags.
+ */
+struct snd_sdw_core snd_sdw_core;
+
+static void sdw_slv_release(struct device *dev)
+{
+ kfree(to_sdw_slave(dev));
+}
+
+static void sdw_mstr_release(struct device *dev)
+{
+ struct sdw_master *mstr = to_sdw_master(dev);
+
+ complete(&mstr->slv_released_complete);
+}
+
+static struct device_type sdw_slv_type = {
+ .groups = NULL,
+ .release = sdw_slv_release,
+};
+
+static struct device_type sdw_mstr_type = {
+ .groups = NULL,
+ .release = sdw_mstr_release,
+};
+
+/**
+ * sdw_slv_verify - return parameter as sdw_slave, or NULL
+ * @dev: device, probably from some driver model iterator
+ *
+ * When traversing the driver model tree, perhaps using driver model
+ * iterators like @device_for_each_child(), you can't assume very much
+ * about the nodes you find. Use this function to avoid oopses caused
+ * by wrongly treating some non-SDW device as an sdw_slave.
+ */
+static struct sdw_slave *sdw_slv_verify(struct device *dev)
+{
+ return (dev->type == &sdw_slv_type)
+ ? to_sdw_slave(dev)
+ : NULL;
+}
+
+/**
+ * sdw_mstr_verify: return parameter as sdw_master, or NULL
+ *
+ * @dev: device, probably from some driver model iterator
+ *
+ * When traversing the driver model tree, perhaps using driver model
+ * iterators like @device_for_each_child(), you can't assume very much
+ * about the nodes you find. Use this function to avoid oopses caused
+ * by wrongly treating some non-SDW device as an sdw_master.
+ */
+static struct sdw_master *sdw_mstr_verify(struct device *dev)
+{
+ return (dev->type == &sdw_mstr_type)
+ ? to_sdw_master(dev)
+ : NULL;
+}
+
+static const struct sdw_slave_id *sdw_match_slv(const struct sdw_slave_id *id,
+ const struct sdw_slave *sdw_slv)
+{
+ const struct sdw_slave_priv *slv_priv = &sdw_slv->priv;
+
+ if (!id)
+ return NULL;
+
+ /*
+ * IDs should be NULL terminated like the last ID in the list should
+ * be null, as done for drivers like platform, i2c etc.
+ */
+ while (id->name[0]) {
+ if (strncmp(slv_priv->name, id->name, SOUNDWIRE_NAME_SIZE) == 0)
+ return id;
+
+ id++;
+ }
+
+ return NULL;
+}
+
+static const struct sdw_master_id *sdw_match_mstr(
+ const struct sdw_master_id *id,
+ const struct sdw_master *sdw_mstr)
+{
+ if (!id)
+ return NULL;
+
+ /*
+ * IDs should be NULL terminated like the last ID in the list should
+ * be null, as done for drivers like platform, i2c etc.
+ */
+ while (id->name[0]) {
+ if (strncmp(sdw_mstr->name, id->name, SOUNDWIRE_NAME_SIZE) == 0)
+ return id;
+ id++;
+ }
+ return NULL;
+}
+
+static int sdw_slv_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_slave *sdw_slv;
+ struct sdw_driver *sdw_drv = to_sdw_driver(driver);
+ struct sdw_slave_driver *drv;
+ int ret = 0;
+
+
+ if (sdw_drv->driver_type != SDW_DRIVER_TYPE_SLAVE)
+ return ret;
+
+ drv = to_sdw_slave_driver(driver);
+ sdw_slv = to_sdw_slave(dev);
+
+ /*
+ * We are matching based on the dev_id field, dev_id field is unique
+ * based on part_id and manufacturer id. Device will be registered
+ * based on dev_id and driver will also have same dev_id for device
+ * its controlling.
+ */
+ ret = (sdw_match_slv(drv->id_table, sdw_slv) != NULL);
+
+ if (ret < 0)
+ sdw_slv->priv.driver = drv;
+
+ return ret;
+}
+
+static int sdw_mstr_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_master *sdw_mstr;
+ struct sdw_driver *sdw_drv = to_sdw_driver(driver);
+ struct sdw_master_driver *drv;
+ int ret = 0;
+
+ if (sdw_drv->driver_type != SDW_DRIVER_TYPE_MASTER)
+ return ret;
+
+ drv = to_sdw_master_driver(driver);
+ sdw_mstr = to_sdw_master(dev);
+
+ ret = (sdw_match_mstr(drv->id_table, sdw_mstr) != NULL);
+
+ if (driver->name && !ret)
+ ret = (strncmp(sdw_mstr->name, driver->name,
+ SOUNDWIRE_NAME_SIZE) == 0);
+
+ if (ret < 0)
+ sdw_mstr->driver = drv;
+
+ return ret;
+}
+
+static int sdw_mstr_probe(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+ struct sdw_master *mstr = to_sdw_master(dev);
+ int ret;
+
+ ret = dev_pm_domain_attach(dev, true);
+
+ if (ret != -EPROBE_DEFER) {
+ ret = sdrv->probe(mstr, sdw_match_mstr(sdrv->id_table, mstr));
+ if (ret < 0)
+ dev_pm_domain_detach(dev, true);
+ }
+
+ return ret;
+}
+
+static int sdw_slv_probe(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver);
+ struct sdw_slave *sdwslv = to_sdw_slave(dev);
+ int ret;
+
+ ret = dev_pm_domain_attach(dev, true);
+
+ if (ret != -EPROBE_DEFER) {
+ ret = sdrv->probe(sdwslv, sdw_match_slv(sdrv->id_table,
+ sdwslv));
+ if (ret < 0)
+ dev_pm_domain_detach(dev, true);
+ }
+
+ return ret;
+}
+
+static int sdw_mstr_remove(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+ int ret;
+
+ ret = sdrv->remove(to_sdw_master(dev));
+ dev_pm_domain_detach(dev, true);
+ return ret;
+
+}
+
+static int sdw_slv_remove(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv = to_sdw_slave_driver(dev->driver);
+ int ret;
+
+ ret = sdrv->remove(to_sdw_slave(dev));
+ dev_pm_domain_detach(dev, true);
+
+ return ret;
+}
+
+static void sdw_slv_shutdown(struct device *dev)
+{
+ const struct sdw_slave_driver *sdrv =
+ to_sdw_slave_driver(dev->driver);
+
+ sdrv->shutdown(to_sdw_slave(dev));
+}
+
+static void sdw_mstr_shutdown(struct device *dev)
+{
+ const struct sdw_master_driver *sdrv =
+ to_sdw_master_driver(dev->driver);
+
+ sdrv->shutdown(to_sdw_master(dev));
+}
+
+static int sdw_match(struct device *dev, struct device_driver *driver)
+{
+ struct sdw_slave *sdw_slv;
+ struct sdw_master *sdw_mstr;
+
+ sdw_slv = sdw_slv_verify(dev);
+ if (sdw_slv)
+ return sdw_slv_match(dev, driver);
+
+ sdw_mstr = sdw_mstr_verify(dev);
+ if (sdw_mstr)
+ return sdw_mstr_match(dev, driver);
+
+ /*
+ * Returning 0 to calling function means match not found, so calling
+ * function will not call probe
+ */
+ return 0;
+
+}
+
+static const struct dev_pm_ops soundwire_pm = {
+ .suspend = pm_generic_suspend,
+ .resume = pm_generic_resume,
+ SET_RUNTIME_PM_OPS(
+ pm_generic_runtime_suspend,
+ pm_generic_runtime_resume,
+ NULL)
+};
+
+static struct bus_type sdw_bus_type = {
+ .name = "soundwire",
+ .match = sdw_match,
+ .pm = &soundwire_pm,
+};
+
+/**
+ * snd_sdw_master_register_driver: SoundWire Master driver registration with
+ * bus. This API will register the Master driver with the SoundWire
+ * bus. It is typically called from the driver's module-init function.
+ *
+ * @driver: Master Driver to be associated with Master interface.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_master_register_driver(struct sdw_master_driver *driver,
+ struct module *owner)
+{
+ int ret;
+
+ if (!driver->probe)
+ return -EINVAL;
+
+ if (!driver->ops->xfer_msg || !driver->ops->reset_page_addr)
+ return -EINVAL;
+
+ if (!driver->port_ops->dpn_set_port_params ||
+ !driver->port_ops->dpn_set_port_transport_params ||
+ !driver->port_ops->dpn_port_enable_ch)
+ return -EINVAL;
+
+ driver->driver.probe = sdw_mstr_probe;
+
+ if (driver->remove)
+ driver->driver.remove = sdw_mstr_remove;
+ if (driver->shutdown)
+ driver->driver.shutdown = sdw_mstr_shutdown;
+
+ /* add the driver to the list of sdw drivers in the driver core */
+ driver->driver.owner = owner;
+ driver->driver.bus = &sdw_bus_type;
+
+ /*
+ * When registration returns, the driver core will have called
+ * probe() for all matching-but-unbound Slaves, devices which are
+ * not bind to any driver still.
+ */
+ ret = driver_register(&driver->driver);
+ if (ret)
+ return ret;
+
+ pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_register_driver);
+
+/**
+ * snd_sdw_slave_driver_register: SoundWire Slave driver registration with
+ * bus. This API will register the Slave driver with the SoundWire bus.
+ * It is typically called from the driver's module-init function.
+ *
+ * @driver: Driver to be associated with Slave.
+ * @owner: Module owner, generally THIS module.
+ */
+int snd_sdw_slave_driver_register(struct sdw_slave_driver *driver,
+ struct module *owner)
+{
+ int ret;
+
+ if (driver->probe)
+ driver->driver.probe = sdw_slv_probe;
+ if (driver->remove)
+ driver->driver.remove = sdw_slv_remove;
+ if (driver->shutdown)
+ driver->driver.shutdown = sdw_slv_shutdown;
+
+ /* Add the driver to the list of sdw drivers in the driver core */
+ driver->driver.owner = owner;
+ driver->driver.bus = &sdw_bus_type;
+
+ /*
+ * When registration returns, the driver core will have called
+ * probe() for all matching-but-unbound Slaves.
+ */
+ ret = driver_register(&driver->driver);
+ if (ret)
+ return ret;
+
+ pr_debug("sdw-core: driver [%s] registered\n", driver->driver.name);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_slave_driver_register);
+
+static int sdw_copy_aud_mod_prop(struct sdw_port_aud_mode_prop *slv_prop,
+ struct sdw_port_aud_mode_prop *prop)
+{
+ /*
+ * Currently goto is used in API to perform different
+ * operations. TODO: Avoid usage of goto statement
+ */
+ memcpy(slv_prop, prop, sizeof(*prop));
+
+ if (!prop->num_bus_freq_cfgs)
+ goto handle_sample_rate;
+
+ slv_prop->clk_freq_buf = kcalloc(prop->num_bus_freq_cfgs,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+
+ if (!slv_prop->clk_freq_buf)
+ goto mem_error;
+
+ memcpy(slv_prop->clk_freq_buf, prop->clk_freq_buf,
+ (prop->num_bus_freq_cfgs *
+ sizeof(unsigned int)));
+
+handle_sample_rate:
+
+ if (!prop->num_sample_rate_cfgs)
+ return 0;
+
+ slv_prop->sample_rate_buf = kcalloc(prop->num_sample_rate_cfgs,
+ sizeof(unsigned int),
+ GFP_KERNEL);
+
+ if (!slv_prop->sample_rate_buf)
+ goto mem_error;
+
+ memcpy(slv_prop->sample_rate_buf, prop->sample_rate_buf,
+ (prop->num_sample_rate_cfgs *
+ sizeof(unsigned int)));
+
+ return 0;
+
+mem_error:
+ kfree(prop->clk_freq_buf);
+ kfree(slv_prop->sample_rate_buf);
+ return -ENOMEM;
+
+}
+
+static int sdw_update_dpn_caps(struct sdw_dpn_caps *slv_dpn_cap,
+ struct sdw_dpn_caps *dpn_cap)
+{
+ int j, ret = 0;
+ struct sdw_port_aud_mode_prop *slv_prop, *prop;
+
+ /*
+ * Currently goto is used in API to perform different
+ * operations. TODO: Avoid usage of goto statement
+ */
+
+ /*
+ * slv_prop and prop are using to make copy of mode properties.
+ * prop holds mode properties received which needs to be updated to
+ * slv_prop.
+ */
+
+ memcpy(slv_dpn_cap, dpn_cap, sizeof(*dpn_cap));
+
+ /*
+ * Copy bps (bits per sample) buffer as part of Slave capabilities
+ */
+ if (!dpn_cap->num_bps)
+ goto handle_ch_cnt;
+
+ slv_dpn_cap->bps_buf = kcalloc(dpn_cap->num_bps, sizeof(u8),
+ GFP_KERNEL);
+
+ if (!slv_dpn_cap->bps_buf) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ memcpy(slv_dpn_cap->bps_buf, dpn_cap->bps_buf,
+ (dpn_cap->num_bps * sizeof(u8)));
+
+handle_ch_cnt:
+ if (!dpn_cap->num_ch_cnt)
+ goto handle_audio_mode_prop;
+
+ slv_dpn_cap->ch_cnt_buf = kcalloc(dpn_cap->num_ch_cnt, sizeof(u8),
+ GFP_KERNEL);
+ if (!dpn_cap->num_ch_cnt) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ /* Copy channel count buffer as part of Slave capabilities */
+ memcpy(slv_dpn_cap->ch_cnt_buf, dpn_cap->ch_cnt_buf,
+ (dpn_cap->num_ch_cnt * sizeof(u8)));
+
+handle_audio_mode_prop:
+
+ slv_dpn_cap->mode_properties = kzalloc((sizeof(*slv_prop) *
+ dpn_cap->num_audio_modes),
+ GFP_KERNEL);
+
+ if (!slv_dpn_cap->mode_properties) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ for (j = 0; j < dpn_cap->num_audio_modes; j++) {
+
+ prop = &dpn_cap->mode_properties[j];
+ slv_prop = &slv_dpn_cap->mode_properties[j];
+
+ /* Copy audio properties as part of Slave capabilities */
+ ret = sdw_copy_aud_mod_prop(slv_prop, prop);
+ if (ret < 0)
+ goto error;
+ }
+
+ return ret;
+
+error:
+ kfree(slv_dpn_cap->mode_properties);
+ kfree(slv_dpn_cap->ch_cnt_buf);
+ kfree(slv_dpn_cap->bps_buf);
+ return ret;
+
+}
+
+/* Free all the memory allocated for registering the capabilities */
+static void sdw_unregister_slv_caps(struct sdw_slave *sdw,
+ unsigned int num_port_direction)
+{
+ int i, j, k;
+ struct sdw_slave_caps *caps = &sdw->priv.caps;
+ struct sdw_dpn_caps *dpn_cap;
+ struct sdw_port_aud_mode_prop *mode_prop;
+ u8 ports;
+
+ for (i = 0; i < num_port_direction; i++) {
+
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+ for (j = 0; j < ports; j++) {
+ dpn_cap = &caps->dpn_caps[i][j];
+ kfree(dpn_cap->bps_buf);
+ kfree(dpn_cap->ch_cnt_buf);
+
+ for (k = 0; k < dpn_cap->num_audio_modes; k++) {
+ mode_prop = dpn_cap->mode_properties;
+ kfree(mode_prop->clk_freq_buf);
+ kfree(mode_prop->sample_rate_buf);
+ }
+ }
+ }
+}
+
+static inline void sdw_copy_slv_caps(struct sdw_slave *sdw,
+ struct sdw_slave_caps *caps)
+{
+ struct sdw_slave_caps *slv_caps;
+
+ slv_caps = &sdw->priv.caps;
+
+ memcpy(slv_caps, caps, sizeof(*slv_caps));
+}
+
+/**
+ * snd_sdw_slave_register_caps: Register Slave device capabilities to the
+ * bus driver. Since bus driver handles bunch of Slave register
+ * programming it should be aware of Slave device capabilities. Slave
+ * device is attached to bus based on enumeration. Once Slave driver is
+ * attached to device and probe of Slave driver is called on device and
+ * driver binding, Slave driver should call this function to register
+ * its capabilities to bus. This should be the very first function to
+ * bus driver from Slave driver once Slave driver is registered and
+ * probed.
+ *
+ * @slave: SoundWire Slave handle.
+ * @cap: Slave caps to be registered to bus driver.
+ */
+int snd_sdw_slave_register_caps(struct sdw_slave *slave,
+ struct sdw_slave_caps *cap)
+{
+ struct sdw_slave_caps *caps;
+ struct sdw_dpn_caps *slv_dpn_cap, *dpn_cap;
+ int i, j, ret;
+ u8 ports;
+
+ caps = &slave->priv.caps;
+
+ sdw_copy_slv_caps(slave, cap);
+
+ for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) {
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+
+ caps->dpn_caps[i] = kzalloc((sizeof(*slv_dpn_cap) *
+ ports), GFP_KERNEL);
+
+ if (caps->dpn_caps[i] == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ }
+
+ for (i = 0; i < SDW_MAX_PORT_DIRECTIONS; i++) {
+
+ if (i == SDW_DATA_DIR_OUT)
+ ports = caps->num_src_ports;
+ else
+ ports = caps->num_sink_ports;
+
+ for (j = 0; j < ports; j++) {
+
+ dpn_cap = &cap->dpn_caps[i][j];
+ slv_dpn_cap = &caps->dpn_caps[i][j];
+
+ ret = sdw_update_dpn_caps(&caps->dpn_caps[i][j],
+ &cap->dpn_caps[i][j]);
+ if (ret < 0) {
+ dev_err(&slave->mstr->dev, "Failed to update Slave caps ret = %d\n", ret);
+ goto error;
+ }
+ }
+ }
+
+ slave->priv.slave_cap_updated = true;
+
+ return 0;
+
+error:
+ sdw_unregister_slv_caps(slave, i);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_slave_register_caps);
+
+/**
+ * snd_sdw_master_add: Registers the SoundWire Master interface. This needs
+ * to be called for each Master interface supported by SoC. This
+ * represents One clock and data line (Optionally multiple data lanes)
+ * of Master interface.
+ *
+ * @master: the Master to be added.
+ */
+int snd_sdw_master_add(struct sdw_master *master)
+{
+ int i, id, ret;
+ struct sdw_bus *sdw_bus = NULL;
+
+ /* Sanity checks */
+ if (unlikely(master->name[0] == '\0')) {
+ pr_err("sdw-core: Attempt to register a master with no name!\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ /* Always start bus with 0th Index */
+ id = idr_alloc(&snd_sdw_core.idr, master, 0, 0, GFP_KERNEL);
+
+ if (id < 0) {
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return id;
+ }
+
+ master->nr = id;
+
+ /*
+ * Initialize the DeviceNumber in the Master structure. Each of
+ * these is assigned to the Slaves enumerating on this Master
+ * interface.
+ */
+ for (i = 0; i <= SDW_MAX_DEVICES; i++)
+ master->sdw_addr[i].dev_num = i;
+
+ mutex_init(&master->lock);
+ mutex_init(&master->msg_lock);
+ INIT_LIST_HEAD(&master->slv_list);
+ INIT_LIST_HEAD(&master->mstr_rt_list);
+
+ sdw_bus = kzalloc(sizeof(*sdw_bus), GFP_KERNEL);
+ if (!sdw_bus) {
+ ret = -ENOMEM;
+ goto alloc_failed;
+ }
+
+ sdw_bus->mstr = master;
+ master->bus = sdw_bus;
+
+ dev_set_name(&master->dev, "sdw-%d", master->nr);
+ master->dev.bus = &sdw_bus_type;
+ master->dev.type = &sdw_mstr_type;
+
+ ret = device_register(&master->dev);
+ if (ret < 0)
+ goto dev_reg_failed;
+
+ dev_dbg(&master->dev, "master [%s] registered\n", master->name);
+
+ /*
+ * Add bus to the list of buses inside core. This is list of Slave
+ * devices enumerated on this bus. Adding new devices at end. It can
+ * be added at any location in list.
+ */
+ list_add_tail(&sdw_bus->bus_node, &snd_sdw_core.bus_list);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ return 0;
+
+dev_reg_failed:
+ kfree(sdw_bus);
+alloc_failed:
+ idr_remove(&snd_sdw_core.idr, master->nr);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_add);
+
+static void sdw_unregister_slv(struct sdw_slave *sdw_slv)
+{
+ struct sdw_master *mstr;
+
+ mstr = sdw_slave_to_master(sdw_slv);
+
+ sdw_unregister_slv_caps(sdw_slv, SDW_MAX_PORT_DIRECTIONS);
+
+ mutex_lock(&mstr->lock);
+ list_del(&sdw_slv->priv.node);
+ mutex_unlock(&mstr->lock);
+
+ mstr->sdw_addr[sdw_slv->dev_num].assigned = false;
+
+ device_unregister(&sdw_slv->dev);
+ kfree(sdw_slv);
+}
+
+static int __unregister_slv(struct device *dev, void *dummy)
+{
+ struct sdw_slave *slave = sdw_slv_verify(dev);
+
+ if (slave)
+ sdw_unregister_slv(slave);
+
+ return 0;
+}
+
+/**
+ * snd_sdw_master_del - unregister SDW Master
+ *
+ * @master: the Master being unregistered
+ */
+void snd_sdw_master_del(struct sdw_master *master)
+{
+ struct sdw_master *found;
+
+ /* First make sure that this Master was ever added */
+ mutex_lock(&snd_sdw_core.core_mutex);
+ found = idr_find(&snd_sdw_core.idr, master->nr);
+
+ if (found != master) {
+ pr_debug("sdw-core: attempting to delete unregistered master [%s]\n",
+ master->name);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+ return;
+ }
+ /*
+ * Detach any active Slaves. This can't fail, thus we do not check
+ * the returned value.
+ */
+ device_for_each_child(&master->dev, NULL, __unregister_slv);
+
+ /* device name is gone after device_unregister */
+ dev_dbg(&master->dev, "master [%s] unregistered\n", master->name);
+
+ /* wait until all references to the device are gone */
+ init_completion(&master->slv_released_complete);
+ device_unregister(&master->dev);
+ wait_for_completion(&master->slv_released_complete);
+
+ /* free bus id */
+ idr_remove(&snd_sdw_core.idr, master->nr);
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ /*
+ * Clear the device structure in case this Master is ever going to
+ * be added again
+ */
+ memset(&master->dev, 0, sizeof(master->dev));
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_del);
+
+/**
+ * snd_sdw_master_get: Return the Master handle from Master number.
+ * Increments the reference count of the module. Similar to
+ * i2c_get_adapter.
+ *
+ * @nr: Master number.
+ *
+ * Returns Master handle on success, else NULL
+ */
+struct sdw_master *snd_sdw_master_get(int nr)
+{
+ struct sdw_master *master;
+
+ mutex_lock(&snd_sdw_core.core_mutex);
+
+ master = idr_find(&snd_sdw_core.idr, nr);
+ if (master && !try_module_get(master->driver->driver.owner))
+ master = NULL;
+
+ mutex_unlock(&snd_sdw_core.core_mutex);
+
+ return master;
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_get);
+
+/**
+ * snd_sdw_master_put: Reverses the effect of sdw_master_get
+ *
+ * @master: Master handle.
+ */
+void snd_sdw_master_put(struct sdw_master *master)
+{
+ if (master)
+ module_put(master->driver->driver.owner);
+}
+EXPORT_SYMBOL_GPL(snd_sdw_master_put);
+
+static void sdw_exit(void)
+{
+ bus_unregister(&sdw_bus_type);
+}
+
+static int sdw_init(void)
+{
+ int retval;
+
+ mutex_init(&snd_sdw_core.core_mutex);
+ INIT_LIST_HEAD(&snd_sdw_core.bus_list);
+ idr_init(&snd_sdw_core.idr);
+ retval = bus_register(&sdw_bus_type);
+
+ if (retval)
+ bus_unregister(&sdw_bus_type);
+ return retval;
+}
+
+subsys_initcall(sdw_init);
+module_exit(sdw_exit);
+
+MODULE_AUTHOR("Hardik Shah <hardik.t.shah at intel.com>");
+MODULE_AUTHOR("Sanyog Kale <sanyog.r.kale at intel.com>");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_DESCRIPTION("SoundWire bus driver");
+MODULE_ALIAS("platform:soundwire");
diff --git a/sound/sdw/sdw_priv.h b/sound/sdw/sdw_priv.h
new file mode 100644
index 0000000..5911aa6
--- /dev/null
+++ b/sound/sdw/sdw_priv.h
@@ -0,0 +1,102 @@
+/*
+ * sdw_priv.h - Private definition for SoundWire bus interface.
+ *
+ * Author: Hardik Shah <hardik.t.shah at intel.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This file is provided under a dual BSD/GPLv2 license. When using or
+ * redistributing this file, you may do so under either license.
+ *
+ * GPL LICENSE SUMMARY
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ *
+ * BSD LICENSE
+ *
+ * Copyright(c) 2016 Intel Corporation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Intel Corporation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ */
+
+#ifndef _LINUX_SDW_PRIV_H
+#define _LINUX_SDW_PRIV_H
+
+/**
+ * sdw_driver: Structure to typecast both Master and Slave driver to generic
+ * SoundWire driver, to find out the driver type.
+ *
+ * @driver_type: Type of SoundWire driver, Master or Slave.
+ * @driver: Generic Linux driver.
+ */
+struct sdw_driver {
+ enum sdw_driver_type driver_type;
+ struct device_driver driver;
+};
+#define to_sdw_driver(d) \
+ container_of(d, struct sdw_driver, driver)
+/**
+ * sdw_bus: Bus structure holding bus related information.
+ *
+ * @bus_node: Node to add the bus in the sdw_core list.
+ * @mstr: Master reference for the bus.
+ */
+
+struct sdw_bus {
+ struct list_head bus_node;
+ struct sdw_master *mstr;
+};
+
+/**
+ * snd_sdw_core: Global SoundWire structure. It handles all the streams
+ * spawned across masters and has list of bus structure per every
+ * Master registered.
+ *
+ * @bus_list: List of all the bus instance.
+ * @core_mutex: Global lock for all bus instances.
+ * @idr: For identifying the registered buses.
+ */
+struct snd_sdw_core {
+ struct list_head bus_list;
+ struct mutex core_mutex;
+ struct idr idr;
+};
+
+#endif /* _LINUX_SDW_PRIV_H */
--
1.7.9.5
More information about the Alsa-devel
mailing list