| // SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) | |
| // Copyright(c) 2015-17 Intel Corporation. | |
| #include <linux/acpi.h> | |
| #include <linux/of.h> | |
| #include <linux/soundwire/sdw.h> | |
| #include <linux/soundwire/sdw_type.h> | |
| #include <sound/sdca.h> | |
| #include "bus.h" | |
| #include "sysfs_local.h" | |
| static void sdw_slave_release(struct device *dev) | |
| { | |
| struct sdw_slave *slave = dev_to_sdw_dev(dev); | |
| of_node_put(slave->dev.of_node); | |
| mutex_destroy(&slave->sdw_dev_lock); | |
| kfree(slave); | |
| } | |
| const struct device_type sdw_slave_type = { | |
| .name = "sdw_slave", | |
| .release = sdw_slave_release, | |
| .uevent = sdw_slave_uevent, | |
| }; | |
| EXPORT_SYMBOL_GPL(sdw_slave_type); | |
| int sdw_slave_add(struct sdw_bus *bus, | |
| struct sdw_slave_id *id, struct fwnode_handle *fwnode) | |
| { | |
| struct sdw_slave *slave; | |
| int ret; | |
| int i; | |
| slave = kzalloc_obj(*slave); | |
| if (!slave) | |
| return -ENOMEM; | |
| /* Initialize data structure */ | |
| memcpy(&slave->id, id, sizeof(*id)); | |
| slave->dev.parent = bus->dev; | |
| slave->dev.fwnode = fwnode; | |
| if (id->unique_id == SDW_IGNORED_UNIQUE_ID) { | |
| /* name shall be sdw:ctrl:link:mfg:part:class */ | |
| dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x", | |
| bus->controller_id, bus->link_id, id->mfg_id, id->part_id, | |
| id->class_id); | |
| } else { | |
| /* name shall be sdw:ctrl:link:mfg:part:class:unique */ | |
| dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x:%01x", | |
| bus->controller_id, bus->link_id, id->mfg_id, id->part_id, | |
| id->class_id, id->unique_id); | |
| } | |
| slave->dev.bus = &sdw_bus_type; | |
| slave->dev.of_node = of_node_get(to_of_node(fwnode)); | |
| slave->dev.type = &sdw_slave_type; | |
| slave->dev.groups = sdw_slave_status_attr_groups; | |
| slave->bus = bus; | |
| slave->status = SDW_SLAVE_UNATTACHED; | |
| init_completion(&slave->enumeration_complete); | |
| init_completion(&slave->initialization_complete); | |
| slave->dev_num = 0; | |
| slave->probed = false; | |
| slave->first_interrupt_done = false; | |
| mutex_init(&slave->sdw_dev_lock); | |
| for (i = 0; i < SDW_MAX_PORTS; i++) | |
| init_completion(&slave->port_ready[i]); | |
| mutex_lock(&bus->bus_lock); | |
| list_add_tail(&slave->node, &bus->slaves); | |
| mutex_unlock(&bus->bus_lock); | |
| /* | |
| * The Soundwire driver probe may optionally register SDCA | |
| * sub-devices, one per Function. This means the information | |
| * on the SDCA revision and the number/type of Functions need | |
| * to be extracted from platform firmware before the SoundWire | |
| * driver probe, and as a consequence before the SoundWire | |
| * device_register() below. | |
| */ | |
| sdca_lookup_interface_revision(slave); | |
| sdca_lookup_functions(slave); | |
| ret = device_register(&slave->dev); | |
| if (ret) { | |
| dev_err(bus->dev, "Failed to add slave: ret %d\n", ret); | |
| /* | |
| * On err, don't free but drop ref as this will be freed | |
| * when release method is invoked. | |
| */ | |
| mutex_lock(&bus->bus_lock); | |
| list_del(&slave->node); | |
| mutex_unlock(&bus->bus_lock); | |
| put_device(&slave->dev); | |
| return ret; | |
| } | |
| sdw_slave_debugfs_init(slave); | |
| return ret; | |
| } | |
| EXPORT_SYMBOL(sdw_slave_add); | |
| #if IS_ENABLED(CONFIG_ACPI) | |
| static bool find_slave(struct sdw_bus *bus, | |
| struct acpi_device *adev, | |
| struct sdw_slave_id *id) | |
| { | |
| unsigned int link_id; | |
| u64 addr; | |
| int ret; | |
| if (acpi_bus_get_status(adev) || !acpi_dev_ready_for_enumeration(adev)) | |
| return false; | |
| ret = acpi_get_local_u64_address(adev->handle, &addr); | |
| if (ret < 0) | |
| return false; | |
| if (bus->ops->override_adr) | |
| addr = bus->ops->override_adr(bus, addr); | |
| if (!addr) | |
| return false; | |
| /* Extract link id from ADR, Bit 51 to 48 (included) */ | |
| link_id = SDW_DISCO_LINK_ID(addr); | |
| /* Check for link_id match */ | |
| if (link_id != bus->link_id) | |
| return false; | |
| sdw_extract_slave_id(bus, addr, id); | |
| return true; | |
| } | |
| struct sdw_acpi_child_walk_data { | |
| struct sdw_bus *bus; | |
| struct acpi_device *adev; | |
| struct sdw_slave_id id; | |
| bool ignore_unique_id; | |
| }; | |
| static int sdw_acpi_check_duplicate(struct acpi_device *adev, void *data) | |
| { | |
| struct sdw_acpi_child_walk_data *cwd = data; | |
| struct sdw_bus *bus = cwd->bus; | |
| struct sdw_slave_id id; | |
| if (adev == cwd->adev) | |
| return 0; | |
| if (!find_slave(bus, adev, &id)) | |
| return 0; | |
| if (cwd->id.sdw_version != id.sdw_version || cwd->id.mfg_id != id.mfg_id || | |
| cwd->id.part_id != id.part_id || cwd->id.class_id != id.class_id) | |
| return 0; | |
| if (cwd->id.unique_id != id.unique_id) { | |
| dev_dbg(bus->dev, | |
| "Valid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n", | |
| cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, | |
| cwd->id.part_id); | |
| cwd->ignore_unique_id = false; | |
| return 0; | |
| } | |
| dev_err(bus->dev, | |
| "Invalid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n", | |
| cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, cwd->id.part_id); | |
| return -ENODEV; | |
| } | |
| static int sdw_acpi_find_one(struct acpi_device *adev, void *data) | |
| { | |
| struct sdw_bus *bus = data; | |
| struct sdw_acpi_child_walk_data cwd = { | |
| .bus = bus, | |
| .adev = adev, | |
| .ignore_unique_id = true, | |
| }; | |
| int ret; | |
| if (!find_slave(bus, adev, &cwd.id)) | |
| return 0; | |
| /* Brute-force O(N^2) search for duplicates. */ | |
| ret = acpi_dev_for_each_child(ACPI_COMPANION(bus->dev), | |
| sdw_acpi_check_duplicate, &cwd); | |
| if (ret) | |
| return ret; | |
| if (cwd.ignore_unique_id) | |
| cwd.id.unique_id = SDW_IGNORED_UNIQUE_ID; | |
| /* Ignore errors and continue. */ | |
| sdw_slave_add(bus, &cwd.id, acpi_fwnode_handle(adev)); | |
| return 0; | |
| } | |
| /* | |
| * sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node | |
| * @bus: SDW bus instance | |
| * | |
| * Scans Master ACPI node for SDW child Slave devices and registers it. | |
| */ | |
| int sdw_acpi_find_slaves(struct sdw_bus *bus) | |
| { | |
| struct acpi_device *parent; | |
| parent = ACPI_COMPANION(bus->dev); | |
| if (!parent) { | |
| dev_err(bus->dev, "Can't find parent for acpi bind\n"); | |
| return -ENODEV; | |
| } | |
| return acpi_dev_for_each_child(parent, sdw_acpi_find_one, bus); | |
| } | |
| #endif | |
| /* | |
| * sdw_of_find_slaves() - Find Slave devices in master device tree node | |
| * @bus: SDW bus instance | |
| * | |
| * Scans Master DT node for SDW child Slave devices and registers it. | |
| */ | |
| int sdw_of_find_slaves(struct sdw_bus *bus) | |
| { | |
| struct device *dev = bus->dev; | |
| struct device_node *node; | |
| for_each_child_of_node(bus->dev->of_node, node) { | |
| int link_id, ret, len; | |
| unsigned int sdw_version; | |
| const char *compat = NULL; | |
| struct sdw_slave_id id; | |
| const __be32 *addr; | |
| ret = of_property_read_string(node, "compatible", &compat); | |
| if (ret) | |
| continue; | |
| ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version, | |
| &id.mfg_id, &id.part_id, &id.class_id); | |
| if (ret != 4) { | |
| dev_err(dev, "Invalid compatible string found %s\n", | |
| compat); | |
| continue; | |
| } | |
| addr = of_get_property(node, "reg", &len); | |
| if (!addr || (len < 2 * sizeof(u32))) { | |
| dev_err(dev, "Invalid Link and Instance ID\n"); | |
| continue; | |
| } | |
| link_id = be32_to_cpup(addr++); | |
| id.unique_id = be32_to_cpup(addr); | |
| id.sdw_version = sdw_version; | |
| /* Check for link_id match */ | |
| if (link_id != bus->link_id) | |
| continue; | |
| sdw_slave_add(bus, &id, of_fwnode_handle(node)); | |
| } | |
| return 0; | |
| } | |
| struct device *of_sdw_find_device_by_node(struct device_node *np) | |
| { | |
| return bus_find_device_by_of_node(&sdw_bus_type, np); | |
| } | |
| EXPORT_SYMBOL_GPL(of_sdw_find_device_by_node); | |
| MODULE_IMPORT_NS("SND_SOC_SDCA"); |