diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /drivers/soundwire | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/soundwire')
-rw-r--r-- | drivers/soundwire/Kconfig | 48 | ||||
-rw-r--r-- | drivers/soundwire/Makefile | 28 | ||||
-rw-r--r-- | drivers/soundwire/bus.c | 1789 | ||||
-rw-r--r-- | drivers/soundwire/bus.h | 228 | ||||
-rw-r--r-- | drivers/soundwire/bus_type.c | 220 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.c | 1716 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.h | 201 | ||||
-rw-r--r-- | drivers/soundwire/debugfs.c | 151 | ||||
-rw-r--r-- | drivers/soundwire/generic_bandwidth_allocation.c | 425 | ||||
-rw-r--r-- | drivers/soundwire/intel.c | 1994 | ||||
-rw-r--r-- | drivers/soundwire/intel.h | 56 | ||||
-rw-r--r-- | drivers/soundwire/intel_init.c | 484 | ||||
-rw-r--r-- | drivers/soundwire/master.c | 188 | ||||
-rw-r--r-- | drivers/soundwire/mipi_disco.c | 387 | ||||
-rw-r--r-- | drivers/soundwire/qcom.c | 932 | ||||
-rw-r--r-- | drivers/soundwire/slave.c | 243 | ||||
-rw-r--r-- | drivers/soundwire/stream.c | 1949 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_local.h | 18 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_slave.c | 270 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_slave_dpn.c | 301 |
20 files changed, 11628 insertions, 0 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig new file mode 100644 index 000000000..016e74230 --- /dev/null +++ b/drivers/soundwire/Kconfig @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# SoundWire subsystem configuration +# + +menuconfig SOUNDWIRE + tristate "SoundWire support" + depends on ACPI || OF + help + SoundWire is a 2-Pin interface with data and clock line ratified + by the MIPI Alliance. SoundWire is used for transporting data + typically related to audio functions. SoundWire interface is + optimized to integrate audio devices in mobile or mobile inspired + systems. Say Y to enable this subsystem, N if you do not have such + a device + +if SOUNDWIRE + +comment "SoundWire Devices" + +config SOUNDWIRE_CADENCE + tristate + +config SOUNDWIRE_INTEL + tristate "Intel SoundWire Master driver" + select SOUNDWIRE_CADENCE + select SOUNDWIRE_GENERIC_ALLOCATION + depends on ACPI && SND_SOC + help + SoundWire Intel Master driver. + If you have an Intel platform which has a SoundWire Master then + enable this config option to get the SoundWire support for that + device. + +config SOUNDWIRE_QCOM + tristate "Qualcomm SoundWire Master driver" + imply SLIMBUS + depends on SND_SOC + help + SoundWire Qualcomm Master driver. + If you have an Qualcomm platform which has a SoundWire Master then + enable this config option to get the SoundWire support for that + device + +config SOUNDWIRE_GENERIC_ALLOCATION + tristate + +endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile new file mode 100644 index 000000000..bf1e250d5 --- /dev/null +++ b/drivers/soundwire/Makefile @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for soundwire core +# + +#Bus Objs +soundwire-bus-y := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o \ + sysfs_slave.o sysfs_slave_dpn.o +obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o + +soundwire-generic-allocation-objs := generic_bandwidth_allocation.o +obj-$(CONFIG_SOUNDWIRE_GENERIC_ALLOCATION) += soundwire-generic-allocation.o + +ifdef CONFIG_DEBUG_FS +soundwire-bus-y += debugfs.o +endif + +#Cadence Objs +soundwire-cadence-y := cadence_master.o +obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o + +#Intel driver +soundwire-intel-y := intel.o intel_init.o +obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o + +#Qualcomm driver +soundwire-qcom-y := qcom.o +obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c new file mode 100644 index 000000000..0e0a19030 --- /dev/null +++ b/drivers/soundwire/bus.c @@ -0,0 +1,1789 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" +#include "sysfs_local.h" + +static DEFINE_IDA(sdw_ida); + +static int sdw_get_id(struct sdw_bus *bus) +{ + int rc = ida_alloc(&sdw_ida, GFP_KERNEL); + + if (rc < 0) + return rc; + + bus->id = rc; + return 0; +} + +/** + * sdw_bus_master_add() - add a bus Master instance + * @bus: bus instance + * @parent: parent device + * @fwnode: firmware node handle + * + * Initializes the bus instance, read properties and create child + * devices. + */ +int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, + struct fwnode_handle *fwnode) +{ + struct sdw_master_prop *prop = NULL; + int ret; + + if (!parent) { + pr_err("SoundWire parent device is not set\n"); + return -ENODEV; + } + + ret = sdw_get_id(bus); + if (ret) { + dev_err(parent, "Failed to get bus id\n"); + return ret; + } + + ret = sdw_master_device_add(bus, parent, fwnode); + if (ret) { + dev_err(parent, "Failed to add master device at link %d\n", + bus->link_id); + return ret; + } + + if (!bus->ops) { + dev_err(bus->dev, "SoundWire Bus ops are not set\n"); + return -EINVAL; + } + + if (!bus->compute_params) { + dev_err(bus->dev, + "Bandwidth allocation not configured, compute_params no set\n"); + return -EINVAL; + } + + mutex_init(&bus->msg_lock); + mutex_init(&bus->bus_lock); + INIT_LIST_HEAD(&bus->slaves); + INIT_LIST_HEAD(&bus->m_rt_list); + + /* + * Initialize multi_link flag + * TODO: populate this flag by reading property from FW node + */ + bus->multi_link = false; + if (bus->ops->read_prop) { + ret = bus->ops->read_prop(bus); + if (ret < 0) { + dev_err(bus->dev, + "Bus read properties failed:%d\n", ret); + return ret; + } + } + + sdw_bus_debugfs_init(bus); + + /* + * Device numbers in SoundWire are 0 through 15. Enumeration device + * number (0), Broadcast device number (15), Group numbers (12 and + * 13) and Master device number (14) are not used for assignment so + * mask these and other higher bits. + */ + + /* Set higher order bits */ + *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM); + + /* Set enumuration device number and broadcast device number */ + set_bit(SDW_ENUM_DEV_NUM, bus->assigned); + set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned); + + /* Set group device numbers and master device number */ + set_bit(SDW_GROUP12_DEV_NUM, bus->assigned); + set_bit(SDW_GROUP13_DEV_NUM, bus->assigned); + set_bit(SDW_MASTER_DEV_NUM, bus->assigned); + + /* + * SDW is an enumerable bus, but devices can be powered off. So, + * they won't be able to report as present. + * + * Create Slave devices based on Slaves described in + * the respective firmware (ACPI/DT) + */ + if (IS_ENABLED(CONFIG_ACPI) && ACPI_HANDLE(bus->dev)) + ret = sdw_acpi_find_slaves(bus); + else if (IS_ENABLED(CONFIG_OF) && bus->dev->of_node) + ret = sdw_of_find_slaves(bus); + else + ret = -ENOTSUPP; /* No ACPI/DT so error out */ + + if (ret) { + dev_err(bus->dev, "Finding slaves failed:%d\n", ret); + return ret; + } + + /* + * Initialize clock values based on Master properties. The max + * frequency is read from max_clk_freq property. Current assumption + * is that the bus will start at highest clock frequency when + * powered on. + * + * Default active bank will be 0 as out of reset the Slaves have + * to start with bank 0 (Table 40 of Spec) + */ + prop = &bus->prop; + bus->params.max_dr_freq = prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR; + bus->params.curr_dr_freq = bus->params.max_dr_freq; + bus->params.curr_bank = SDW_BANK0; + bus->params.next_bank = SDW_BANK1; + + return 0; +} +EXPORT_SYMBOL(sdw_bus_master_add); + +static int sdw_delete_slave(struct device *dev, void *data) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_bus *bus = slave->bus; + + pm_runtime_disable(dev); + + sdw_slave_debugfs_exit(slave); + + mutex_lock(&bus->bus_lock); + + if (slave->dev_num) /* clear dev_num if assigned */ + clear_bit(slave->dev_num, bus->assigned); + + list_del_init(&slave->node); + mutex_unlock(&bus->bus_lock); + + device_unregister(dev); + return 0; +} + +/** + * sdw_bus_master_delete() - delete the bus master instance + * @bus: bus to be deleted + * + * Remove the instance, delete the child devices. + */ +void sdw_bus_master_delete(struct sdw_bus *bus) +{ + device_for_each_child(bus->dev, NULL, sdw_delete_slave); + sdw_master_device_del(bus); + + sdw_bus_debugfs_exit(bus); + ida_free(&sdw_ida, bus->id); +} +EXPORT_SYMBOL(sdw_bus_master_delete); + +/* + * SDW IO Calls + */ + +static inline int find_response_code(enum sdw_command_response resp) +{ + switch (resp) { + case SDW_CMD_OK: + return 0; + + case SDW_CMD_IGNORED: + return -ENODATA; + + case SDW_CMD_TIMEOUT: + return -ETIMEDOUT; + + default: + return -EIO; + } +} + +static inline int do_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg(bus, msg); + ret = find_response_code(resp); + + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static inline int do_transfer_defer(struct sdw_bus *bus, + struct sdw_msg *msg, + struct sdw_defer *defer) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + defer->msg = msg; + defer->length = msg->len; + init_completion(&defer->complete); + + for (i = 0; i <= retry; i++) { + resp = bus->ops->xfer_msg_defer(bus, msg, defer); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num) +{ + int retry = bus->prop.err_threshold; + enum sdw_command_response resp; + int ret = 0, i; + + for (i = 0; i <= retry; i++) { + resp = bus->ops->reset_page_addr(bus, dev_num); + ret = find_response_code(resp); + /* if cmd is ok or ignored return */ + if (ret == 0 || ret == -ENODATA) + return ret; + } + + return ret; +} + +static int sdw_transfer_unlocked(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int ret; + + ret = do_transfer(bus, msg); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + return ret; +} + +/** + * sdw_transfer() - Synchronous transfer message to a SDW Slave device + * @bus: SDW bus + * @msg: SDW message to be xfered + */ +int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg) +{ + int ret; + + mutex_lock(&bus->msg_lock); + + ret = sdw_transfer_unlocked(bus, msg); + + mutex_unlock(&bus->msg_lock); + + return ret; +} + +/** + * sdw_transfer_defer() - Asynchronously transfer message to a SDW Slave device + * @bus: SDW bus + * @msg: SDW message to be xfered + * @defer: Defer block for signal completion + * + * Caller needs to hold the msg_lock lock while calling this + */ +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer) +{ + int ret; + + if (!bus->ops->xfer_msg_defer) + return -ENOTSUPP; + + ret = do_transfer_defer(bus, msg, defer); + if (ret != 0 && ret != -ENODATA) + dev_err(bus->dev, "Defer trf on Slave %d failed:%d\n", + msg->dev_num, ret); + + if (msg->page) + sdw_reset_page(bus, msg->dev_num); + + return ret; +} + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf) +{ + memset(msg, 0, sizeof(*msg)); + msg->addr = addr; /* addr is 16 bit and truncated here */ + msg->len = count; + msg->dev_num = dev_num; + msg->flags = flags; + msg->buf = buf; + + if (addr < SDW_REG_NO_PAGE) /* no paging area */ + return 0; + + if (addr >= SDW_REG_MAX) { /* illegal addr */ + pr_err("SDW: Invalid address %x passed\n", addr); + return -EINVAL; + } + + if (addr < SDW_REG_OPTIONAL_PAGE) { /* 32k but no page */ + if (slave && !slave->prop.paging_support) + return 0; + /* no need for else as that will fall-through to paging */ + } + + /* paging mandatory */ + if (dev_num == SDW_ENUM_DEV_NUM || dev_num == SDW_BROADCAST_DEV_NUM) { + pr_err("SDW: Invalid device for paging :%d\n", dev_num); + return -EINVAL; + } + + if (!slave) { + pr_err("SDW: No slave for paging addr\n"); + return -EINVAL; + } + + if (!slave->prop.paging_support) { + dev_err(&slave->dev, + "address %x needs paging but no support\n", addr); + return -EINVAL; + } + + msg->addr_page1 = FIELD_GET(SDW_SCP_ADDRPAGE1_MASK, addr); + msg->addr_page2 = FIELD_GET(SDW_SCP_ADDRPAGE2_MASK, addr); + msg->addr |= BIT(15); + msg->page = true; + + return 0; +} + +/* + * Read/Write IO functions. + * no_pm versions can only be called by the bus, e.g. while enumerating or + * handling suspend-resume sequences. + * all clients need to use the pm versions + */ + +static int +sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_READ, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +static int +sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, slave, addr, count, + slave->dev_num, SDW_MSG_FLAG_WRITE, val); + if (ret < 0) + return ret; + + return sdw_transfer(slave->bus, &msg); +} + +int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite_no_pm(slave, addr, 1, &value); +} +EXPORT_SYMBOL(sdw_write_no_pm); + +static int +sdw_bread_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr) +{ + struct sdw_msg msg; + u8 buf; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_READ, &buf); + if (ret) + return ret; + + ret = sdw_transfer(bus, &msg); + if (ret < 0) + return ret; + + return buf; +} + +static int +sdw_bwrite_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_WRITE, &value); + if (ret) + return ret; + + return sdw_transfer(bus, &msg); +} + +int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr) +{ + struct sdw_msg msg; + u8 buf; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_READ, &buf); + if (ret) + return ret; + + ret = sdw_transfer_unlocked(bus, &msg); + if (ret < 0) + return ret; + + return buf; +} +EXPORT_SYMBOL(sdw_bread_no_pm_unlocked); + +int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value) +{ + struct sdw_msg msg; + int ret; + + ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num, + SDW_MSG_FLAG_WRITE, &value); + if (ret) + return ret; + + return sdw_transfer_unlocked(bus, &msg); +} +EXPORT_SYMBOL(sdw_bwrite_no_pm_unlocked); + +int sdw_read_no_pm(struct sdw_slave *slave, u32 addr) +{ + u8 buf; + int ret; + + ret = sdw_nread_no_pm(slave, addr, 1, &buf); + if (ret < 0) + return ret; + else + return buf; +} +EXPORT_SYMBOL(sdw_read_no_pm); + +static int sdw_update_no_pm(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +{ + int tmp; + + tmp = sdw_read_no_pm(slave, addr); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | val; + return sdw_write_no_pm(slave, addr, tmp); +} + +/** + * sdw_nread() - Read "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + int ret; + + ret = pm_runtime_get_sync(&slave->dev); + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(&slave->dev); + return ret; + } + + ret = sdw_nread_no_pm(slave, addr, count, val); + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put(&slave->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nread); + +/** + * sdw_nwrite() - Write "n" contiguous SDW Slave registers + * @slave: SDW Slave + * @addr: Register address + * @count: length + * @val: Buffer for values to be read + */ +int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val) +{ + int ret; + + ret = pm_runtime_get_sync(&slave->dev); + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(&slave->dev); + return ret; + } + + ret = sdw_nwrite_no_pm(slave, addr, count, val); + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put(&slave->dev); + + return ret; +} +EXPORT_SYMBOL(sdw_nwrite); + +/** + * sdw_read() - Read a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + */ +int sdw_read(struct sdw_slave *slave, u32 addr) +{ + u8 buf; + int ret; + + ret = sdw_nread(slave, addr, 1, &buf); + if (ret < 0) + return ret; + + return buf; +} +EXPORT_SYMBOL(sdw_read); + +/** + * sdw_write() - Write a SDW Slave register + * @slave: SDW Slave + * @addr: Register address + * @value: Register value + */ +int sdw_write(struct sdw_slave *slave, u32 addr, u8 value) +{ + return sdw_nwrite(slave, addr, 1, &value); +} +EXPORT_SYMBOL(sdw_write); + +/* + * SDW alert handling + */ + +/* called with bus_lock held */ +static struct sdw_slave *sdw_get_slave(struct sdw_bus *bus, int i) +{ + struct sdw_slave *slave = NULL; + + list_for_each_entry(slave, &bus->slaves, node) { + if (slave->dev_num == i) + return slave; + } + + return NULL; +} + +static int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id) +{ + if (slave->id.mfg_id != id.mfg_id || + slave->id.part_id != id.part_id || + slave->id.class_id != id.class_id || + (slave->id.unique_id != SDW_IGNORED_UNIQUE_ID && + slave->id.unique_id != id.unique_id)) + return -ENODEV; + + return 0; +} + +/* called with bus_lock held */ +static int sdw_get_device_num(struct sdw_slave *slave) +{ + int bit; + + bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES); + if (bit == SDW_MAX_DEVICES) { + bit = -ENODEV; + goto err; + } + + /* + * Do not update dev_num in Slave data structure here, + * Update once program dev_num is successful + */ + set_bit(bit, slave->bus->assigned); + +err: + return bit; +} + +static int sdw_assign_device_num(struct sdw_slave *slave) +{ + int ret, dev_num; + bool new_device = false; + + /* check first if device number is assigned, if so reuse that */ + if (!slave->dev_num) { + if (!slave->dev_num_sticky) { + mutex_lock(&slave->bus->bus_lock); + dev_num = sdw_get_device_num(slave); + mutex_unlock(&slave->bus->bus_lock); + if (dev_num < 0) { + dev_err(slave->bus->dev, "Get dev_num failed: %d\n", + dev_num); + return dev_num; + } + slave->dev_num = dev_num; + slave->dev_num_sticky = dev_num; + new_device = true; + } else { + slave->dev_num = slave->dev_num_sticky; + } + } + + if (!new_device) + dev_dbg(slave->bus->dev, + "Slave already registered, reusing dev_num:%d\n", + slave->dev_num); + + /* Clear the slave->dev_num to transfer message on device 0 */ + dev_num = slave->dev_num; + slave->dev_num = 0; + + ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num); + if (ret < 0) { + dev_err(&slave->dev, "Program device_num %d failed: %d\n", + dev_num, ret); + return ret; + } + + /* After xfer of msg, restore dev_num */ + slave->dev_num = slave->dev_num_sticky; + + return 0; +} + +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id) +{ + dev_dbg(bus->dev, "SDW Slave Addr: %llx\n", addr); + + id->sdw_version = SDW_VERSION(addr); + id->unique_id = SDW_UNIQUE_ID(addr); + id->mfg_id = SDW_MFG_ID(addr); + id->part_id = SDW_PART_ID(addr); + id->class_id = SDW_CLASS_ID(addr); + + dev_dbg(bus->dev, + "SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x\n", + id->class_id, id->part_id, id->mfg_id, + id->unique_id, id->sdw_version); +} + +static int sdw_program_device_num(struct sdw_bus *bus) +{ + u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0}; + struct sdw_slave *slave, *_s; + struct sdw_slave_id id; + struct sdw_msg msg; + bool found; + int count = 0, ret; + u64 addr; + + /* No Slave, so use raw xfer api */ + ret = sdw_fill_msg(&msg, NULL, SDW_SCP_DEVID_0, + SDW_NUM_DEV_ID_REGISTERS, 0, SDW_MSG_FLAG_READ, buf); + if (ret < 0) + return ret; + + do { + ret = sdw_transfer(bus, &msg); + if (ret == -ENODATA) { /* end of device id reads */ + dev_dbg(bus->dev, "No more devices to enumerate\n"); + ret = 0; + break; + } + if (ret < 0) { + dev_err(bus->dev, "DEVID read fail:%d\n", ret); + break; + } + + /* + * Construct the addr and extract. Cast the higher shift + * bits to avoid truncation due to size limit. + */ + addr = buf[5] | (buf[4] << 8) | (buf[3] << 16) | + ((u64)buf[2] << 24) | ((u64)buf[1] << 32) | + ((u64)buf[0] << 40); + + sdw_extract_slave_id(bus, addr, &id); + + found = false; + /* Now compare with entries */ + list_for_each_entry_safe(slave, _s, &bus->slaves, node) { + if (sdw_compare_devid(slave, id) == 0) { + found = true; + + /* + * Assign a new dev_num to this Slave and + * not mark it present. It will be marked + * present after it reports ATTACHED on new + * dev_num + */ + ret = sdw_assign_device_num(slave); + if (ret) { + dev_err(slave->bus->dev, + "Assign dev_num failed:%d\n", + ret); + return ret; + } + + break; + } + } + + if (!found) { + /* TODO: Park this device in Group 13 */ + + /* + * add Slave device even if there is no platform + * firmware description. There will be no driver probe + * but the user/integration will be able to see the + * device, enumeration status and device number in sysfs + */ + sdw_slave_add(bus, &id, NULL); + + dev_err(bus->dev, "Slave Entry not found\n"); + } + + count++; + + /* + * Check till error out or retry (count) exhausts. + * Device can drop off and rejoin during enumeration + * so count till twice the bound. + */ + + } while (ret == 0 && count < (SDW_MAX_DEVICES * 2)); + + return ret; +} + +static void sdw_modify_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + mutex_lock(&slave->bus->bus_lock); + + dev_vdbg(&slave->dev, + "%s: changing status slave %d status %d new status %d\n", + __func__, slave->dev_num, slave->status, status); + + if (status == SDW_SLAVE_UNATTACHED) { + dev_dbg(&slave->dev, + "%s: initializing enumeration and init completion for Slave %d\n", + __func__, slave->dev_num); + + reinit_completion(&slave->enumeration_complete); + reinit_completion(&slave->initialization_complete); + + } else if ((status == SDW_SLAVE_ATTACHED) && + (slave->status == SDW_SLAVE_UNATTACHED)) { + dev_dbg(&slave->dev, + "%s: signaling enumeration completion for Slave %d\n", + __func__, slave->dev_num); + + complete_all(&slave->enumeration_complete); + } + slave->status = status; + mutex_unlock(&slave->bus->bus_lock); +} + +static enum sdw_clk_stop_mode sdw_get_clk_stop_mode(struct sdw_slave *slave) +{ + enum sdw_clk_stop_mode mode; + + /* + * Query for clock stop mode if Slave implements + * ops->get_clk_stop_mode, else read from property. + */ + if (slave->ops && slave->ops->get_clk_stop_mode) { + mode = slave->ops->get_clk_stop_mode(slave); + } else { + if (slave->prop.clk_stop_mode1) + mode = SDW_CLK_STOP_MODE1; + else + mode = SDW_CLK_STOP_MODE0; + } + + return mode; +} + +static int sdw_slave_clk_stop_callback(struct sdw_slave *slave, + enum sdw_clk_stop_mode mode, + enum sdw_clk_stop_type type) +{ + int ret; + + if (slave->ops && slave->ops->clk_stop) { + ret = slave->ops->clk_stop(slave, mode, type); + if (ret < 0) { + dev_err(&slave->dev, + "Clk Stop type =%d failed: %d\n", type, ret); + return ret; + } + } + + return 0; +} + +static int sdw_slave_clk_stop_prepare(struct sdw_slave *slave, + enum sdw_clk_stop_mode mode, + bool prepare) +{ + bool wake_en; + u32 val = 0; + int ret; + + wake_en = slave->prop.wake_capable; + + if (prepare) { + val = SDW_SCP_SYSTEMCTRL_CLK_STP_PREP; + + if (mode == SDW_CLK_STOP_MODE1) + val |= SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1; + + if (wake_en) + val |= SDW_SCP_SYSTEMCTRL_WAKE_UP_EN; + } else { + val = sdw_read_no_pm(slave, SDW_SCP_SYSTEMCTRL); + + val &= ~(SDW_SCP_SYSTEMCTRL_CLK_STP_PREP); + } + + ret = sdw_write_no_pm(slave, SDW_SCP_SYSTEMCTRL, val); + + if (ret != 0) + dev_err(&slave->dev, + "Clock Stop prepare failed for slave: %d", ret); + + return ret; +} + +static int sdw_bus_wait_for_clk_prep_deprep(struct sdw_bus *bus, u16 dev_num) +{ + int retry = bus->clk_stop_timeout; + int val; + + do { + val = sdw_bread_no_pm(bus, dev_num, SDW_SCP_STAT) & + SDW_SCP_STAT_CLK_STP_NF; + if (!val) { + dev_info(bus->dev, "clock stop prep/de-prep done slave:%d", + dev_num); + return 0; + } + + usleep_range(1000, 1500); + retry--; + } while (retry); + + dev_err(bus->dev, "clock stop prep/de-prep failed slave:%d", + dev_num); + + return -ETIMEDOUT; +} + +/** + * sdw_bus_prep_clk_stop: prepare Slave(s) for clock stop + * + * @bus: SDW bus instance + * + * Query Slave for clock stop mode and prepare for that mode. + */ +int sdw_bus_prep_clk_stop(struct sdw_bus *bus) +{ + enum sdw_clk_stop_mode slave_mode; + bool simple_clk_stop = true; + struct sdw_slave *slave; + bool is_slave = false; + int ret = 0; + + /* + * In order to save on transition time, prepare + * each Slave and then wait for all Slave(s) to be + * prepared for clock stop. + */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + /* Identify if Slave(s) are available on Bus */ + is_slave = true; + + slave_mode = sdw_get_clk_stop_mode(slave); + slave->curr_clk_stop_mode = slave_mode; + + ret = sdw_slave_clk_stop_callback(slave, slave_mode, + SDW_CLK_PRE_PREPARE); + if (ret < 0) { + dev_err(&slave->dev, + "pre-prepare failed:%d", ret); + return ret; + } + + ret = sdw_slave_clk_stop_prepare(slave, + slave_mode, true); + if (ret < 0) { + dev_err(&slave->dev, + "pre-prepare failed:%d", ret); + return ret; + } + + if (slave_mode == SDW_CLK_STOP_MODE1) + simple_clk_stop = false; + } + + if (is_slave && !simple_clk_stop) { + ret = sdw_bus_wait_for_clk_prep_deprep(bus, + SDW_BROADCAST_DEV_NUM); + if (ret < 0) + return ret; + } + + /* Don't need to inform slaves if there is no slave attached */ + if (!is_slave) + return ret; + + /* Inform slaves that prep is done */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + slave_mode = slave->curr_clk_stop_mode; + + if (slave_mode == SDW_CLK_STOP_MODE1) { + ret = sdw_slave_clk_stop_callback(slave, + slave_mode, + SDW_CLK_POST_PREPARE); + + if (ret < 0) { + dev_err(&slave->dev, + "post-prepare failed:%d", ret); + } + } + } + + return ret; +} +EXPORT_SYMBOL(sdw_bus_prep_clk_stop); + +/** + * sdw_bus_clk_stop: stop bus clock + * + * @bus: SDW bus instance + * + * After preparing the Slaves for clock stop, stop the clock by broadcasting + * write to SCP_CTRL register. + */ +int sdw_bus_clk_stop(struct sdw_bus *bus) +{ + int ret; + + /* + * broadcast clock stop now, attached Slaves will ACK this, + * unattached will ignore + */ + ret = sdw_bwrite_no_pm(bus, SDW_BROADCAST_DEV_NUM, + SDW_SCP_CTRL, SDW_SCP_CTRL_CLK_STP_NOW); + if (ret < 0) { + if (ret == -ENODATA) + dev_dbg(bus->dev, + "ClockStopNow Broadcast msg ignored %d", ret); + else + dev_err(bus->dev, + "ClockStopNow Broadcast msg failed %d", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_bus_clk_stop); + +/** + * sdw_bus_exit_clk_stop: Exit clock stop mode + * + * @bus: SDW bus instance + * + * This De-prepares the Slaves by exiting Clock Stop Mode 0. For the Slaves + * exiting Clock Stop Mode 1, they will be de-prepared after they enumerate + * back. + */ +int sdw_bus_exit_clk_stop(struct sdw_bus *bus) +{ + enum sdw_clk_stop_mode mode; + bool simple_clk_stop = true; + struct sdw_slave *slave; + bool is_slave = false; + int ret; + + /* + * In order to save on transition time, de-prepare + * each Slave and then wait for all Slave(s) to be + * de-prepared after clock resume. + */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + /* Identify if Slave(s) are available on Bus */ + is_slave = true; + + mode = slave->curr_clk_stop_mode; + + if (mode == SDW_CLK_STOP_MODE1) { + simple_clk_stop = false; + continue; + } + + ret = sdw_slave_clk_stop_callback(slave, mode, + SDW_CLK_PRE_DEPREPARE); + if (ret < 0) + dev_warn(&slave->dev, + "clk stop deprep failed:%d", ret); + + ret = sdw_slave_clk_stop_prepare(slave, mode, + false); + + if (ret < 0) + dev_warn(&slave->dev, + "clk stop deprep failed:%d", ret); + } + + if (is_slave && !simple_clk_stop) + sdw_bus_wait_for_clk_prep_deprep(bus, SDW_BROADCAST_DEV_NUM); + + /* + * Don't need to call slave callback function if there is no slave + * attached + */ + if (!is_slave) + return 0; + + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num) + continue; + + if (slave->status != SDW_SLAVE_ATTACHED && + slave->status != SDW_SLAVE_ALERT) + continue; + + mode = slave->curr_clk_stop_mode; + sdw_slave_clk_stop_callback(slave, mode, + SDW_CLK_POST_DEPREPARE); + } + + return 0; +} +EXPORT_SYMBOL(sdw_bus_exit_clk_stop); + +int sdw_configure_dpn_intr(struct sdw_slave *slave, + int port, bool enable, int mask) +{ + u32 addr; + int ret; + u8 val = 0; + + if (slave->bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) { + dev_dbg(&slave->dev, "TEST FAIL interrupt %s\n", + enable ? "on" : "off"); + mask |= SDW_DPN_INT_TEST_FAIL; + } + + addr = SDW_DPN_INTMASK(port); + + /* Set/Clear port ready interrupt mask */ + if (enable) { + val |= mask; + val |= SDW_DPN_INT_PORT_READY; + } else { + val &= ~(mask); + val &= ~SDW_DPN_INT_PORT_READY; + } + + ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val); + if (ret < 0) + dev_err(slave->bus->dev, + "SDW_DPN_INTMASK write failed:%d\n", val); + + return ret; +} + +static int sdw_slave_set_frequency(struct sdw_slave *slave) +{ + u32 mclk_freq = slave->bus->prop.mclk_freq; + u32 curr_freq = slave->bus->params.curr_dr_freq >> 1; + unsigned int scale; + u8 scale_index; + u8 base; + int ret; + + /* + * frequency base and scale registers are required for SDCA + * devices. They may also be used for 1.2+/non-SDCA devices, + * but we will need a DisCo property to cover this case + */ + if (!slave->id.class_id) + return 0; + + if (!mclk_freq) { + dev_err(&slave->dev, + "no bus MCLK, cannot set SDW_SCP_BUS_CLOCK_BASE\n"); + return -EINVAL; + } + + /* + * map base frequency using Table 89 of SoundWire 1.2 spec. + * The order of the tests just follows the specification, this + * is not a selection between possible values or a search for + * the best value but just a mapping. Only one case per platform + * is relevant. + * Some BIOS have inconsistent values for mclk_freq but a + * correct root so we force the mclk_freq to avoid variations. + */ + if (!(19200000 % mclk_freq)) { + mclk_freq = 19200000; + base = SDW_SCP_BASE_CLOCK_19200000_HZ; + } else if (!(24000000 % mclk_freq)) { + mclk_freq = 24000000; + base = SDW_SCP_BASE_CLOCK_24000000_HZ; + } else if (!(24576000 % mclk_freq)) { + mclk_freq = 24576000; + base = SDW_SCP_BASE_CLOCK_24576000_HZ; + } else if (!(22579200 % mclk_freq)) { + mclk_freq = 22579200; + base = SDW_SCP_BASE_CLOCK_22579200_HZ; + } else if (!(32000000 % mclk_freq)) { + mclk_freq = 32000000; + base = SDW_SCP_BASE_CLOCK_32000000_HZ; + } else { + dev_err(&slave->dev, + "Unsupported clock base, mclk %d\n", + mclk_freq); + return -EINVAL; + } + + if (mclk_freq % curr_freq) { + dev_err(&slave->dev, + "mclk %d is not multiple of bus curr_freq %d\n", + mclk_freq, curr_freq); + return -EINVAL; + } + + scale = mclk_freq / curr_freq; + + /* + * map scale to Table 90 of SoundWire 1.2 spec - and check + * that the scale is a power of two and maximum 64 + */ + scale_index = ilog2(scale); + + if (BIT(scale_index) != scale || scale_index > 6) { + dev_err(&slave->dev, + "No match found for scale %d, bus mclk %d curr_freq %d\n", + scale, mclk_freq, curr_freq); + return -EINVAL; + } + scale_index++; + + ret = sdw_write_no_pm(slave, SDW_SCP_BUS_CLOCK_BASE, base); + if (ret < 0) { + dev_err(&slave->dev, + "SDW_SCP_BUS_CLOCK_BASE write failed:%d\n", ret); + return ret; + } + + /* initialize scale for both banks */ + ret = sdw_write_no_pm(slave, SDW_SCP_BUSCLOCK_SCALE_B0, scale_index); + if (ret < 0) { + dev_err(&slave->dev, + "SDW_SCP_BUSCLOCK_SCALE_B0 write failed:%d\n", ret); + return ret; + } + ret = sdw_write_no_pm(slave, SDW_SCP_BUSCLOCK_SCALE_B1, scale_index); + if (ret < 0) + dev_err(&slave->dev, + "SDW_SCP_BUSCLOCK_SCALE_B1 write failed:%d\n", ret); + + dev_dbg(&slave->dev, + "Configured bus base %d, scale %d, mclk %d, curr_freq %d\n", + base, scale_index, mclk_freq, curr_freq); + + return ret; +} + +static int sdw_initialize_slave(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int ret; + u8 val; + + ret = sdw_slave_set_frequency(slave); + if (ret < 0) + return ret; + + /* + * Set SCP_INT1_MASK register, typically bus clash and + * implementation-defined interrupt mask. The Parity detection + * may not always be correct on startup so its use is + * device-dependent, it might e.g. only be enabled in + * steady-state after a couple of frames. + */ + val = slave->prop.scp_int1_mask; + + /* Enable SCP interrupts */ + ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, val, val); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INTMASK1 write failed:%d\n", ret); + return ret; + } + + /* No need to continue if DP0 is not present */ + if (!slave->prop.dp0_prop) + return 0; + + /* Enable DP0 interrupts */ + val = prop->dp0_prop->imp_def_interrupts; + val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE; + + ret = sdw_update_no_pm(slave, SDW_DP0_INTMASK, val, val); + if (ret < 0) + dev_err(slave->bus->dev, + "SDW_DP0_INTMASK read failed:%d\n", ret); + return ret; +} + +static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, status2, ret, count = 0; + + status = sdw_read(slave, SDW_DP0_INT); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d\n", status); + return status; + } + + do { + if (status & SDW_DP0_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port 0\n"); + clear |= SDW_DP0_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only for + * ports implementing Channel Prepare state machine (CP_SM) + */ + + if (status & SDW_DP0_INT_PORT_READY) { + complete(&slave->port_ready[0]); + clear |= SDW_DP0_INT_PORT_READY; + } + + if (status & SDW_DP0_INT_BRA_FAILURE) { + dev_err(&slave->dev, "BRA failed\n"); + clear |= SDW_DP0_INT_BRA_FAILURE; + } + + impl_int_mask = SDW_DP0_INT_IMPDEF1 | + SDW_DP0_INT_IMPDEF2 | SDW_DP0_INT_IMPDEF3; + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, SDW_DP0_INT, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT write failed:%d\n", ret); + return ret; + } + + /* Read DP0 interrupt again */ + status2 = sdw_read(slave, SDW_DP0_INT); + if (status2 < 0) { + dev_err(slave->bus->dev, + "SDW_DP0_INT read failed:%d\n", status2); + return status2; + } + status &= status2; + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on DP0 read\n"); + + return ret; +} + +static int sdw_handle_port_interrupt(struct sdw_slave *slave, + int port, u8 *slave_status) +{ + u8 clear = 0, impl_int_mask; + int status, status2, ret, count = 0; + u32 addr; + + if (port == 0) + return sdw_handle_dp0_interrupt(slave, slave_status); + + addr = SDW_DPN_INT(port); + status = sdw_read(slave, addr); + if (status < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d\n", status); + + return status; + } + + do { + if (status & SDW_DPN_INT_TEST_FAIL) { + dev_err(&slave->dev, "Test fail for port:%d\n", port); + clear |= SDW_DPN_INT_TEST_FAIL; + } + + /* + * Assumption: PORT_READY interrupt will be received only + * for ports implementing CP_SM. + */ + if (status & SDW_DPN_INT_PORT_READY) { + complete(&slave->port_ready[port]); + clear |= SDW_DPN_INT_PORT_READY; + } + + impl_int_mask = SDW_DPN_INT_IMPDEF1 | + SDW_DPN_INT_IMPDEF2 | SDW_DPN_INT_IMPDEF3; + + if (status & impl_int_mask) { + clear |= impl_int_mask; + *slave_status = clear; + } + + /* clear the interrupt */ + ret = sdw_write(slave, addr, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT write failed:%d\n", ret); + return ret; + } + + /* Read DPN interrupt again */ + status2 = sdw_read(slave, addr); + if (status2 < 0) { + dev_err(slave->bus->dev, + "SDW_DPN_INT read failed:%d\n", status2); + return status2; + } + status &= status2; + + count++; + + /* we can get alerts while processing so keep retrying */ + } while (status != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on port read"); + + return ret; +} + +static int sdw_handle_slave_alerts(struct sdw_slave *slave) +{ + struct sdw_slave_intr_status slave_intr; + u8 clear = 0, bit, port_status[15] = {0}; + int port_num, stat, ret, count = 0; + unsigned long port; + bool slave_notify = false; + u8 buf, buf2[2], _buf, _buf2[2]; + bool parity_check; + bool parity_quirk; + + sdw_modify_slave_status(slave, SDW_SLAVE_ALERT); + + ret = pm_runtime_get_sync(&slave->dev); + if (ret < 0 && ret != -EACCES) { + dev_err(&slave->dev, "Failed to resume device: %d\n", ret); + pm_runtime_put_noidle(&slave->dev); + return ret; + } + + /* Read Intstat 1, Intstat 2 and Intstat 3 registers */ + ret = sdw_read(slave, SDW_SCP_INT1); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d\n", ret); + goto io_err; + } + buf = ret; + + ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, buf2); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT2/3 read failed:%d\n", ret); + goto io_err; + } + + do { + /* + * Check parity, bus clash and Slave (impl defined) + * interrupt + */ + if (buf & SDW_SCP_INT1_PARITY) { + parity_check = slave->prop.scp_int1_mask & SDW_SCP_INT1_PARITY; + parity_quirk = !slave->first_interrupt_done && + (slave->prop.quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY); + + if (parity_check && !parity_quirk) + dev_err(&slave->dev, "Parity error detected\n"); + clear |= SDW_SCP_INT1_PARITY; + } + + if (buf & SDW_SCP_INT1_BUS_CLASH) { + if (slave->prop.scp_int1_mask & SDW_SCP_INT1_BUS_CLASH) + dev_err(&slave->dev, "Bus clash detected\n"); + clear |= SDW_SCP_INT1_BUS_CLASH; + } + + /* + * When bus clash or parity errors are detected, such errors + * are unlikely to be recoverable errors. + * TODO: In such scenario, reset bus. Make this configurable + * via sysfs property with bus reset being the default. + */ + + if (buf & SDW_SCP_INT1_IMPL_DEF) { + if (slave->prop.scp_int1_mask & SDW_SCP_INT1_IMPL_DEF) { + dev_dbg(&slave->dev, "Slave impl defined interrupt\n"); + slave_notify = true; + } + clear |= SDW_SCP_INT1_IMPL_DEF; + } + + /* Check port 0 - 3 interrupts */ + port = buf & SDW_SCP_INT1_PORT0_3; + + /* To get port number corresponding to bits, shift it */ + port = FIELD_GET(SDW_SCP_INT1_PORT0_3, port); + for_each_set_bit(bit, &port, 8) { + sdw_handle_port_interrupt(slave, bit, + &port_status[bit]); + } + + /* Check if cascade 2 interrupt is present */ + if (buf & SDW_SCP_INT1_SCP2_CASCADE) { + port = buf2[0] & SDW_SCP_INTSTAT2_PORT4_10; + for_each_set_bit(bit, &port, 8) { + /* scp2 ports start from 4 */ + port_num = bit + 3; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* now check last cascade */ + if (buf2[0] & SDW_SCP_INTSTAT2_SCP3_CASCADE) { + port = buf2[1] & SDW_SCP_INTSTAT3_PORT11_14; + for_each_set_bit(bit, &port, 8) { + /* scp3 ports start from 11 */ + port_num = bit + 10; + sdw_handle_port_interrupt(slave, + port_num, + &port_status[port_num]); + } + } + + /* Update the Slave driver */ + if (slave_notify && slave->ops && + slave->ops->interrupt_callback) { + slave_intr.control_port = clear; + memcpy(slave_intr.port, &port_status, + sizeof(slave_intr.port)); + + slave->ops->interrupt_callback(slave, &slave_intr); + } + + /* Ack interrupt */ + ret = sdw_write(slave, SDW_SCP_INT1, clear); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 write failed:%d\n", ret); + goto io_err; + } + + /* at this point all initial interrupt sources were handled */ + slave->first_interrupt_done = true; + + /* + * Read status again to ensure no new interrupts arrived + * while servicing interrupts. + */ + ret = sdw_read(slave, SDW_SCP_INT1); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT1 read failed:%d\n", ret); + goto io_err; + } + _buf = ret; + + ret = sdw_nread(slave, SDW_SCP_INTSTAT2, 2, _buf2); + if (ret < 0) { + dev_err(slave->bus->dev, + "SDW_SCP_INT2/3 read failed:%d\n", ret); + goto io_err; + } + + /* Make sure no interrupts are pending */ + buf &= _buf; + buf2[0] &= _buf2[0]; + buf2[1] &= _buf2[1]; + stat = buf || buf2[0] || buf2[1]; + + /* + * Exit loop if Slave is continuously in ALERT state even + * after servicing the interrupt multiple times. + */ + count++; + + /* we can get alerts while processing so keep retrying */ + } while (stat != 0 && count < SDW_READ_INTR_CLEAR_RETRY); + + if (count == SDW_READ_INTR_CLEAR_RETRY) + dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read\n"); + +io_err: + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + return ret; +} + +static int sdw_update_slave_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + unsigned long time; + + if (!slave->probed) { + /* + * the slave status update is typically handled in an + * interrupt thread, which can race with the driver + * probe, e.g. when a module needs to be loaded. + * + * make sure the probe is complete before updating + * status. + */ + time = wait_for_completion_timeout(&slave->probe_complete, + msecs_to_jiffies(DEFAULT_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Probe not complete, timed out\n"); + return -ETIMEDOUT; + } + } + + if (!slave->ops || !slave->ops->update_status) + return 0; + + return slave->ops->update_status(slave, status); +} + +/** + * sdw_handle_slave_status() - Handle Slave status + * @bus: SDW bus instance + * @status: Status for all Slave(s) + */ +int sdw_handle_slave_status(struct sdw_bus *bus, + enum sdw_slave_status status[]) +{ + enum sdw_slave_status prev_status; + struct sdw_slave *slave; + bool attached_initializing; + int i, ret = 0; + + /* first check if any Slaves fell off the bus */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (status[i] == SDW_SLAVE_UNATTACHED && + slave->status != SDW_SLAVE_UNATTACHED) + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + } + + if (status[0] == SDW_SLAVE_ATTACHED) { + dev_dbg(bus->dev, "Slave attached, programming device number\n"); + ret = sdw_program_device_num(bus); + if (ret) + dev_err(bus->dev, "Slave attach failed: %d\n", ret); + /* + * programming a device number will have side effects, + * so we deal with other devices at a later time + */ + return ret; + } + + /* Continue to check other slave statuses */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + attached_initializing = false; + + switch (status[i]) { + case SDW_SLAVE_UNATTACHED: + if (slave->status == SDW_SLAVE_UNATTACHED) + break; + + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + break; + + case SDW_SLAVE_ALERT: + ret = sdw_handle_slave_alerts(slave); + if (ret) + dev_err(bus->dev, + "Slave %d alert handling failed: %d\n", + i, ret); + break; + + case SDW_SLAVE_ATTACHED: + if (slave->status == SDW_SLAVE_ATTACHED) + break; + + prev_status = slave->status; + sdw_modify_slave_status(slave, SDW_SLAVE_ATTACHED); + + if (prev_status == SDW_SLAVE_ALERT) + break; + + attached_initializing = true; + + ret = sdw_initialize_slave(slave); + if (ret) + dev_err(bus->dev, + "Slave %d initialization failed: %d\n", + i, ret); + + break; + + default: + dev_err(bus->dev, "Invalid slave %d status:%d\n", + i, status[i]); + break; + } + + ret = sdw_update_slave_status(slave, status[i]); + if (ret) + dev_err(slave->bus->dev, + "Update Slave status failed:%d\n", ret); + if (attached_initializing) { + dev_dbg(&slave->dev, + "%s: signaling initialization completion for Slave %d\n", + __func__, slave->dev_num); + + complete_all(&slave->initialization_complete); + + /* + * If the manager became pm_runtime active, the peripherals will be + * restarted and attach, but their pm_runtime status may remain + * suspended. If the 'update_slave_status' callback initiates + * any sort of deferred processing, this processing would not be + * cancelled on pm_runtime suspend. + * To avoid such zombie states, we queue a request to resume. + * This would be a no-op in case the peripheral was being resumed + * by e.g. the ALSA/ASoC framework. + */ + pm_request_resume(&slave->dev); + } + } + + return ret; +} +EXPORT_SYMBOL(sdw_handle_slave_status); + +void sdw_clear_slave_status(struct sdw_bus *bus, u32 request) +{ + struct sdw_slave *slave; + int i; + + /* Check all non-zero devices */ + for (i = 1; i <= SDW_MAX_DEVICES; i++) { + mutex_lock(&bus->bus_lock); + if (test_bit(i, bus->assigned) == false) { + mutex_unlock(&bus->bus_lock); + continue; + } + mutex_unlock(&bus->bus_lock); + + slave = sdw_get_slave(bus, i); + if (!slave) + continue; + + if (slave->status != SDW_SLAVE_UNATTACHED) { + sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED); + slave->first_interrupt_done = false; + } + + /* keep track of request, used in pm_runtime resume */ + slave->unattach_request = request; + } +} +EXPORT_SYMBOL(sdw_clear_slave_status); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h new file mode 100644 index 000000000..2e049d39c --- /dev/null +++ b/drivers/soundwire/bus.h @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright(c) 2015-17 Intel Corporation. */ + +#ifndef __SDW_BUS_H +#define __SDW_BUS_H + +#define DEFAULT_BANK_SWITCH_TIMEOUT 3000 +#define DEFAULT_PROBE_TIMEOUT 2000 + +#if IS_ENABLED(CONFIG_ACPI) +int sdw_acpi_find_slaves(struct sdw_bus *bus); +#else +static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) +{ + return -ENOTSUPP; +} +#endif + +int sdw_of_find_slaves(struct sdw_bus *bus); +void sdw_extract_slave_id(struct sdw_bus *bus, + u64 addr, struct sdw_slave_id *id); +int sdw_slave_add(struct sdw_bus *bus, struct sdw_slave_id *id, + struct fwnode_handle *fwnode); +int sdw_master_device_add(struct sdw_bus *bus, struct device *parent, + struct fwnode_handle *fwnode); +int sdw_master_device_del(struct sdw_bus *bus); + +#ifdef CONFIG_DEBUG_FS +void sdw_bus_debugfs_init(struct sdw_bus *bus); +void sdw_bus_debugfs_exit(struct sdw_bus *bus); +void sdw_slave_debugfs_init(struct sdw_slave *slave); +void sdw_slave_debugfs_exit(struct sdw_slave *slave); +void sdw_debugfs_init(void); +void sdw_debugfs_exit(void); +#else +static inline void sdw_bus_debugfs_init(struct sdw_bus *bus) {} +static inline void sdw_bus_debugfs_exit(struct sdw_bus *bus) {} +static inline void sdw_slave_debugfs_init(struct sdw_slave *slave) {} +static inline void sdw_slave_debugfs_exit(struct sdw_slave *slave) {} +static inline void sdw_debugfs_init(void) {} +static inline void sdw_debugfs_exit(void) {} +#endif + +enum { + SDW_MSG_FLAG_READ = 0, + SDW_MSG_FLAG_WRITE, +}; + +/** + * struct sdw_msg - Message structure + * @addr: Register address accessed in the Slave + * @len: number of messages + * @dev_num: Slave device number + * @addr_page1: SCP address page 1 Slave register + * @addr_page2: SCP address page 2 Slave register + * @flags: transfer flags, indicate if xfer is read or write + * @buf: message data buffer + * @ssp_sync: Send message at SSP (Stream Synchronization Point) + * @page: address requires paging + */ +struct sdw_msg { + u16 addr; + u16 len; + u8 dev_num; + u8 addr_page1; + u8 addr_page2; + u8 flags; + u8 *buf; + bool ssp_sync; + bool page; +}; + +#define SDW_DOUBLE_RATE_FACTOR 2 +#define SDW_STRM_RATE_GROUPING 1 + +extern int sdw_rows[SDW_FRAME_ROWS]; +extern int sdw_cols[SDW_FRAME_COLS]; + +int sdw_find_row_index(int row); +int sdw_find_col_index(int col); + +/** + * sdw_port_runtime: Runtime port parameters for Master or Slave + * + * @num: Port number. For audio streams, valid port number ranges from + * [1,14] + * @ch_mask: Channel mask + * @transport_params: Transport parameters + * @port_params: Port parameters + * @port_node: List node for Master or Slave port_list + * + * SoundWire spec has no mention of ports for Master interface but the + * concept is logically extended. + */ +struct sdw_port_runtime { + int num; + int ch_mask; + struct sdw_transport_params transport_params; + struct sdw_port_params port_params; + struct list_head port_node; +}; + +/** + * sdw_slave_runtime: Runtime Stream parameters for Slave + * + * @slave: Slave handle + * @direction: Data direction for Slave + * @ch_count: Number of channels handled by the Slave for + * this stream + * @m_rt_node: sdw_master_runtime list node + * @port_list: List of Slave Ports configured for this stream + */ +struct sdw_slave_runtime { + struct sdw_slave *slave; + enum sdw_data_direction direction; + unsigned int ch_count; + struct list_head m_rt_node; + struct list_head port_list; +}; + +/** + * sdw_master_runtime: Runtime stream parameters for Master + * + * @bus: Bus handle + * @stream: Stream runtime handle + * @direction: Data direction for Master + * @ch_count: Number of channels handled by the Master for + * this stream, can be zero. + * @slave_rt_list: Slave runtime list + * @port_list: List of Master Ports configured for this stream, can be zero. + * @stream_node: sdw_stream_runtime master_list node + * @bus_node: sdw_bus m_rt_list node + */ +struct sdw_master_runtime { + struct sdw_bus *bus; + struct sdw_stream_runtime *stream; + enum sdw_data_direction direction; + unsigned int ch_count; + struct list_head slave_rt_list; + struct list_head port_list; + struct list_head stream_node; + struct list_head bus_node; +}; + +struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, + enum sdw_data_direction direction, + unsigned int port_num); +int sdw_configure_dpn_intr(struct sdw_slave *slave, int port, + bool enable, int mask); + +int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg); +int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, + struct sdw_defer *defer); + +#define SDW_READ_INTR_CLEAR_RETRY 10 + +int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, + u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf); + +/* Retrieve and return channel count from channel mask */ +static inline int sdw_ch_mask_to_ch(int ch_mask) +{ + int c = 0; + + for (c = 0; ch_mask; ch_mask >>= 1) + c += ch_mask & 1; + + return c; +} + +/* Fill transport parameter data structure */ +static inline void sdw_fill_xport_params(struct sdw_transport_params *params, + int port_num, bool grp_ctrl_valid, + int grp_ctrl, int sample_int, + int off1, int off2, + int hstart, int hstop, + int pack_mode, int lane_ctrl) +{ + params->port_num = port_num; + params->blk_grp_ctrl_valid = grp_ctrl_valid; + params->blk_grp_ctrl = grp_ctrl; + params->sample_interval = sample_int; + params->offset1 = off1; + params->offset2 = off2; + params->hstart = hstart; + params->hstop = hstop; + params->blk_pkg_mode = pack_mode; + params->lane_ctrl = lane_ctrl; +} + +/* Fill port parameter data structure */ +static inline void sdw_fill_port_params(struct sdw_port_params *params, + int port_num, int bps, + int flow_mode, int data_mode) +{ + params->num = port_num; + params->bps = bps; + params->flow_mode = flow_mode; + params->data_mode = data_mode; +} + +/* Read-Modify-Write Slave register */ +static inline int sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) +{ + int tmp; + + tmp = sdw_read(slave, addr); + if (tmp < 0) + return tmp; + + tmp = (tmp & ~mask) | val; + return sdw_write(slave, addr, tmp); +} + +/* broadcast read/write for tests */ +int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr); +int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value); + +/* + * At the moment we only track Master-initiated hw_reset. + * Additional fields can be added as needed + */ +#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0) + +void sdw_clear_slave_status(struct sdw_bus *bus, u32 request); +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size); + +#endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c new file mode 100644 index 000000000..2e8986ccc --- /dev/null +++ b/drivers/soundwire/bus_type.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation. + +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_domain.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" +#include "sysfs_local.h" + +/** + * sdw_get_device_id - find the matching SoundWire device id + * @slave: SoundWire Slave Device + * @drv: SoundWire Slave Driver + * + * The match is done by comparing the mfg_id and part_id from the + * struct sdw_device_id. + */ +static const struct sdw_device_id * +sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) +{ + const struct sdw_device_id *id; + + for (id = drv->id_table; id && id->mfg_id; id++) + if (slave->id.mfg_id == id->mfg_id && + slave->id.part_id == id->part_id && + (!id->sdw_version || + slave->id.sdw_version == id->sdw_version) && + (!id->class_id || + slave->id.class_id == id->class_id)) + return id; + + return NULL; +} + +static int sdw_bus_match(struct device *dev, struct device_driver *ddrv) +{ + struct sdw_slave *slave; + struct sdw_driver *drv; + int ret = 0; + + if (is_sdw_slave(dev)) { + slave = dev_to_sdw_dev(dev); + drv = drv_to_sdw_driver(ddrv); + + ret = !!sdw_get_device_id(slave, drv); + } + return ret; +} + +int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size) +{ + /* modalias is sdw:m<mfg_id>p<part_id>v<version>c<class_id> */ + + return snprintf(buf, size, "sdw:m%04Xp%04Xv%02Xc%02X\n", + slave->id.mfg_id, slave->id.part_id, + slave->id.sdw_version, slave->id.class_id); +} + +int sdw_slave_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + char modalias[32]; + + sdw_slave_modalias(slave, modalias, sizeof(modalias)); + + if (add_uevent_var(env, "MODALIAS=%s", modalias)) + return -ENOMEM; + + return 0; +} + +struct bus_type sdw_bus_type = { + .name = "soundwire", + .match = sdw_bus_match, +}; +EXPORT_SYMBOL_GPL(sdw_bus_type); + +static int sdw_drv_probe(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + const struct sdw_device_id *id; + int ret; + + /* + * fw description is mandatory to bind + */ + if (!dev->fwnode) + return -ENODEV; + + if (!IS_ENABLED(CONFIG_ACPI) && !dev->of_node) + return -ENODEV; + + id = sdw_get_device_id(slave, drv); + if (!id) + return -ENODEV; + + slave->ops = drv->ops; + + /* + * attach to power domain but don't turn on (last arg) + */ + ret = dev_pm_domain_attach(dev, false); + if (ret) + return ret; + + ret = drv->probe(slave, id); + if (ret) { + dev_err(dev, "Probe of %s failed: %d\n", drv->name, ret); + dev_pm_domain_detach(dev, false); + return ret; + } + + /* device is probed so let's read the properties now */ + if (slave->ops && slave->ops->read_prop) + slave->ops->read_prop(slave); + + /* init the sysfs as we have properties now */ + ret = sdw_slave_sysfs_init(slave); + if (ret < 0) + dev_warn(dev, "Slave sysfs init failed:%d\n", ret); + + /* + * Check for valid clk_stop_timeout, use DisCo worst case value of + * 300ms + * + * TODO: check the timeouts and driver removal case + */ + if (slave->prop.clk_stop_timeout == 0) + slave->prop.clk_stop_timeout = 300; + + slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout, + slave->prop.clk_stop_timeout); + + slave->probed = true; + complete(&slave->probe_complete); + + dev_dbg(dev, "probe complete\n"); + + return 0; +} + +static int sdw_drv_remove(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + int ret = 0; + + if (drv->remove) + ret = drv->remove(slave); + + dev_pm_domain_detach(dev, false); + + return ret; +} + +static void sdw_drv_shutdown(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); + + if (drv->shutdown) + drv->shutdown(slave); +} + +/** + * __sdw_register_driver() - register a SoundWire Slave driver + * @drv: driver to register + * @owner: owning module/driver + * + * Return: zero on success, else a negative error code. + */ +int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) +{ + drv->driver.bus = &sdw_bus_type; + + if (!drv->probe) { + pr_err("driver %s didn't provide SDW probe routine\n", + drv->name); + return -EINVAL; + } + + drv->driver.owner = owner; + drv->driver.probe = sdw_drv_probe; + drv->driver.remove = sdw_drv_remove; + drv->driver.shutdown = sdw_drv_shutdown; + + return driver_register(&drv->driver); +} +EXPORT_SYMBOL_GPL(__sdw_register_driver); + +/** + * sdw_unregister_driver() - unregisters the SoundWire Slave driver + * @drv: driver to unregister + */ +void sdw_unregister_driver(struct sdw_driver *drv) +{ + driver_unregister(&drv->driver); +} +EXPORT_SYMBOL_GPL(sdw_unregister_driver); + +static int __init sdw_bus_init(void) +{ + sdw_debugfs_init(); + return bus_register(&sdw_bus_type); +} + +static void __exit sdw_bus_exit(void) +{ + sdw_debugfs_exit(); + bus_unregister(&sdw_bus_type); +} + +postcore_initcall(sdw_bus_init); +module_exit(sdw_bus_exit); + +MODULE_DESCRIPTION("SoundWire bus"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c new file mode 100644 index 000000000..18e7d158f --- /dev/null +++ b/drivers/soundwire/cadence_master.c @@ -0,0 +1,1716 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * Cadence SoundWire Master module + * Used by Master driver + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/workqueue.h> +#include "bus.h" +#include "cadence_master.h" + +static int interrupt_mask; +module_param_named(cnds_mcp_int_mask, interrupt_mask, int, 0444); +MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); + +#define CDNS_MCP_CONFIG 0x0 + +#define CDNS_MCP_CONFIG_MCMD_RETRY GENMASK(27, 24) +#define CDNS_MCP_CONFIG_MPREQ_DELAY GENMASK(20, 16) +#define CDNS_MCP_CONFIG_MMASTER BIT(7) +#define CDNS_MCP_CONFIG_BUS_REL BIT(6) +#define CDNS_MCP_CONFIG_SNIFFER BIT(5) +#define CDNS_MCP_CONFIG_SSPMOD BIT(4) +#define CDNS_MCP_CONFIG_CMD BIT(3) +#define CDNS_MCP_CONFIG_OP GENMASK(2, 0) +#define CDNS_MCP_CONFIG_OP_NORMAL 0 + +#define CDNS_MCP_CONTROL 0x4 + +#define CDNS_MCP_CONTROL_RST_DELAY GENMASK(10, 8) +#define CDNS_MCP_CONTROL_CMD_RST BIT(7) +#define CDNS_MCP_CONTROL_SOFT_RST BIT(6) +#define CDNS_MCP_CONTROL_SW_RST BIT(5) +#define CDNS_MCP_CONTROL_HW_RST BIT(4) +#define CDNS_MCP_CONTROL_CLK_PAUSE BIT(3) +#define CDNS_MCP_CONTROL_CLK_STOP_CLR BIT(2) +#define CDNS_MCP_CONTROL_CMD_ACCEPT BIT(1) +#define CDNS_MCP_CONTROL_BLOCK_WAKEUP BIT(0) + +#define CDNS_MCP_CMDCTRL 0x8 + +#define CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR BIT(2) + +#define CDNS_MCP_SSPSTAT 0xC +#define CDNS_MCP_FRAME_SHAPE 0x10 +#define CDNS_MCP_FRAME_SHAPE_INIT 0x14 +#define CDNS_MCP_FRAME_SHAPE_COL_MASK GENMASK(2, 0) +#define CDNS_MCP_FRAME_SHAPE_ROW_MASK GENMASK(7, 3) + +#define CDNS_MCP_CONFIG_UPDATE 0x18 +#define CDNS_MCP_CONFIG_UPDATE_BIT BIT(0) + +#define CDNS_MCP_PHYCTRL 0x1C +#define CDNS_MCP_SSP_CTRL0 0x20 +#define CDNS_MCP_SSP_CTRL1 0x28 +#define CDNS_MCP_CLK_CTRL0 0x30 +#define CDNS_MCP_CLK_CTRL1 0x38 +#define CDNS_MCP_CLK_MCLKD_MASK GENMASK(7, 0) + +#define CDNS_MCP_STAT 0x40 + +#define CDNS_MCP_STAT_ACTIVE_BANK BIT(20) +#define CDNS_MCP_STAT_CLK_STOP BIT(16) + +#define CDNS_MCP_INTSTAT 0x44 +#define CDNS_MCP_INTMASK 0x48 + +#define CDNS_MCP_INT_IRQ BIT(31) +#define CDNS_MCP_INT_RESERVED1 GENMASK(30, 17) +#define CDNS_MCP_INT_WAKEUP BIT(16) +#define CDNS_MCP_INT_SLAVE_RSVD BIT(15) +#define CDNS_MCP_INT_SLAVE_ALERT BIT(14) +#define CDNS_MCP_INT_SLAVE_ATTACH BIT(13) +#define CDNS_MCP_INT_SLAVE_NATTACH BIT(12) +#define CDNS_MCP_INT_SLAVE_MASK GENMASK(15, 12) +#define CDNS_MCP_INT_DPINT BIT(11) +#define CDNS_MCP_INT_CTRL_CLASH BIT(10) +#define CDNS_MCP_INT_DATA_CLASH BIT(9) +#define CDNS_MCP_INT_PARITY BIT(8) +#define CDNS_MCP_INT_CMD_ERR BIT(7) +#define CDNS_MCP_INT_RESERVED2 GENMASK(6, 4) +#define CDNS_MCP_INT_RX_NE BIT(3) +#define CDNS_MCP_INT_RX_WL BIT(2) +#define CDNS_MCP_INT_TXE BIT(1) +#define CDNS_MCP_INT_TXF BIT(0) +#define CDNS_MCP_INT_RESERVED (CDNS_MCP_INT_RESERVED1 | CDNS_MCP_INT_RESERVED2) + +#define CDNS_MCP_INTSET 0x4C + +#define CDNS_MCP_SLAVE_STAT 0x50 +#define CDNS_MCP_SLAVE_STAT_MASK GENMASK(1, 0) + +#define CDNS_MCP_SLAVE_INTSTAT0 0x54 +#define CDNS_MCP_SLAVE_INTSTAT1 0x58 +#define CDNS_MCP_SLAVE_INTSTAT_NPRESENT BIT(0) +#define CDNS_MCP_SLAVE_INTSTAT_ATTACHED BIT(1) +#define CDNS_MCP_SLAVE_INTSTAT_ALERT BIT(2) +#define CDNS_MCP_SLAVE_INTSTAT_RESERVED BIT(3) +#define CDNS_MCP_SLAVE_STATUS_BITS GENMASK(3, 0) +#define CDNS_MCP_SLAVE_STATUS_NUM 4 + +#define CDNS_MCP_SLAVE_INTMASK0 0x5C +#define CDNS_MCP_SLAVE_INTMASK1 0x60 + +#define CDNS_MCP_SLAVE_INTMASK0_MASK GENMASK(31, 0) +#define CDNS_MCP_SLAVE_INTMASK1_MASK GENMASK(15, 0) + +#define CDNS_MCP_PORT_INTSTAT 0x64 +#define CDNS_MCP_PDI_STAT 0x6C + +#define CDNS_MCP_FIFOLEVEL 0x78 +#define CDNS_MCP_FIFOSTAT 0x7C +#define CDNS_MCP_RX_FIFO_AVAIL GENMASK(5, 0) + +#define CDNS_MCP_CMD_BASE 0x80 +#define CDNS_MCP_RESP_BASE 0x80 +#define CDNS_MCP_CMD_LEN 0x20 +#define CDNS_MCP_CMD_WORD_LEN 0x4 + +#define CDNS_MCP_CMD_SSP_TAG BIT(31) +#define CDNS_MCP_CMD_COMMAND GENMASK(30, 28) +#define CDNS_MCP_CMD_DEV_ADDR GENMASK(27, 24) +#define CDNS_MCP_CMD_REG_ADDR GENMASK(23, 8) +#define CDNS_MCP_CMD_REG_DATA GENMASK(7, 0) + +#define CDNS_MCP_CMD_READ 2 +#define CDNS_MCP_CMD_WRITE 3 + +#define CDNS_MCP_RESP_RDATA GENMASK(15, 8) +#define CDNS_MCP_RESP_ACK BIT(0) +#define CDNS_MCP_RESP_NACK BIT(1) + +#define CDNS_DP_SIZE 128 + +#define CDNS_DPN_B0_CONFIG(n) (0x100 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_CH_EN(n) (0x104 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_SAMPLE_CTRL(n) (0x108 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_OFFSET_CTRL(n) (0x10C + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_HCTRL(n) (0x110 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B0_ASYNC_CTRL(n) (0x114 + CDNS_DP_SIZE * (n)) + +#define CDNS_DPN_B1_CONFIG(n) (0x118 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_CH_EN(n) (0x11C + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_SAMPLE_CTRL(n) (0x120 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_OFFSET_CTRL(n) (0x124 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_HCTRL(n) (0x128 + CDNS_DP_SIZE * (n)) +#define CDNS_DPN_B1_ASYNC_CTRL(n) (0x12C + CDNS_DP_SIZE * (n)) + +#define CDNS_DPN_CONFIG_BPM BIT(18) +#define CDNS_DPN_CONFIG_BGC GENMASK(17, 16) +#define CDNS_DPN_CONFIG_WL GENMASK(12, 8) +#define CDNS_DPN_CONFIG_PORT_DAT GENMASK(3, 2) +#define CDNS_DPN_CONFIG_PORT_FLOW GENMASK(1, 0) + +#define CDNS_DPN_SAMPLE_CTRL_SI GENMASK(15, 0) + +#define CDNS_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define CDNS_DPN_OFFSET_CTRL_2 GENMASK(15, 8) + +#define CDNS_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define CDNS_DPN_HCTRL_HSTART GENMASK(7, 4) +#define CDNS_DPN_HCTRL_LCTRL GENMASK(10, 8) + +#define CDNS_PORTCTRL 0x130 +#define CDNS_PORTCTRL_TEST_FAILED BIT(1) +#define CDNS_PORTCTRL_DIRN BIT(7) +#define CDNS_PORTCTRL_BANK_INVERT BIT(8) + +#define CDNS_PORT_OFFSET 0x80 + +#define CDNS_PDI_CONFIG(n) (0x1100 + (n) * 16) + +#define CDNS_PDI_CONFIG_SOFT_RESET BIT(24) +#define CDNS_PDI_CONFIG_CHANNEL GENMASK(15, 8) +#define CDNS_PDI_CONFIG_PORT GENMASK(4, 0) + +/* Driver defaults */ +#define CDNS_TX_TIMEOUT 2000 + +#define CDNS_SCP_RX_FIFOLEVEL 0x2 + +/* + * register accessor helpers + */ +static inline u32 cdns_readl(struct sdw_cdns *cdns, int offset) +{ + return readl(cdns->registers + offset); +} + +static inline void cdns_writel(struct sdw_cdns *cdns, int offset, u32 value) +{ + writel(value, cdns->registers + offset); +} + +static inline void cdns_updatel(struct sdw_cdns *cdns, + int offset, u32 mask, u32 val) +{ + u32 tmp; + + tmp = cdns_readl(cdns, offset); + tmp = (tmp & ~mask) | val; + cdns_writel(cdns, offset, tmp); +} + +static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value) +{ + int timeout = 10; + u32 reg_read; + + /* Wait for bit to be set */ + do { + reg_read = readl(cdns->registers + offset); + if ((reg_read & mask) == value) + return 0; + + timeout--; + usleep_range(50, 100); + } while (timeout != 0); + + return -ETIMEDOUT; +} + +static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value) +{ + writel(value, cdns->registers + offset); + + /* Wait for bit to be self cleared */ + return cdns_set_wait(cdns, offset, value, 0); +} + +/* + * all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL + * need to be confirmed with a write to MCP_CONFIG_UPDATE + */ +static int cdns_config_update(struct sdw_cdns *cdns) +{ + int ret; + + if (sdw_cdns_is_clock_stop(cdns)) { + dev_err(cdns->dev, "Cannot program MCP_CONFIG_UPDATE in ClockStopMode\n"); + return -EINVAL; + } + + ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT); + if (ret < 0) + dev_err(cdns->dev, "Config update timedout\n"); + + return ret; +} + +/* + * debugfs + */ +#ifdef CONFIG_DEBUG_FS + +#define RD_BUF (2 * PAGE_SIZE) + +static ssize_t cdns_sprintf(struct sdw_cdns *cdns, + char *buf, size_t pos, unsigned int reg) +{ + return scnprintf(buf + pos, RD_BUF - pos, + "%4x\t%8x\n", reg, cdns_readl(cdns, reg)); +} + +static int cdns_reg_show(struct seq_file *s, void *data) +{ + struct sdw_cdns *cdns = s->private; + char *buf; + ssize_t ret; + int num_ports; + int i, j; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + ret += scnprintf(buf + ret, RD_BUF - ret, "\nMCP Registers\n"); + /* 8 MCP registers */ + for (i = CDNS_MCP_CONFIG; i <= CDNS_MCP_PHYCTRL; i += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, i); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nStatus & Intr Registers\n"); + /* 13 Status & Intr registers (offsets 0x70 and 0x74 not defined) */ + for (i = CDNS_MCP_STAT; i <= CDNS_MCP_FIFOSTAT; i += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, i); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nSSP & Clk ctrl Registers\n"); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL0); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_SSP_CTRL1); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL0); + ret += cdns_sprintf(cdns, buf, ret, CDNS_MCP_CLK_CTRL1); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn B0 Registers\n"); + + num_ports = cdns->num_ports; + + for (i = 0; i < num_ports; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDP-%d\n", i); + for (j = CDNS_DPN_B0_CONFIG(i); + j < CDNS_DPN_B0_ASYNC_CTRL(i); j += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, j); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn B1 Registers\n"); + for (i = 0; i < num_ports; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDP-%d\n", i); + + for (j = CDNS_DPN_B1_CONFIG(i); + j < CDNS_DPN_B1_ASYNC_CTRL(i); j += sizeof(u32)) + ret += cdns_sprintf(cdns, buf, ret, j); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nDPn Control Registers\n"); + for (i = 0; i < num_ports; i++) + ret += cdns_sprintf(cdns, buf, ret, + CDNS_PORTCTRL + i * CDNS_PORT_OFFSET); + + ret += scnprintf(buf + ret, RD_BUF - ret, + "\nPDIn Config Registers\n"); + + /* number of PDI and ports is interchangeable */ + for (i = 0; i < num_ports; i++) + ret += cdns_sprintf(cdns, buf, ret, CDNS_PDI_CONFIG(i)); + + seq_printf(s, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(cdns_reg); + +static int cdns_hw_reset(void *data, u64 value) +{ + struct sdw_cdns *cdns = data; + int ret; + + if (value != 1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + ret = sdw_cdns_exit_reset(cdns); + + dev_dbg(cdns->dev, "link hw_reset done: %d\n", ret); + + return ret; +} + +DEFINE_DEBUGFS_ATTRIBUTE(cdns_hw_reset_fops, NULL, cdns_hw_reset, "%llu\n"); + +static int cdns_parity_error_injection(void *data, u64 value) +{ + struct sdw_cdns *cdns = data; + struct sdw_bus *bus; + int ret; + + if (value != 1) + return -EINVAL; + + bus = &cdns->bus; + + /* + * Resume Master device. If this results in a bus reset, the + * Slave devices will re-attach and be re-enumerated. + */ + ret = pm_runtime_get_sync(bus->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(cdns->dev, + "pm_runtime_get_sync failed in %s, ret %d\n", + __func__, ret); + pm_runtime_put_noidle(bus->dev); + return ret; + } + + /* + * wait long enough for Slave(s) to be in steady state. This + * does not need to be super precise. + */ + msleep(200); + + /* + * Take the bus lock here to make sure that any bus transactions + * will be queued while we inject a parity error on a dummy read + */ + mutex_lock(&bus->bus_lock); + + /* program hardware to inject parity error */ + cdns_updatel(cdns, CDNS_MCP_CMDCTRL, + CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR, + CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR); + + /* commit changes */ + cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT, + CDNS_MCP_CONFIG_UPDATE_BIT); + + /* do a broadcast dummy read to avoid bus clashes */ + ret = sdw_bread_no_pm_unlocked(&cdns->bus, 0xf, SDW_SCP_DEVID_0); + dev_info(cdns->dev, "parity error injection, read: %d\n", ret); + + /* program hardware to disable parity error */ + cdns_updatel(cdns, CDNS_MCP_CMDCTRL, + CDNS_MCP_CMDCTRL_INSERT_PARITY_ERR, + 0); + + /* commit changes */ + cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT, + CDNS_MCP_CONFIG_UPDATE_BIT); + + /* Continue bus operation with parity error injection disabled */ + mutex_unlock(&bus->bus_lock); + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + /* + * allow Master device to enter pm_runtime suspend. This may + * also result in Slave devices suspending. + */ + pm_runtime_mark_last_busy(bus->dev); + pm_runtime_put_autosuspend(bus->dev); + + return 0; +} + +DEFINE_DEBUGFS_ATTRIBUTE(cdns_parity_error_fops, NULL, + cdns_parity_error_injection, "%llu\n"); + +/** + * sdw_cdns_debugfs_init() - Cadence debugfs init + * @cdns: Cadence instance + * @root: debugfs root + */ +void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root) +{ + debugfs_create_file("cdns-registers", 0400, root, cdns, &cdns_reg_fops); + + debugfs_create_file("cdns-hw-reset", 0200, root, cdns, + &cdns_hw_reset_fops); + + debugfs_create_file("cdns-parity-error-injection", 0200, root, cdns, + &cdns_parity_error_fops); +} +EXPORT_SYMBOL_GPL(sdw_cdns_debugfs_init); + +#endif /* CONFIG_DEBUG_FS */ + +/* + * IO Calls + */ +static enum sdw_command_response +cdns_fill_msg_resp(struct sdw_cdns *cdns, + struct sdw_msg *msg, int count, int offset) +{ + int nack = 0, no_ack = 0; + int i; + + /* check message response */ + for (i = 0; i < count; i++) { + if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) { + no_ack = 1; + dev_dbg_ratelimited(cdns->dev, "Msg Ack not received\n"); + } + if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) { + nack = 1; + dev_err_ratelimited(cdns->dev, "Msg NACK received\n"); + } + } + + if (nack) { + dev_err_ratelimited(cdns->dev, "Msg NACKed for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_dbg_ratelimited(cdns->dev, "Msg ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + + if (msg->flags == SDW_MSG_FLAG_READ) { + /* fill response */ + for (i = 0; i < count; i++) + msg->buf[i + offset] = FIELD_GET(CDNS_MCP_RESP_RDATA, + cdns->response_buf[i]); + } + + return SDW_CMD_OK; +} + +static void cdns_read_response(struct sdw_cdns *cdns) +{ + u32 num_resp, cmd_base; + int i; + + /* RX_FIFO_AVAIL can be 2 entries more than the FIFO size */ + BUILD_BUG_ON(ARRAY_SIZE(cdns->response_buf) < CDNS_MCP_CMD_LEN + 2); + + num_resp = cdns_readl(cdns, CDNS_MCP_FIFOSTAT); + num_resp &= CDNS_MCP_RX_FIFO_AVAIL; + if (num_resp > ARRAY_SIZE(cdns->response_buf)) { + dev_warn(cdns->dev, "RX AVAIL %d too long\n", num_resp); + num_resp = ARRAY_SIZE(cdns->response_buf); + } + + cmd_base = CDNS_MCP_CMD_BASE; + + for (i = 0; i < num_resp; i++) { + cdns->response_buf[i] = cdns_readl(cdns, cmd_base); + cmd_base += CDNS_MCP_CMD_WORD_LEN; + } +} + +static enum sdw_command_response +_cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd, + int offset, int count, bool defer) +{ + unsigned long time; + u32 base, i, data; + u16 addr; + + /* Program the watermark level for RX FIFO */ + if (cdns->msg_count != count) { + cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, count); + cdns->msg_count = count; + } + + base = CDNS_MCP_CMD_BASE; + addr = msg->addr; + + for (i = 0; i < count; i++) { + data = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num); + data |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, cmd); + data |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, addr); + addr++; + + if (msg->flags == SDW_MSG_FLAG_WRITE) + data |= msg->buf[i + offset]; + + data |= FIELD_PREP(CDNS_MCP_CMD_SSP_TAG, msg->ssp_sync); + cdns_writel(cdns, base, data); + base += CDNS_MCP_CMD_WORD_LEN; + } + + if (defer) + return SDW_CMD_OK; + + /* wait for timeout or response */ + time = wait_for_completion_timeout(&cdns->tx_complete, + msecs_to_jiffies(CDNS_TX_TIMEOUT)); + if (!time) { + dev_err(cdns->dev, "IO transfer timed out, cmd %d device %d addr %x len %d\n", + cmd, msg->dev_num, msg->addr, msg->len); + msg->len = 0; + + /* Drain anything in the RX_FIFO */ + cdns_read_response(cdns); + + return SDW_CMD_TIMEOUT; + } + + return cdns_fill_msg_resp(cdns, msg, count, offset); +} + +static enum sdw_command_response +cdns_program_scp_addr(struct sdw_cdns *cdns, struct sdw_msg *msg) +{ + int nack = 0, no_ack = 0; + unsigned long time; + u32 data[2], base; + int i; + + /* Program the watermark level for RX FIFO */ + if (cdns->msg_count != CDNS_SCP_RX_FIFOLEVEL) { + cdns_writel(cdns, CDNS_MCP_FIFOLEVEL, CDNS_SCP_RX_FIFOLEVEL); + cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL; + } + + data[0] = FIELD_PREP(CDNS_MCP_CMD_DEV_ADDR, msg->dev_num); + data[0] |= FIELD_PREP(CDNS_MCP_CMD_COMMAND, 0x3); + data[1] = data[0]; + + data[0] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE1); + data[1] |= FIELD_PREP(CDNS_MCP_CMD_REG_ADDR, SDW_SCP_ADDRPAGE2); + + data[0] |= msg->addr_page1; + data[1] |= msg->addr_page2; + + base = CDNS_MCP_CMD_BASE; + cdns_writel(cdns, base, data[0]); + base += CDNS_MCP_CMD_WORD_LEN; + cdns_writel(cdns, base, data[1]); + + time = wait_for_completion_timeout(&cdns->tx_complete, + msecs_to_jiffies(CDNS_TX_TIMEOUT)); + if (!time) { + dev_err(cdns->dev, "SCP Msg trf timed out\n"); + msg->len = 0; + return SDW_CMD_TIMEOUT; + } + + /* check response the writes */ + for (i = 0; i < 2; i++) { + if (!(cdns->response_buf[i] & CDNS_MCP_RESP_ACK)) { + no_ack = 1; + dev_err(cdns->dev, "Program SCP Ack not received\n"); + if (cdns->response_buf[i] & CDNS_MCP_RESP_NACK) { + nack = 1; + dev_err(cdns->dev, "Program SCP NACK received\n"); + } + } + } + + /* For NACK, NO ack, don't return err if we are in Broadcast mode */ + if (nack) { + dev_err_ratelimited(cdns->dev, + "SCP_addrpage NACKed for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_dbg_ratelimited(cdns->dev, + "SCP_addrpage ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + + return SDW_CMD_OK; +} + +static int cdns_prep_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int *cmd) +{ + int ret; + + if (msg->page) { + ret = cdns_program_scp_addr(cdns, msg); + if (ret) { + msg->len = 0; + return ret; + } + } + + switch (msg->flags) { + case SDW_MSG_FLAG_READ: + *cmd = CDNS_MCP_CMD_READ; + break; + + case SDW_MSG_FLAG_WRITE: + *cmd = CDNS_MCP_CMD_WRITE; + break; + + default: + dev_err(cdns->dev, "Invalid msg cmd: %d\n", msg->flags); + return -EINVAL; + } + + return 0; +} + +enum sdw_command_response +cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int cmd = 0, ret, i; + + ret = cdns_prep_msg(cdns, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + + for (i = 0; i < msg->len / CDNS_MCP_CMD_LEN; i++) { + ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN, + CDNS_MCP_CMD_LEN, false); + if (ret < 0) + goto exit; + } + + if (!(msg->len % CDNS_MCP_CMD_LEN)) + goto exit; + + ret = _cdns_xfer_msg(cdns, msg, cmd, i * CDNS_MCP_CMD_LEN, + msg->len % CDNS_MCP_CMD_LEN, false); + +exit: + return ret; +} +EXPORT_SYMBOL(cdns_xfer_msg); + +enum sdw_command_response +cdns_xfer_msg_defer(struct sdw_bus *bus, + struct sdw_msg *msg, struct sdw_defer *defer) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int cmd = 0, ret; + + /* for defer only 1 message is supported */ + if (msg->len > 1) + return -ENOTSUPP; + + ret = cdns_prep_msg(cdns, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + + cdns->defer = defer; + cdns->defer->length = msg->len; + + return _cdns_xfer_msg(cdns, msg, cmd, 0, msg->len, true); +} +EXPORT_SYMBOL(cdns_xfer_msg_defer); + +enum sdw_command_response +cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_msg msg; + + /* Create dummy message with valid device number */ + memset(&msg, 0, sizeof(msg)); + msg.dev_num = dev_num; + + return cdns_program_scp_addr(cdns, &msg); +} +EXPORT_SYMBOL(cdns_reset_page_addr); + +/* + * IRQ handling + */ + +static int cdns_update_slave_status(struct sdw_cdns *cdns, + u32 slave0, u32 slave1) +{ + enum sdw_slave_status status[SDW_MAX_DEVICES + 1]; + bool is_slave = false; + u64 slave; + u32 mask; + int i, set_status; + + /* combine the two status */ + slave = ((u64)slave1 << 32) | slave0; + memset(status, 0, sizeof(status)); + + for (i = 0; i <= SDW_MAX_DEVICES; i++) { + mask = (slave >> (i * CDNS_MCP_SLAVE_STATUS_NUM)) & + CDNS_MCP_SLAVE_STATUS_BITS; + if (!mask) + continue; + + is_slave = true; + set_status = 0; + + if (mask & CDNS_MCP_SLAVE_INTSTAT_RESERVED) { + status[i] = SDW_SLAVE_RESERVED; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_ATTACHED) { + status[i] = SDW_SLAVE_ATTACHED; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_ALERT) { + status[i] = SDW_SLAVE_ALERT; + set_status++; + } + + if (mask & CDNS_MCP_SLAVE_INTSTAT_NPRESENT) { + status[i] = SDW_SLAVE_UNATTACHED; + set_status++; + } + + /* first check if Slave reported multiple status */ + if (set_status > 1) { + u32 val; + + dev_warn_ratelimited(cdns->dev, + "Slave %d reported multiple Status: %d\n", + i, mask); + + /* check latest status extracted from PING commands */ + val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT); + val >>= (i * 2); + + switch (val & 0x3) { + case 0: + status[i] = SDW_SLAVE_UNATTACHED; + break; + case 1: + status[i] = SDW_SLAVE_ATTACHED; + break; + case 2: + status[i] = SDW_SLAVE_ALERT; + break; + case 3: + default: + status[i] = SDW_SLAVE_RESERVED; + break; + } + + dev_warn_ratelimited(cdns->dev, + "Slave %d status updated to %d\n", + i, status[i]); + + } + } + + if (is_slave) + return sdw_handle_slave_status(&cdns->bus, status); + + return 0; +} + +/** + * sdw_cdns_irq() - Cadence interrupt handler + * @irq: irq number + * @dev_id: irq context + */ +irqreturn_t sdw_cdns_irq(int irq, void *dev_id) +{ + struct sdw_cdns *cdns = dev_id; + u32 int_status; + int ret = IRQ_HANDLED; + + /* Check if the link is up */ + if (!cdns->link_up) + return IRQ_NONE; + + int_status = cdns_readl(cdns, CDNS_MCP_INTSTAT); + + /* check for reserved values read as zero */ + if (int_status & CDNS_MCP_INT_RESERVED) + return IRQ_NONE; + + if (!(int_status & CDNS_MCP_INT_IRQ)) + return IRQ_NONE; + + if (int_status & CDNS_MCP_INT_RX_WL) { + cdns_read_response(cdns); + + if (cdns->defer) { + cdns_fill_msg_resp(cdns, cdns->defer->msg, + cdns->defer->length, 0); + complete(&cdns->defer->complete); + cdns->defer = NULL; + } else { + complete(&cdns->tx_complete); + } + } + + if (int_status & CDNS_MCP_INT_PARITY) { + /* Parity error detected by Master */ + dev_err_ratelimited(cdns->dev, "Parity error\n"); + } + + if (int_status & CDNS_MCP_INT_CTRL_CLASH) { + /* Slave is driving bit slot during control word */ + dev_err_ratelimited(cdns->dev, "Bus clash for control word\n"); + } + + if (int_status & CDNS_MCP_INT_DATA_CLASH) { + /* + * Multiple slaves trying to drive bit slot, or issue with + * ownership of data bits or Slave gone bonkers + */ + dev_err_ratelimited(cdns->dev, "Bus clash for data word\n"); + } + + if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL && + int_status & CDNS_MCP_INT_DPINT) { + u32 port_intstat; + + /* just log which ports report an error */ + port_intstat = cdns_readl(cdns, CDNS_MCP_PORT_INTSTAT); + dev_err_ratelimited(cdns->dev, "DP interrupt: PortIntStat %8x\n", + port_intstat); + + /* clear status w/ write1 */ + cdns_writel(cdns, CDNS_MCP_PORT_INTSTAT, port_intstat); + } + + if (int_status & CDNS_MCP_INT_SLAVE_MASK) { + /* Mask the Slave interrupt and wake thread */ + cdns_updatel(cdns, CDNS_MCP_INTMASK, + CDNS_MCP_INT_SLAVE_MASK, 0); + + int_status &= ~CDNS_MCP_INT_SLAVE_MASK; + + /* + * Deal with possible race condition between interrupt + * handling and disabling interrupts on suspend. + * + * If the master is in the process of disabling + * interrupts, don't schedule a workqueue + */ + if (cdns->interrupt_enabled) + schedule_work(&cdns->work); + } + + cdns_writel(cdns, CDNS_MCP_INTSTAT, int_status); + return ret; +} +EXPORT_SYMBOL(sdw_cdns_irq); + +/** + * To update slave status in a work since we will need to handle + * other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave + * process. + * @work: cdns worker thread + */ +static void cdns_update_slave_status_work(struct work_struct *work) +{ + struct sdw_cdns *cdns = + container_of(work, struct sdw_cdns, work); + u32 slave0, slave1; + + dev_dbg_ratelimited(cdns->dev, "Slave status change\n"); + + slave0 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0); + slave1 = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1); + + cdns_update_slave_status(cdns, slave0, slave1); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave1); + + /* clear and unmask Slave interrupt now */ + cdns_writel(cdns, CDNS_MCP_INTSTAT, CDNS_MCP_INT_SLAVE_MASK); + cdns_updatel(cdns, CDNS_MCP_INTMASK, + CDNS_MCP_INT_SLAVE_MASK, CDNS_MCP_INT_SLAVE_MASK); + +} + +/* + * init routines + */ + +/** + * sdw_cdns_exit_reset() - Program reset parameters and start bus operations + * @cdns: Cadence instance + */ +int sdw_cdns_exit_reset(struct sdw_cdns *cdns) +{ + /* program maximum length reset to be safe */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_RST_DELAY, + CDNS_MCP_CONTROL_RST_DELAY); + + /* use hardware generated reset */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_HW_RST, + CDNS_MCP_CONTROL_HW_RST); + + /* commit changes */ + cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE, + CDNS_MCP_CONFIG_UPDATE_BIT, + CDNS_MCP_CONFIG_UPDATE_BIT); + + /* don't wait here */ + return 0; + +} +EXPORT_SYMBOL(sdw_cdns_exit_reset); + +/** + * sdw_cdns_enable_slave_interrupt() - Enable SDW slave interrupts + * @cdns: Cadence instance + * @state: boolean for true/false + */ +static void cdns_enable_slave_interrupts(struct sdw_cdns *cdns, bool state) +{ + u32 mask; + + mask = cdns_readl(cdns, CDNS_MCP_INTMASK); + if (state) + mask |= CDNS_MCP_INT_SLAVE_MASK; + else + mask &= ~CDNS_MCP_INT_SLAVE_MASK; + + cdns_writel(cdns, CDNS_MCP_INTMASK, mask); +} + +/** + * sdw_cdns_enable_interrupt() - Enable SDW interrupts + * @cdns: Cadence instance + * @state: True if we are trying to enable interrupt. + */ +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state) +{ + u32 slave_intmask0 = 0; + u32 slave_intmask1 = 0; + u32 mask = 0; + + if (!state) + goto update_masks; + + slave_intmask0 = CDNS_MCP_SLAVE_INTMASK0_MASK; + slave_intmask1 = CDNS_MCP_SLAVE_INTMASK1_MASK; + + /* enable detection of all slave state changes */ + mask = CDNS_MCP_INT_SLAVE_MASK; + + /* enable detection of bus issues */ + mask |= CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH | + CDNS_MCP_INT_PARITY; + + /* port interrupt limited to test modes for now */ + if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL) + mask |= CDNS_MCP_INT_DPINT; + + /* enable detection of RX fifo level */ + mask |= CDNS_MCP_INT_RX_WL; + + /* + * CDNS_MCP_INT_IRQ needs to be set otherwise all previous + * settings are irrelevant + */ + mask |= CDNS_MCP_INT_IRQ; + + if (interrupt_mask) /* parameter override */ + mask = interrupt_mask; + +update_masks: + /* clear slave interrupt status before enabling interrupt */ + if (state) { + u32 slave_state; + + slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT0, slave_state); + slave_state = cdns_readl(cdns, CDNS_MCP_SLAVE_INTSTAT1); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTSTAT1, slave_state); + } + cdns->interrupt_enabled = state; + + /* + * Complete any on-going status updates before updating masks, + * and cancel queued status updates. + * + * There could be a race with a new interrupt thrown before + * the 3 mask updates below are complete, so in the interrupt + * we use the 'interrupt_enabled' status to prevent new work + * from being queued. + */ + if (!state) + cancel_work_sync(&cdns->work); + + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK0, slave_intmask0); + cdns_writel(cdns, CDNS_MCP_SLAVE_INTMASK1, slave_intmask1); + cdns_writel(cdns, CDNS_MCP_INTMASK, mask); + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_enable_interrupt); + +static int cdns_allocate_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_pdi **stream, + u32 num, u32 pdi_offset) +{ + struct sdw_cdns_pdi *pdi; + int i; + + if (!num) + return 0; + + pdi = devm_kcalloc(cdns->dev, num, sizeof(*pdi), GFP_KERNEL); + if (!pdi) + return -ENOMEM; + + for (i = 0; i < num; i++) { + pdi[i].num = i + pdi_offset; + } + + *stream = pdi; + return 0; +} + +/** + * sdw_cdns_pdi_init() - PDI initialization routine + * + * @cdns: Cadence instance + * @config: Stream configurations + */ +int sdw_cdns_pdi_init(struct sdw_cdns *cdns, + struct sdw_cdns_stream_config config) +{ + struct sdw_cdns_streams *stream; + int offset; + int ret; + + cdns->pcm.num_bd = config.pcm_bd; + cdns->pcm.num_in = config.pcm_in; + cdns->pcm.num_out = config.pcm_out; + cdns->pdm.num_bd = config.pdm_bd; + cdns->pdm.num_in = config.pdm_in; + cdns->pdm.num_out = config.pdm_out; + + /* Allocate PDIs for PCMs */ + stream = &cdns->pcm; + + /* we allocate PDI0 and PDI1 which are used for Bulk */ + offset = 0; + + ret = cdns_allocate_pdi(cdns, &stream->bd, + stream->num_bd, offset); + if (ret) + return ret; + + offset += stream->num_bd; + + ret = cdns_allocate_pdi(cdns, &stream->in, + stream->num_in, offset); + if (ret) + return ret; + + offset += stream->num_in; + + ret = cdns_allocate_pdi(cdns, &stream->out, + stream->num_out, offset); + if (ret) + return ret; + + /* Update total number of PCM PDIs */ + stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out; + cdns->num_ports = stream->num_pdi; + + /* Allocate PDIs for PDMs */ + stream = &cdns->pdm; + ret = cdns_allocate_pdi(cdns, &stream->bd, + stream->num_bd, offset); + if (ret) + return ret; + + offset += stream->num_bd; + + ret = cdns_allocate_pdi(cdns, &stream->in, + stream->num_in, offset); + if (ret) + return ret; + + offset += stream->num_in; + + ret = cdns_allocate_pdi(cdns, &stream->out, + stream->num_out, offset); + + if (ret) + return ret; + + /* Update total number of PDM PDIs */ + stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out; + cdns->num_ports += stream->num_pdi; + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_pdi_init); + +static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols) +{ + u32 val; + int c; + int r; + + r = sdw_find_row_index(n_rows); + c = sdw_find_col_index(n_cols); + + val = FIELD_PREP(CDNS_MCP_FRAME_SHAPE_ROW_MASK, r); + val |= FIELD_PREP(CDNS_MCP_FRAME_SHAPE_COL_MASK, c); + + return val; +} + +static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) +{ + struct sdw_bus *bus = &cdns->bus; + struct sdw_master_prop *prop = &bus->prop; + u32 val; + u32 ssp_interval; + int divider; + + /* Set clock divider */ + divider = (prop->mclk_freq / prop->max_clk_freq) - 1; + + cdns_updatel(cdns, CDNS_MCP_CLK_CTRL0, + CDNS_MCP_CLK_MCLKD_MASK, divider); + cdns_updatel(cdns, CDNS_MCP_CLK_CTRL1, + CDNS_MCP_CLK_MCLKD_MASK, divider); + + /* + * Frame shape changes after initialization have to be done + * with the bank switch mechanism + */ + val = cdns_set_initial_frame_shape(prop->default_row, + prop->default_col); + cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val); + + /* Set SSP interval to default value */ + ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ; + cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval); + cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval); +} + +/** + * sdw_cdns_init() - Cadence initialization + * @cdns: Cadence instance + */ +int sdw_cdns_init(struct sdw_cdns *cdns) +{ + u32 val; + + cdns_init_clock_ctrl(cdns); + + /* reset msg_count to default value of FIFOLEVEL */ + cdns->msg_count = cdns_readl(cdns, CDNS_MCP_FIFOLEVEL); + + /* flush command FIFOs */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST, + CDNS_MCP_CONTROL_CMD_RST); + + /* Set cmd accept mode */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, + CDNS_MCP_CONTROL_CMD_ACCEPT); + + /* Configure mcp config */ + val = cdns_readl(cdns, CDNS_MCP_CONFIG); + + /* enable bus operations with clock and data */ + val &= ~CDNS_MCP_CONFIG_OP; + val |= CDNS_MCP_CONFIG_OP_NORMAL; + + /* Set cmd mode for Tx and Rx cmds */ + val &= ~CDNS_MCP_CONFIG_CMD; + + /* Disable sniffer mode */ + val &= ~CDNS_MCP_CONFIG_SNIFFER; + + /* Disable auto bus release */ + val &= ~CDNS_MCP_CONFIG_BUS_REL; + + if (cdns->bus.multi_link) + /* Set Multi-master mode to take gsync into account */ + val |= CDNS_MCP_CONFIG_MMASTER; + + /* leave frame delay to hardware default of 0x1F */ + + /* leave command retry to hardware default of 0 */ + + cdns_writel(cdns, CDNS_MCP_CONFIG, val); + + /* changes will be committed later */ + return 0; +} +EXPORT_SYMBOL(sdw_cdns_init); + +int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params) +{ + struct sdw_master_prop *prop = &bus->prop; + struct sdw_cdns *cdns = bus_to_cdns(bus); + int mcp_clkctrl_off; + int divider; + + if (!params->curr_dr_freq) { + dev_err(cdns->dev, "NULL curr_dr_freq\n"); + return -EINVAL; + } + + divider = prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR / + params->curr_dr_freq; + divider--; /* divider is 1/(N+1) */ + + if (params->next_bank) + mcp_clkctrl_off = CDNS_MCP_CLK_CTRL1; + else + mcp_clkctrl_off = CDNS_MCP_CLK_CTRL0; + + cdns_updatel(cdns, mcp_clkctrl_off, CDNS_MCP_CLK_MCLKD_MASK, divider); + + return 0; +} +EXPORT_SYMBOL(cdns_bus_conf); + +static int cdns_port_params(struct sdw_bus *bus, + struct sdw_port_params *p_params, unsigned int bank) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int dpn_config = 0, dpn_config_off; + + if (bank) + dpn_config_off = CDNS_DPN_B1_CONFIG(p_params->num); + else + dpn_config_off = CDNS_DPN_B0_CONFIG(p_params->num); + + dpn_config = cdns_readl(cdns, dpn_config_off); + + u32p_replace_bits(&dpn_config, (p_params->bps - 1), CDNS_DPN_CONFIG_WL); + u32p_replace_bits(&dpn_config, p_params->flow_mode, CDNS_DPN_CONFIG_PORT_FLOW); + u32p_replace_bits(&dpn_config, p_params->data_mode, CDNS_DPN_CONFIG_PORT_DAT); + + cdns_writel(cdns, dpn_config_off, dpn_config); + + return 0; +} + +static int cdns_transport_params(struct sdw_bus *bus, + struct sdw_transport_params *t_params, + enum sdw_reg_bank bank) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int dpn_offsetctrl = 0, dpn_offsetctrl_off; + int dpn_config = 0, dpn_config_off; + int dpn_hctrl = 0, dpn_hctrl_off; + int num = t_params->port_num; + int dpn_samplectrl_off; + + /* + * Note: Only full data port is supported on the Master side for + * both PCM and PDM ports. + */ + + if (bank) { + dpn_config_off = CDNS_DPN_B1_CONFIG(num); + dpn_samplectrl_off = CDNS_DPN_B1_SAMPLE_CTRL(num); + dpn_hctrl_off = CDNS_DPN_B1_HCTRL(num); + dpn_offsetctrl_off = CDNS_DPN_B1_OFFSET_CTRL(num); + } else { + dpn_config_off = CDNS_DPN_B0_CONFIG(num); + dpn_samplectrl_off = CDNS_DPN_B0_SAMPLE_CTRL(num); + dpn_hctrl_off = CDNS_DPN_B0_HCTRL(num); + dpn_offsetctrl_off = CDNS_DPN_B0_OFFSET_CTRL(num); + } + + dpn_config = cdns_readl(cdns, dpn_config_off); + u32p_replace_bits(&dpn_config, t_params->blk_grp_ctrl, CDNS_DPN_CONFIG_BGC); + u32p_replace_bits(&dpn_config, t_params->blk_pkg_mode, CDNS_DPN_CONFIG_BPM); + cdns_writel(cdns, dpn_config_off, dpn_config); + + u32p_replace_bits(&dpn_offsetctrl, t_params->offset1, CDNS_DPN_OFFSET_CTRL_1); + u32p_replace_bits(&dpn_offsetctrl, t_params->offset2, CDNS_DPN_OFFSET_CTRL_2); + cdns_writel(cdns, dpn_offsetctrl_off, dpn_offsetctrl); + + u32p_replace_bits(&dpn_hctrl, t_params->hstart, CDNS_DPN_HCTRL_HSTART); + u32p_replace_bits(&dpn_hctrl, t_params->hstop, CDNS_DPN_HCTRL_HSTOP); + u32p_replace_bits(&dpn_hctrl, t_params->lane_ctrl, CDNS_DPN_HCTRL_LCTRL); + + cdns_writel(cdns, dpn_hctrl_off, dpn_hctrl); + cdns_writel(cdns, dpn_samplectrl_off, (t_params->sample_interval - 1)); + + return 0; +} + +static int cdns_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, unsigned int bank) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + int dpn_chnen_off, ch_mask; + + if (bank) + dpn_chnen_off = CDNS_DPN_B1_CH_EN(enable_ch->port_num); + else + dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->port_num); + + ch_mask = enable_ch->ch_mask * enable_ch->enable; + cdns_writel(cdns, dpn_chnen_off, ch_mask); + + return 0; +} + +static const struct sdw_master_port_ops cdns_port_ops = { + .dpn_set_port_params = cdns_port_params, + .dpn_set_port_transport_params = cdns_transport_params, + .dpn_port_enable_ch = cdns_port_enable, +}; + +/** + * sdw_cdns_is_clock_stop: Check clock status + * + * @cdns: Cadence instance + */ +bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns) +{ + return !!(cdns_readl(cdns, CDNS_MCP_STAT) & CDNS_MCP_STAT_CLK_STOP); +} +EXPORT_SYMBOL(sdw_cdns_is_clock_stop); + +/** + * sdw_cdns_clock_stop: Cadence clock stop configuration routine + * + * @cdns: Cadence instance + * @block_wake: prevent wakes if required by the platform + */ +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake) +{ + bool slave_present = false; + struct sdw_slave *slave; + int ret; + + /* Check suspend status */ + if (sdw_cdns_is_clock_stop(cdns)) { + dev_dbg(cdns->dev, "Clock is already stopped\n"); + return 0; + } + + /* + * Before entering clock stop we mask the Slave + * interrupts. This helps avoid having to deal with e.g. a + * Slave becoming UNATTACHED while the clock is being stopped + */ + cdns_enable_slave_interrupts(cdns, false); + + /* + * For specific platforms, it is required to be able to put + * master into a state in which it ignores wake-up trials + * in clock stop state + */ + if (block_wake) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, + CDNS_MCP_CONTROL_BLOCK_WAKEUP); + + list_for_each_entry(slave, &cdns->bus.slaves, node) { + if (slave->status == SDW_SLAVE_ATTACHED || + slave->status == SDW_SLAVE_ALERT) { + slave_present = true; + break; + } + } + + /* + * This CMD_ACCEPT should be used when there are no devices + * attached on the link when entering clock stop mode. If this is + * not set and there is a broadcast write then the command ignored + * will be treated as a failure + */ + if (!slave_present) + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, + CDNS_MCP_CONTROL_CMD_ACCEPT); + else + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + /* commit changes */ + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + /* Prepare slaves for clock stop */ + if (slave_present) { + ret = sdw_bus_prep_clk_stop(&cdns->bus); + if (ret < 0 && ret != -ENODATA) { + dev_err(cdns->dev, "prepare clock stop failed %d\n", ret); + return ret; + } + } + + /* + * Enter clock stop mode and only report errors if there are + * Slave devices present (ALERT or ATTACHED) + */ + ret = sdw_bus_clk_stop(&cdns->bus); + if (ret < 0 && slave_present && ret != -ENODATA) { + dev_err(cdns->dev, "bus clock stop failed %d", ret); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, + CDNS_MCP_STAT_CLK_STOP, + CDNS_MCP_STAT_CLK_STOP); + if (ret < 0) + dev_err(cdns->dev, "Clock stop failed %d\n", ret); + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_stop); + +/** + * sdw_cdns_clock_restart: Cadence PM clock restart configuration routine + * + * @cdns: Cadence instance + * @bus_reset: context may be lost while in low power modes and the bus + * may require a Severe Reset and re-enumeration after a wake. + */ +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset) +{ + int ret; + + /* unmask Slave interrupts that were masked when stopping the clock */ + cdns_enable_slave_interrupts(cdns, true); + + ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_CLK_STOP_CLR); + if (ret < 0) { + dev_err(cdns->dev, "Couldn't exit from clock stop\n"); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_STAT, CDNS_MCP_STAT_CLK_STOP, 0); + if (ret < 0) { + dev_err(cdns->dev, "clock stop exit failed %d\n", ret); + return ret; + } + + cdns_updatel(cdns, CDNS_MCP_CONTROL, + CDNS_MCP_CONTROL_BLOCK_WAKEUP, 0); + + /* + * clear CMD_ACCEPT so that the command ignored + * will be treated as a failure during a broadcast write + */ + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, 0); + + if (!bus_reset) { + + /* enable bus operations with clock and data */ + cdns_updatel(cdns, CDNS_MCP_CONFIG, + CDNS_MCP_CONFIG_OP, + CDNS_MCP_CONFIG_OP_NORMAL); + + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config_update failed\n", __func__); + return ret; + } + + ret = sdw_bus_exit_clk_stop(&cdns->bus); + if (ret < 0) + dev_err(cdns->dev, "bus failed to exit clock stop %d\n", ret); + } + + return ret; +} +EXPORT_SYMBOL(sdw_cdns_clock_restart); + +/** + * sdw_cdns_probe() - Cadence probe routine + * @cdns: Cadence instance + */ +int sdw_cdns_probe(struct sdw_cdns *cdns) +{ + init_completion(&cdns->tx_complete); + cdns->bus.port_ops = &cdns_port_ops; + + INIT_WORK(&cdns->work, cdns_update_slave_status_work); + return 0; +} +EXPORT_SYMBOL(sdw_cdns_probe); + +int cdns_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, bool pcm, int direction) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_cdns_dma_data *dma; + + if (stream) { + /* first paranoia check */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dma = dai->playback_dma_data; + else + dma = dai->capture_dma_data; + + if (dma) { + dev_err(dai->dev, + "dma_data already allocated for dai %s\n", + dai->name); + return -EINVAL; + } + + /* allocate and set dma info */ + dma = kzalloc(sizeof(*dma), GFP_KERNEL); + if (!dma) + return -ENOMEM; + + if (pcm) + dma->stream_type = SDW_STREAM_PCM; + else + dma->stream_type = SDW_STREAM_PDM; + + dma->bus = &cdns->bus; + dma->link_id = cdns->instance; + + dma->stream = stream; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = dma; + else + dai->capture_dma_data = dma; + } else { + /* for NULL stream we release allocated dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + kfree(dai->playback_dma_data); + dai->playback_dma_data = NULL; + } else { + kfree(dai->capture_dma_data); + dai->capture_dma_data = NULL; + } + } + return 0; +} +EXPORT_SYMBOL(cdns_set_sdw_stream); + +/** + * cdns_find_pdi() - Find a free PDI + * + * @cdns: Cadence instance + * @offset: Starting offset + * @num: Number of PDIs + * @pdi: PDI instances + * @dai_id: DAI id + * + * Find a PDI for a given PDI array. The PDI num and dai_id are + * expected to match, return NULL otherwise. + */ +static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns, + unsigned int offset, + unsigned int num, + struct sdw_cdns_pdi *pdi, + int dai_id) +{ + int i; + + for (i = offset; i < offset + num; i++) + if (pdi[i].num == dai_id) + return &pdi[i]; + + return NULL; +} + +/** + * sdw_cdns_config_stream: Configure a stream + * + * @cdns: Cadence instance + * @ch: Channel count + * @dir: Data direction + * @pdi: PDI to be used + */ +void sdw_cdns_config_stream(struct sdw_cdns *cdns, + u32 ch, u32 dir, struct sdw_cdns_pdi *pdi) +{ + u32 offset, val = 0; + + if (dir == SDW_DATA_DIR_RX) { + val = CDNS_PORTCTRL_DIRN; + + if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL) + val |= CDNS_PORTCTRL_TEST_FAILED; + } + offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET; + cdns_updatel(cdns, offset, + CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED, + val); + + val = pdi->num; + val |= CDNS_PDI_CONFIG_SOFT_RESET; + val |= FIELD_PREP(CDNS_PDI_CONFIG_CHANNEL, (1 << ch) - 1); + cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val); +} +EXPORT_SYMBOL(sdw_cdns_config_stream); + +/** + * sdw_cdns_alloc_pdi() - Allocate a PDI + * + * @cdns: Cadence instance + * @stream: Stream to be allocated + * @ch: Channel count + * @dir: Data direction + * @dai_id: DAI id + */ +struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_streams *stream, + u32 ch, u32 dir, int dai_id) +{ + struct sdw_cdns_pdi *pdi = NULL; + + if (dir == SDW_DATA_DIR_RX) + pdi = cdns_find_pdi(cdns, 0, stream->num_in, stream->in, + dai_id); + else + pdi = cdns_find_pdi(cdns, 0, stream->num_out, stream->out, + dai_id); + + /* check if we found a PDI, else find in bi-directional */ + if (!pdi) + pdi = cdns_find_pdi(cdns, 2, stream->num_bd, stream->bd, + dai_id); + + if (pdi) { + pdi->l_ch_num = 0; + pdi->h_ch_num = ch - 1; + pdi->dir = dir; + pdi->ch_count = ch; + } + + return pdi; +} +EXPORT_SYMBOL(sdw_cdns_alloc_pdi); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Cadence Soundwire Library"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h new file mode 100644 index 000000000..e7f0108d4 --- /dev/null +++ b/drivers/soundwire/cadence_master.h @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright(c) 2015-17 Intel Corporation. */ +#include <sound/soc.h> + +#ifndef __SDW_CADENCE_H +#define __SDW_CADENCE_H + +#define SDW_CADENCE_GSYNC_KHZ 4 /* 4 kHz */ +#define SDW_CADENCE_GSYNC_HZ (SDW_CADENCE_GSYNC_KHZ * 1000) + +/* + * The Cadence IP supports up to 32 entries in the FIFO, though implementations + * can configure the IP to have a smaller FIFO. + */ +#define CDNS_MCP_IP_MAX_CMD_LEN 32 + +/** + * struct sdw_cdns_pdi: PDI (Physical Data Interface) instance + * + * @num: pdi number + * @intel_alh_id: link identifier + * @l_ch_num: low channel for PDI + * @h_ch_num: high channel for PDI + * @ch_count: total channel count for PDI + * @dir: data direction + * @type: stream type, PDM or PCM + */ +struct sdw_cdns_pdi { + int num; + int intel_alh_id; + int l_ch_num; + int h_ch_num; + int ch_count; + enum sdw_data_direction dir; + enum sdw_stream_type type; +}; + +/** + * struct sdw_cdns_streams: Cadence stream data structure + * + * @num_bd: number of bidirectional streams + * @num_in: number of input streams + * @num_out: number of output streams + * @num_ch_bd: number of bidirectional stream channels + * @num_ch_bd: number of input stream channels + * @num_ch_bd: number of output stream channels + * @num_pdi: total number of PDIs + * @bd: bidirectional streams + * @in: input streams + * @out: output streams + */ +struct sdw_cdns_streams { + unsigned int num_bd; + unsigned int num_in; + unsigned int num_out; + unsigned int num_ch_bd; + unsigned int num_ch_in; + unsigned int num_ch_out; + unsigned int num_pdi; + struct sdw_cdns_pdi *bd; + struct sdw_cdns_pdi *in; + struct sdw_cdns_pdi *out; +}; + +/** + * struct sdw_cdns_stream_config: stream configuration + * + * @pcm_bd: number of bidirectional PCM streams supported + * @pcm_in: number of input PCM streams supported + * @pcm_out: number of output PCM streams supported + * @pdm_bd: number of bidirectional PDM streams supported + * @pdm_in: number of input PDM streams supported + * @pdm_out: number of output PDM streams supported + */ +struct sdw_cdns_stream_config { + unsigned int pcm_bd; + unsigned int pcm_in; + unsigned int pcm_out; + unsigned int pdm_bd; + unsigned int pdm_in; + unsigned int pdm_out; +}; + +/** + * struct sdw_cdns_dma_data: Cadence DMA data + * + * @name: SoundWire stream name + * @stream: stream runtime + * @pdi: PDI used for this dai + * @bus: Bus handle + * @stream_type: Stream type + * @link_id: Master link id + * @hw_params: hw_params to be applied in .prepare step + * @suspended: status set when suspended, to be used in .prepare + */ +struct sdw_cdns_dma_data { + char *name; + struct sdw_stream_runtime *stream; + struct sdw_cdns_pdi *pdi; + struct sdw_bus *bus; + enum sdw_stream_type stream_type; + int link_id; + struct snd_pcm_hw_params *hw_params; + bool suspended; +}; + +/** + * struct sdw_cdns - Cadence driver context + * @dev: Linux device + * @bus: Bus handle + * @instance: instance number + * @response_buf: SoundWire response buffer + * @tx_complete: Tx completion + * @defer: Defer pointer + * @ports: Data ports + * @num_ports: Total number of data ports + * @pcm: PCM streams + * @pdm: PDM streams + * @registers: Cadence registers + * @link_up: Link status + * @msg_count: Messages sent on bus + */ +struct sdw_cdns { + struct device *dev; + struct sdw_bus bus; + unsigned int instance; + + /* + * The datasheet says the RX FIFO AVAIL can be 2 entries more + * than the FIFO capacity, so allow for this. + */ + u32 response_buf[CDNS_MCP_IP_MAX_CMD_LEN + 2]; + + struct completion tx_complete; + struct sdw_defer *defer; + + struct sdw_cdns_port *ports; + int num_ports; + + struct sdw_cdns_streams pcm; + struct sdw_cdns_streams pdm; + + void __iomem *registers; + + bool link_up; + unsigned int msg_count; + bool interrupt_enabled; + + struct work_struct work; + + struct list_head list; +}; + +#define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus) + +/* Exported symbols */ + +int sdw_cdns_probe(struct sdw_cdns *cdns); +extern struct sdw_master_ops sdw_cdns_master_ops; + +irqreturn_t sdw_cdns_irq(int irq, void *dev_id); +irqreturn_t sdw_cdns_thread(int irq, void *dev_id); + +int sdw_cdns_init(struct sdw_cdns *cdns); +int sdw_cdns_pdi_init(struct sdw_cdns *cdns, + struct sdw_cdns_stream_config config); +int sdw_cdns_exit_reset(struct sdw_cdns *cdns); +int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state); + +bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns); +int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake); +int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset); + +#ifdef CONFIG_DEBUG_FS +void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root); +#endif + +struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_streams *stream, + u32 ch, u32 dir, int dai_id); +void sdw_cdns_config_stream(struct sdw_cdns *cdns, + u32 ch, u32 dir, struct sdw_cdns_pdi *pdi); + +enum sdw_command_response +cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num); + +enum sdw_command_response +cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg); + +enum sdw_command_response +cdns_xfer_msg_defer(struct sdw_bus *bus, + struct sdw_msg *msg, struct sdw_defer *defer); + +enum sdw_command_response +cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num); + +int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params); + +int cdns_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, bool pcm, int direction); +#endif /* __SDW_CADENCE_H */ diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c new file mode 100644 index 000000000..49900cd20 --- /dev/null +++ b/drivers/soundwire/debugfs.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2017-2019 Intel Corporation. + +#include <linux/device.h> +#include <linux/debugfs.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include "bus.h" + +static struct dentry *sdw_debugfs_root; + +void sdw_bus_debugfs_init(struct sdw_bus *bus) +{ + char name[16]; + + if (!sdw_debugfs_root) + return; + + /* create the debugfs master-N */ + snprintf(name, sizeof(name), "master-%d-%d", bus->id, bus->link_id); + bus->debugfs = debugfs_create_dir(name, sdw_debugfs_root); +} + +void sdw_bus_debugfs_exit(struct sdw_bus *bus) +{ + debugfs_remove_recursive(bus->debugfs); +} + +#define RD_BUF (3 * PAGE_SIZE) + +static ssize_t sdw_sprintf(struct sdw_slave *slave, + char *buf, size_t pos, unsigned int reg) +{ + int value; + + value = sdw_read(slave, reg); + + if (value < 0) + return scnprintf(buf + pos, RD_BUF - pos, "%3x\tXX\n", reg); + else + return scnprintf(buf + pos, RD_BUF - pos, + "%3x\t%2x\n", reg, value); +} + +static int sdw_slave_reg_show(struct seq_file *s_file, void *data) +{ + struct sdw_slave *slave = s_file->private; + char *buf; + ssize_t ret; + int i, j; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + + /* DP0 non-banked registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP0\n"); + for (i = SDW_DP0_INT; i <= SDW_DP0_PREPARECTRL; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* DP0 Bank 0 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n"); + ret += sdw_sprintf(slave, buf, ret, SDW_DP0_CHANNELEN); + for (i = SDW_DP0_SAMPLECTRL1; i <= SDW_DP0_LANECTRL; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* DP0 Bank 1 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n"); + ret += sdw_sprintf(slave, buf, ret, + SDW_DP0_CHANNELEN + SDW_BANK1_OFFSET); + for (i = SDW_DP0_SAMPLECTRL1 + SDW_BANK1_OFFSET; + i <= SDW_DP0_LANECTRL + SDW_BANK1_OFFSET; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* SCP registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nSCP\n"); + for (i = SDW_SCP_INT1; i <= SDW_SCP_BANKDELAY; i++) + ret += sdw_sprintf(slave, buf, ret, i); + for (i = SDW_SCP_DEVID_0; i <= SDW_SCP_DEVID_5; i++) + ret += sdw_sprintf(slave, buf, ret, i); + + /* + * SCP Bank 0/1 registers are read-only and cannot be + * retrieved from the Slave. The Master typically keeps track + * of the current frame size so the information can be found + * in other places + */ + + /* DP1..14 registers */ + for (i = 1; SDW_VALID_PORT_RANGE(i); i++) { + + /* DPi registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP%d\n", i); + for (j = SDW_DPN_INT(i); j <= SDW_DPN_PREPARECTRL(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + + /* DPi Bank0 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n"); + for (j = SDW_DPN_CHANNELEN_B0(i); + j <= SDW_DPN_LANECTRL_B0(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + + /* DPi Bank1 registers */ + ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n"); + for (j = SDW_DPN_CHANNELEN_B1(i); + j <= SDW_DPN_LANECTRL_B1(i); j++) + ret += sdw_sprintf(slave, buf, ret, j); + } + + seq_printf(s_file, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg); + +void sdw_slave_debugfs_init(struct sdw_slave *slave) +{ + struct dentry *master; + struct dentry *d; + char name[32]; + + master = slave->bus->debugfs; + + /* create the debugfs slave-name */ + snprintf(name, sizeof(name), "%s", dev_name(&slave->dev)); + d = debugfs_create_dir(name, master); + + debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops); + + slave->debugfs = d; +} + +void sdw_slave_debugfs_exit(struct sdw_slave *slave) +{ + debugfs_remove_recursive(slave->debugfs); +} + +void sdw_debugfs_init(void) +{ + sdw_debugfs_root = debugfs_create_dir("soundwire", NULL); +} + +void sdw_debugfs_exit(void) +{ + debugfs_remove_recursive(sdw_debugfs_root); +} diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c new file mode 100644 index 000000000..0bdef38c9 --- /dev/null +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) +// Copyright(c) 2015-2020 Intel Corporation. + +/* + * Bandwidth management algorithm based on 2^n gears + * + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" + +#define SDW_STRM_RATE_GROUPING 1 + +struct sdw_group_params { + unsigned int rate; + int full_bw; + int payload_bw; + int hwidth; +}; + +struct sdw_group { + unsigned int count; + unsigned int max_size; + unsigned int *rates; +}; + +struct sdw_transport_data { + int hstart; + int hstop; + int block_offset; + int sub_block_offset; +}; + +static void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, + struct sdw_transport_data *t_data) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_port_runtime *p_rt; + int port_bo, sample_int; + unsigned int rate, bps, ch = 0; + unsigned int slave_total_ch; + struct sdw_bus_params *b_params = &m_rt->bus->params; + + port_bo = t_data->block_offset; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (m_rt->bus->params.curr_dr_freq / rate); + slave_total_ch = 0; + + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ch = sdw_ch_mask_to_ch(p_rt->ch_mask); + + sdw_fill_xport_params(&p_rt->transport_params, + p_rt->num, false, + SDW_BLK_GRP_CNT_1, + sample_int, port_bo, port_bo >> 8, + t_data->hstart, + t_data->hstop, + (SDW_BLK_GRP_CNT_1 * ch), 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->s_data_mode); + + port_bo += bps * ch; + slave_total_ch += ch; + } + + if (m_rt->direction == SDW_DATA_DIR_TX && + m_rt->ch_count == slave_total_ch) { + /* + * Slave devices were configured to access all channels + * of the stream, which indicates that they operate in + * 'mirror mode'. Make sure we reset the port offset for + * the next device in the list + */ + port_bo = t_data->block_offset; + } + } +} + +static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_group_params *params, + int port_bo, int hstop) +{ + struct sdw_transport_data t_data = {0}; + struct sdw_port_runtime *p_rt; + struct sdw_bus *bus = m_rt->bus; + struct sdw_bus_params *b_params = &bus->params; + int sample_int, hstart = 0; + unsigned int rate, bps, ch, no_ch; + + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + ch = m_rt->ch_count; + sample_int = (bus->params.curr_dr_freq / rate); + + if (rate != params->rate) + return; + + t_data.hstop = hstop; + hstart = hstop - params->hwidth + 1; + t_data.hstart = hstart; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + no_ch = sdw_ch_mask_to_ch(p_rt->ch_mask); + + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, + false, SDW_BLK_GRP_CNT_1, sample_int, + port_bo, port_bo >> 8, hstart, hstop, + (SDW_BLK_GRP_CNT_1 * no_ch), 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->m_data_mode); + + /* Check for first entry */ + if (!(p_rt == list_first_entry(&m_rt->port_list, + struct sdw_port_runtime, + port_node))) { + port_bo += bps * ch; + continue; + } + + t_data.hstart = hstart; + t_data.hstop = hstop; + t_data.block_offset = port_bo; + t_data.sub_block_offset = 0; + port_bo += bps * ch; + } + + sdw_compute_slave_ports(m_rt, &t_data); +} + +static void _sdw_compute_port_params(struct sdw_bus *bus, + struct sdw_group_params *params, int count) +{ + struct sdw_master_runtime *m_rt = NULL; + int hstop = bus->params.col - 1; + int block_offset, port_bo, i; + + /* Run loop for all groups to compute transport parameters */ + for (i = 0; i < count; i++) { + port_bo = 1; + block_offset = 1; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + sdw_compute_master_ports(m_rt, ¶ms[i], + port_bo, hstop); + + block_offset += m_rt->ch_count * + m_rt->stream->params.bps; + port_bo = block_offset; + } + + hstop = hstop - params[i].hwidth; + } +} + +static int sdw_compute_group_params(struct sdw_bus *bus, + struct sdw_group_params *params, + int *rates, int count) +{ + struct sdw_master_runtime *m_rt = NULL; + int sel_col = bus->params.col; + unsigned int rate, bps, ch; + int i, column_needed = 0; + + /* Calculate bandwidth per group */ + for (i = 0; i < count; i++) { + params[i].rate = rates[i]; + params[i].full_bw = bus->params.curr_dr_freq / params[i].rate; + } + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + ch = m_rt->ch_count; + + for (i = 0; i < count; i++) { + if (rate == params[i].rate) + params[i].payload_bw += bps * ch; + } + } + + for (i = 0; i < count; i++) { + params[i].hwidth = (sel_col * + params[i].payload_bw + params[i].full_bw - 1) / + params[i].full_bw; + + column_needed += params[i].hwidth; + } + + if (column_needed > sel_col - 1) + return -EINVAL; + + return 0; +} + +static int sdw_add_element_group_count(struct sdw_group *group, + unsigned int rate) +{ + int num = group->count; + int i; + + for (i = 0; i <= num; i++) { + if (rate == group->rates[i]) + break; + + if (i != num) + continue; + + if (group->count >= group->max_size) { + unsigned int *rates; + + group->max_size += 1; + rates = krealloc(group->rates, + (sizeof(int) * group->max_size), + GFP_KERNEL); + if (!rates) + return -ENOMEM; + group->rates = rates; + } + + group->rates[group->count++] = rate; + } + + return 0; +} + +static int sdw_get_group_count(struct sdw_bus *bus, + struct sdw_group *group) +{ + struct sdw_master_runtime *m_rt; + unsigned int rate; + int ret = 0; + + group->count = 0; + group->max_size = SDW_STRM_RATE_GROUPING; + group->rates = kcalloc(group->max_size, sizeof(int), GFP_KERNEL); + if (!group->rates) + return -ENOMEM; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + if (m_rt == list_first_entry(&bus->m_rt_list, + struct sdw_master_runtime, + bus_node)) { + group->rates[group->count++] = rate; + + } else { + ret = sdw_add_element_group_count(group, rate); + if (ret < 0) { + kfree(group->rates); + return ret; + } + } + } + + return ret; +} + +/** + * sdw_compute_port_params: Compute transport and port parameters + * + * @bus: SDW Bus instance + */ +static int sdw_compute_port_params(struct sdw_bus *bus) +{ + struct sdw_group_params *params = NULL; + struct sdw_group group; + int ret; + + ret = sdw_get_group_count(bus, &group); + if (ret < 0) + return ret; + + if (group.count == 0) + goto out; + + params = kcalloc(group.count, sizeof(*params), GFP_KERNEL); + if (!params) { + ret = -ENOMEM; + goto out; + } + + /* Compute transport parameters for grouped streams */ + ret = sdw_compute_group_params(bus, params, + &group.rates[0], group.count); + if (ret < 0) + goto free_params; + + _sdw_compute_port_params(bus, params, group.count); + +free_params: + kfree(params); +out: + kfree(group.rates); + + return ret; +} + +static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) +{ + struct sdw_master_prop *prop = &bus->prop; + int frame_int, frame_freq; + int r, c; + + for (c = 0; c < SDW_FRAME_COLS; c++) { + for (r = 0; r < SDW_FRAME_ROWS; r++) { + if (sdw_rows[r] != prop->default_row || + sdw_cols[c] != prop->default_col) + continue; + + frame_int = sdw_rows[r] * sdw_cols[c]; + frame_freq = clk_freq / frame_int; + + if ((clk_freq - (frame_freq * SDW_FRAME_CTRL_BITS)) < + bus->params.bandwidth) + continue; + + bus->params.row = sdw_rows[r]; + bus->params.col = sdw_cols[c]; + return 0; + } + } + + return -EINVAL; +} + +/** + * sdw_compute_bus_params: Compute bus parameters + * + * @bus: SDW Bus instance + */ +static int sdw_compute_bus_params(struct sdw_bus *bus) +{ + unsigned int max_dr_freq, curr_dr_freq = 0; + struct sdw_master_prop *mstr_prop = &bus->prop; + int i, clk_values, ret; + bool is_gear = false; + u32 *clk_buf; + + if (mstr_prop->num_clk_gears) { + clk_values = mstr_prop->num_clk_gears; + clk_buf = mstr_prop->clk_gears; + is_gear = true; + } else if (mstr_prop->num_clk_freq) { + clk_values = mstr_prop->num_clk_freq; + clk_buf = mstr_prop->clk_freq; + } else { + clk_values = 1; + clk_buf = NULL; + } + + max_dr_freq = mstr_prop->max_clk_freq * SDW_DOUBLE_RATE_FACTOR; + + for (i = 0; i < clk_values; i++) { + if (!clk_buf) + curr_dr_freq = max_dr_freq; + else + curr_dr_freq = (is_gear) ? + (max_dr_freq >> clk_buf[i]) : + clk_buf[i] * SDW_DOUBLE_RATE_FACTOR; + + if (curr_dr_freq <= bus->params.bandwidth) + continue; + + break; + + /* + * TODO: Check all the Slave(s) port(s) audio modes and find + * whether given clock rate is supported with glitchless + * transition. + */ + } + + if (i == clk_values) + return -EINVAL; + + ret = sdw_select_row_col(bus, curr_dr_freq); + if (ret < 0) + return -EINVAL; + + bus->params.curr_dr_freq = curr_dr_freq; + return 0; +} + +/** + * sdw_compute_params: Compute bus, transport and port parameters + * + * @bus: SDW Bus instance + */ +int sdw_compute_params(struct sdw_bus *bus) +{ + int ret; + + /* Computes clock frequency, frame shape and frame frequency */ + ret = sdw_compute_bus_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute bus params failed: %d", ret); + return ret; + } + + /* Compute transport and port params */ + ret = sdw_compute_port_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute transport params failed: %d", ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(sdw_compute_params); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("SoundWire Generic Bandwidth Allocation"); diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c new file mode 100644 index 000000000..4487fbb8f --- /dev/null +++ b/drivers/soundwire/intel.c @@ -0,0 +1,1994 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * Soundwire Intel Master Driver + */ + +#include <linux/acpi.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <linux/pm_runtime.h> +#include <sound/soc.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_intel.h> +#include "cadence_master.h" +#include "bus.h" +#include "intel.h" + +#define INTEL_MASTER_SUSPEND_DELAY_MS 3000 + +/* + * debug/config flags for the Intel SoundWire Master. + * + * Since we may have multiple masters active, we can have up to 8 + * flags reused in each byte, with master0 using the ls-byte, etc. + */ + +#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0) +#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1) +#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2) +#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3) + +static int md_flags; +module_param_named(sdw_md_flags, md_flags, int, 0444); +MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)"); + +/* Intel SHIM Registers Definition */ +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_LCTL 0x4 +#define SDW_SHIM_IPPTR 0x8 +#define SDW_SHIM_SYNC 0xC + +#define SDW_SHIM_CTLSCAP(x) (0x010 + 0x60 * (x)) +#define SDW_SHIM_CTLS0CM(x) (0x012 + 0x60 * (x)) +#define SDW_SHIM_CTLS1CM(x) (0x014 + 0x60 * (x)) +#define SDW_SHIM_CTLS2CM(x) (0x016 + 0x60 * (x)) +#define SDW_SHIM_CTLS3CM(x) (0x018 + 0x60 * (x)) +#define SDW_SHIM_PCMSCAP(x) (0x020 + 0x60 * (x)) + +#define SDW_SHIM_PCMSYCHM(x, y) (0x022 + (0x60 * (x)) + (0x2 * (y))) +#define SDW_SHIM_PCMSYCHC(x, y) (0x042 + (0x60 * (x)) + (0x2 * (y))) +#define SDW_SHIM_PDMSCAP(x) (0x062 + 0x60 * (x)) +#define SDW_SHIM_IOCTL(x) (0x06C + 0x60 * (x)) +#define SDW_SHIM_CTMCTL(x) (0x06E + 0x60 * (x)) + +#define SDW_SHIM_WAKEEN 0x190 +#define SDW_SHIM_WAKESTS 0x192 + +#define SDW_SHIM_LCTL_SPA BIT(0) +#define SDW_SHIM_LCTL_SPA_MASK GENMASK(3, 0) +#define SDW_SHIM_LCTL_CPA BIT(8) +#define SDW_SHIM_LCTL_CPA_MASK GENMASK(11, 8) + +#define SDW_SHIM_SYNC_SYNCPRD_VAL_24 (24000 / SDW_CADENCE_GSYNC_KHZ - 1) +#define SDW_SHIM_SYNC_SYNCPRD_VAL_38_4 (38400 / SDW_CADENCE_GSYNC_KHZ - 1) +#define SDW_SHIM_SYNC_SYNCPRD GENMASK(14, 0) +#define SDW_SHIM_SYNC_SYNCCPU BIT(15) +#define SDW_SHIM_SYNC_CMDSYNC_MASK GENMASK(19, 16) +#define SDW_SHIM_SYNC_CMDSYNC BIT(16) +#define SDW_SHIM_SYNC_SYNCGO BIT(24) + +#define SDW_SHIM_PCMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PCMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PCMSCAP_BSS GENMASK(12, 8) + +#define SDW_SHIM_PCMSYCM_LCHN GENMASK(3, 0) +#define SDW_SHIM_PCMSYCM_HCHN GENMASK(7, 4) +#define SDW_SHIM_PCMSYCM_STREAM GENMASK(13, 8) +#define SDW_SHIM_PCMSYCM_DIR BIT(15) + +#define SDW_SHIM_PDMSCAP_ISS GENMASK(3, 0) +#define SDW_SHIM_PDMSCAP_OSS GENMASK(7, 4) +#define SDW_SHIM_PDMSCAP_BSS GENMASK(12, 8) +#define SDW_SHIM_PDMSCAP_CPSS GENMASK(15, 13) + +#define SDW_SHIM_IOCTL_MIF BIT(0) +#define SDW_SHIM_IOCTL_CO BIT(1) +#define SDW_SHIM_IOCTL_COE BIT(2) +#define SDW_SHIM_IOCTL_DO BIT(3) +#define SDW_SHIM_IOCTL_DOE BIT(4) +#define SDW_SHIM_IOCTL_BKE BIT(5) +#define SDW_SHIM_IOCTL_WPDD BIT(6) +#define SDW_SHIM_IOCTL_CIBD BIT(8) +#define SDW_SHIM_IOCTL_DIBD BIT(9) + +#define SDW_SHIM_CTMCTL_DACTQE BIT(0) +#define SDW_SHIM_CTMCTL_DODS BIT(1) +#define SDW_SHIM_CTMCTL_DOAIS GENMASK(4, 3) + +#define SDW_SHIM_WAKEEN_ENABLE BIT(0) +#define SDW_SHIM_WAKESTS_STATUS BIT(0) + +/* Intel ALH Register definitions */ +#define SDW_ALH_STRMZCFG(x) (0x000 + (0x4 * (x))) +#define SDW_ALH_NUM_STREAMS 64 + +#define SDW_ALH_STRMZCFG_DMAT_VAL 0x3 +#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0) +#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16) + +enum intel_pdi_type { + INTEL_PDI_IN = 0, + INTEL_PDI_OUT = 1, + INTEL_PDI_BD = 2, +}; + +#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns) + +/* + * Read, write helpers for HW registers + */ +static inline int intel_readl(void __iomem *base, int offset) +{ + return readl(base + offset); +} + +static inline void intel_writel(void __iomem *base, int offset, int value) +{ + writel(value, base + offset); +} + +static inline u16 intel_readw(void __iomem *base, int offset) +{ + return readw(base + offset); +} + +static inline void intel_writew(void __iomem *base, int offset, u16 value) +{ + writew(value, base + offset); +} + +static int intel_wait_bit(void __iomem *base, int offset, u32 mask, u32 target) +{ + int timeout = 10; + u32 reg_read; + + do { + reg_read = readl(base + offset); + if ((reg_read & mask) == target) + return 0; + + timeout--; + usleep_range(50, 100); + } while (timeout != 0); + + return -EAGAIN; +} + +static int intel_clear_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + writel(value, base + offset); + return intel_wait_bit(base, offset, mask, 0); +} + +static int intel_set_bit(void __iomem *base, int offset, u32 value, u32 mask) +{ + writel(value, base + offset); + return intel_wait_bit(base, offset, mask, mask); +} + +/* + * debugfs + */ +#ifdef CONFIG_DEBUG_FS + +#define RD_BUF (2 * PAGE_SIZE) + +static ssize_t intel_sprintf(void __iomem *mem, bool l, + char *buf, size_t pos, unsigned int reg) +{ + int value; + + if (l) + value = intel_readl(mem, reg); + else + value = intel_readw(mem, reg); + + return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value); +} + +static int intel_reg_show(struct seq_file *s_file, void *data) +{ + struct sdw_intel *sdw = s_file->private; + void __iomem *s = sdw->link_res->shim; + void __iomem *a = sdw->link_res->alh; + char *buf; + ssize_t ret; + int i, j; + unsigned int links, reg; + + buf = kzalloc(RD_BUF, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + links = intel_readl(s, SDW_SHIM_LCAP) & GENMASK(2, 0); + + ret = scnprintf(buf, RD_BUF, "Register Value\n"); + ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n"); + + for (i = 0; i < links; i++) { + reg = SDW_SHIM_LCAP + i * 4; + ret += intel_sprintf(s, true, buf, ret, reg); + } + + for (i = 0; i < links; i++) { + ret += scnprintf(buf + ret, RD_BUF - ret, "\nLink%d\n", i); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLSCAP(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS0CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS1CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS2CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTLS3CM(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PCMSCAP(i)); + + ret += scnprintf(buf + ret, RD_BUF - ret, "\n PCMSyCH registers\n"); + + /* + * the value 10 is the number of PDIs. We will need a + * cleanup to remove hard-coded Intel configurations + * from cadence_master.c + */ + for (j = 0; j < 10; j++) { + ret += intel_sprintf(s, false, buf, ret, + SDW_SHIM_PCMSYCHM(i, j)); + ret += intel_sprintf(s, false, buf, ret, + SDW_SHIM_PCMSYCHC(i, j)); + } + ret += scnprintf(buf + ret, RD_BUF - ret, "\n PDMSCAP, IOCTL, CTMCTL\n"); + + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_PDMSCAP(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_IOCTL(i)); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_CTMCTL(i)); + } + + ret += scnprintf(buf + ret, RD_BUF - ret, "\nWake registers\n"); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKEEN); + ret += intel_sprintf(s, false, buf, ret, SDW_SHIM_WAKESTS); + + ret += scnprintf(buf + ret, RD_BUF - ret, "\nALH STRMzCFG\n"); + for (i = 0; i < SDW_ALH_NUM_STREAMS; i++) + ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i)); + + seq_printf(s_file, "%s", buf); + kfree(buf); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(intel_reg); + +static int intel_set_m_datamode(void *data, u64 value) +{ + struct sdw_intel *sdw = data; + struct sdw_bus *bus = &sdw->cdns.bus; + + if (value > SDW_PORT_DATA_MODE_STATIC_1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + bus->params.m_data_mode = value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(intel_set_m_datamode_fops, NULL, + intel_set_m_datamode, "%llu\n"); + +static int intel_set_s_datamode(void *data, u64 value) +{ + struct sdw_intel *sdw = data; + struct sdw_bus *bus = &sdw->cdns.bus; + + if (value > SDW_PORT_DATA_MODE_STATIC_1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + bus->params.s_data_mode = value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(intel_set_s_datamode_fops, NULL, + intel_set_s_datamode, "%llu\n"); + +static void intel_debugfs_init(struct sdw_intel *sdw) +{ + struct dentry *root = sdw->cdns.bus.debugfs; + + if (!root) + return; + + sdw->debugfs = debugfs_create_dir("intel-sdw", root); + + debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw, + &intel_reg_fops); + + debugfs_create_file("intel-m-datamode", 0200, sdw->debugfs, sdw, + &intel_set_m_datamode_fops); + + debugfs_create_file("intel-s-datamode", 0200, sdw->debugfs, sdw, + &intel_set_s_datamode_fops); + + sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs); +} + +static void intel_debugfs_exit(struct sdw_intel *sdw) +{ + debugfs_remove_recursive(sdw->debugfs); +} +#else +static void intel_debugfs_init(struct sdw_intel *sdw) {} +static void intel_debugfs_exit(struct sdw_intel *sdw) {} +#endif /* CONFIG_DEBUG_FS */ + +/* + * shim ops + */ + +static int intel_link_power_up(struct sdw_intel *sdw) +{ + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->link_res->shim; + u32 *shim_mask = sdw->link_res->shim_mask; + struct sdw_bus *bus = &sdw->cdns.bus; + struct sdw_master_prop *prop = &bus->prop; + u32 spa_mask, cpa_mask; + u32 link_control; + int ret = 0; + u32 syncprd; + u32 sync_reg; + + mutex_lock(sdw->link_res->shim_lock); + + /* + * The hardware relies on an internal counter, typically 4kHz, + * to generate the SoundWire SSP - which defines a 'safe' + * synchronization point between commands and audio transport + * and allows for multi link synchronization. The SYNCPRD value + * is only dependent on the oscillator clock provided to + * the IP, so adjust based on _DSD properties reported in DSDT + * tables. The values reported are based on either 24MHz + * (CNL/CML) or 38.4 MHz (ICL/TGL+). + */ + if (prop->mclk_freq % 6000000) + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; + else + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; + + if (!*shim_mask) { + dev_dbg(sdw->cdns.dev, "%s: powering up all links\n", __func__); + + /* we first need to program the SyncPRD/CPU registers */ + dev_dbg(sdw->cdns.dev, + "%s: first link up, programming SYNCPRD\n", __func__); + + /* set SyncPRD period */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + u32p_replace_bits(&sync_reg, syncprd, SDW_SHIM_SYNC_SYNCPRD); + + /* Set SyncCPU bit */ + sync_reg |= SDW_SHIM_SYNC_SYNCCPU; + intel_writel(shim, SDW_SHIM_SYNC, sync_reg); + + /* Link power up sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + + /* only power-up enabled links */ + spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, sdw->link_res->link_mask); + cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask); + + link_control |= spa_mask; + + ret = intel_set_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) { + dev_err(sdw->cdns.dev, "Failed to power up link: %d\n", ret); + goto out; + } + + /* SyncCPU will change once link is active */ + ret = intel_wait_bit(shim, SDW_SHIM_SYNC, + SDW_SHIM_SYNC_SYNCCPU, 0); + if (ret < 0) { + dev_err(sdw->cdns.dev, + "Failed to set SHIM_SYNC: %d\n", ret); + goto out; + } + } + + *shim_mask |= BIT(link_id); + + sdw->cdns.link_up = true; +out: + mutex_unlock(sdw->link_res->shim_lock); + + return ret; +} + +/* this needs to be called with shim_lock */ +static void intel_shim_glue_to_master_ip(struct sdw_intel *sdw) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + u16 ioctl; + + /* Switch to MIP from Glue logic */ + ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); + + ioctl &= ~(SDW_SHIM_IOCTL_DOE); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl &= ~(SDW_SHIM_IOCTL_DO); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl |= (SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl &= ~(SDW_SHIM_IOCTL_BKE); + ioctl &= ~(SDW_SHIM_IOCTL_COE); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + /* at this point Master IP has full control of the I/Os */ +} + +/* this needs to be called with shim_lock */ +static void intel_shim_master_ip_to_glue(struct sdw_intel *sdw) +{ + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->link_res->shim; + u16 ioctl; + + /* Glue logic */ + ioctl = intel_readw(shim, SDW_SHIM_IOCTL(link_id)); + ioctl |= SDW_SHIM_IOCTL_BKE; + ioctl |= SDW_SHIM_IOCTL_COE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl &= ~(SDW_SHIM_IOCTL_MIF); + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + /* at this point Integration Glue has full control of the I/Os */ +} + +static int intel_shim_init(struct sdw_intel *sdw, bool clock_stop) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + int ret = 0; + u16 ioctl = 0, act = 0; + + mutex_lock(sdw->link_res->shim_lock); + + /* Initialize Shim */ + ioctl |= SDW_SHIM_IOCTL_BKE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl |= SDW_SHIM_IOCTL_WPDD; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl |= SDW_SHIM_IOCTL_DO; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + ioctl |= SDW_SHIM_IOCTL_DOE; + intel_writew(shim, SDW_SHIM_IOCTL(link_id), ioctl); + usleep_range(10, 15); + + intel_shim_glue_to_master_ip(sdw); + + u16p_replace_bits(&act, 0x1, SDW_SHIM_CTMCTL_DOAIS); + act |= SDW_SHIM_CTMCTL_DACTQE; + act |= SDW_SHIM_CTMCTL_DODS; + intel_writew(shim, SDW_SHIM_CTMCTL(link_id), act); + usleep_range(10, 15); + + mutex_unlock(sdw->link_res->shim_lock); + + return ret; +} + +static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + u16 wake_en, wake_sts; + + mutex_lock(sdw->link_res->shim_lock); + wake_en = intel_readw(shim, SDW_SHIM_WAKEEN); + + if (wake_enable) { + /* Enable the wakeup */ + wake_en |= (SDW_SHIM_WAKEEN_ENABLE << link_id); + intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); + } else { + /* Disable the wake up interrupt */ + wake_en &= ~(SDW_SHIM_WAKEEN_ENABLE << link_id); + intel_writew(shim, SDW_SHIM_WAKEEN, wake_en); + + /* Clear wake status */ + wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); + wake_sts |= (SDW_SHIM_WAKESTS_STATUS << link_id); + intel_writew(shim, SDW_SHIM_WAKESTS, wake_sts); + } + mutex_unlock(sdw->link_res->shim_lock); +} + +static int intel_link_power_down(struct sdw_intel *sdw) +{ + u32 link_control, spa_mask, cpa_mask; + unsigned int link_id = sdw->instance; + void __iomem *shim = sdw->link_res->shim; + u32 *shim_mask = sdw->link_res->shim_mask; + int ret = 0; + + mutex_lock(sdw->link_res->shim_lock); + + if (!(*shim_mask & BIT(link_id))) + dev_err(sdw->cdns.dev, + "%s: Unbalanced power-up/down calls\n", __func__); + + sdw->cdns.link_up = false; + + intel_shim_master_ip_to_glue(sdw); + + *shim_mask &= ~BIT(link_id); + + if (!*shim_mask) { + + dev_dbg(sdw->cdns.dev, "%s: powering down all links\n", __func__); + + /* Link power down sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); + + /* only power-down enabled links */ + spa_mask = FIELD_PREP(SDW_SHIM_LCTL_SPA_MASK, ~sdw->link_res->link_mask); + cpa_mask = FIELD_PREP(SDW_SHIM_LCTL_CPA_MASK, sdw->link_res->link_mask); + + link_control &= spa_mask; + + ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); + if (ret < 0) { + dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__); + + /* + * we leave the sdw->cdns.link_up flag as false since we've disabled + * the link at this point and cannot handle interrupts any longer. + */ + } + } + + link_control = intel_readl(shim, SDW_SHIM_LCTL); + + mutex_unlock(sdw->link_res->shim_lock); + + return ret; +} + +static void intel_shim_sync_arm(struct sdw_intel *sdw) +{ + void __iomem *shim = sdw->link_res->shim; + u32 sync_reg; + + mutex_lock(sdw->link_res->shim_lock); + + /* update SYNC register */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + sync_reg |= (SDW_SHIM_SYNC_CMDSYNC << sdw->instance); + intel_writel(shim, SDW_SHIM_SYNC, sync_reg); + + mutex_unlock(sdw->link_res->shim_lock); +} + +static int intel_shim_sync_go_unlocked(struct sdw_intel *sdw) +{ + void __iomem *shim = sdw->link_res->shim; + u32 sync_reg; + int ret; + + /* Read SYNC register */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + + /* + * Set SyncGO bit to synchronously trigger a bank switch for + * all the masters. A write to SYNCGO bit clears CMDSYNC bit for all + * the Masters. + */ + sync_reg |= SDW_SHIM_SYNC_SYNCGO; + + ret = intel_clear_bit(shim, SDW_SHIM_SYNC, sync_reg, + SDW_SHIM_SYNC_SYNCGO); + + if (ret < 0) + dev_err(sdw->cdns.dev, "SyncGO clear failed: %d\n", ret); + + return ret; +} + +static int intel_shim_sync_go(struct sdw_intel *sdw) +{ + int ret; + + mutex_lock(sdw->link_res->shim_lock); + + ret = intel_shim_sync_go_unlocked(sdw); + + mutex_unlock(sdw->link_res->shim_lock); + + return ret; +} + +/* + * PDI routines + */ +static void intel_pdi_init(struct sdw_intel *sdw, + struct sdw_cdns_stream_config *config) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + int pcm_cap, pdm_cap; + + /* PCM Stream Capability */ + pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id)); + + config->pcm_bd = FIELD_GET(SDW_SHIM_PCMSCAP_BSS, pcm_cap); + config->pcm_in = FIELD_GET(SDW_SHIM_PCMSCAP_ISS, pcm_cap); + config->pcm_out = FIELD_GET(SDW_SHIM_PCMSCAP_OSS, pcm_cap); + + dev_dbg(sdw->cdns.dev, "PCM cap bd:%d in:%d out:%d\n", + config->pcm_bd, config->pcm_in, config->pcm_out); + + /* PDM Stream Capability */ + pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id)); + + config->pdm_bd = FIELD_GET(SDW_SHIM_PDMSCAP_BSS, pdm_cap); + config->pdm_in = FIELD_GET(SDW_SHIM_PDMSCAP_ISS, pdm_cap); + config->pdm_out = FIELD_GET(SDW_SHIM_PDMSCAP_OSS, pdm_cap); + + dev_dbg(sdw->cdns.dev, "PDM cap bd:%d in:%d out:%d\n", + config->pdm_bd, config->pdm_in, config->pdm_out); +} + +static int +intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + int count; + + if (pcm) { + count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num)); + + /* + * WORKAROUND: on all existing Intel controllers, pdi + * number 2 reports channel count as 1 even though it + * supports 8 channels. Performing hardcoding for pdi + * number 2. + */ + if (pdi_num == 2) + count = 7; + + } else { + count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id)); + count = FIELD_GET(SDW_SHIM_PDMSCAP_CPSS, count); + } + + /* zero based values for channel count in register */ + count++; + + return count; +} + +static int intel_pdi_get_ch_update(struct sdw_intel *sdw, + struct sdw_cdns_pdi *pdi, + unsigned int num_pdi, + unsigned int *num_ch, bool pcm) +{ + int i, ch_count = 0; + + for (i = 0; i < num_pdi; i++) { + pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num, pcm); + ch_count += pdi->ch_count; + pdi++; + } + + *num_ch = ch_count; + return 0; +} + +static int intel_pdi_stream_ch_update(struct sdw_intel *sdw, + struct sdw_cdns_streams *stream, bool pcm) +{ + intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd, + &stream->num_ch_bd, pcm); + + intel_pdi_get_ch_update(sdw, stream->in, stream->num_in, + &stream->num_ch_in, pcm); + + intel_pdi_get_ch_update(sdw, stream->out, stream->num_out, + &stream->num_ch_out, pcm); + + return 0; +} + +static int intel_pdi_ch_update(struct sdw_intel *sdw) +{ + /* First update PCM streams followed by PDM streams */ + intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm, true); + intel_pdi_stream_ch_update(sdw, &sdw->cdns.pdm, false); + + return 0; +} + +static void +intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) +{ + void __iomem *shim = sdw->link_res->shim; + unsigned int link_id = sdw->instance; + int pdi_conf = 0; + + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; + + /* + * Program stream parameters to stream SHIM register + * This is applicable for PCM stream only. + */ + if (pdi->type != SDW_STREAM_PCM) + return; + + if (pdi->dir == SDW_DATA_DIR_RX) + pdi_conf |= SDW_SHIM_PCMSYCM_DIR; + else + pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR); + + u32p_replace_bits(&pdi_conf, pdi->intel_alh_id, SDW_SHIM_PCMSYCM_STREAM); + u32p_replace_bits(&pdi_conf, pdi->l_ch_num, SDW_SHIM_PCMSYCM_LCHN); + u32p_replace_bits(&pdi_conf, pdi->h_ch_num, SDW_SHIM_PCMSYCM_HCHN); + + intel_writew(shim, SDW_SHIM_PCMSYCHM(link_id, pdi->num), pdi_conf); +} + +static void +intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) +{ + void __iomem *alh = sdw->link_res->alh; + unsigned int link_id = sdw->instance; + unsigned int conf; + + /* the Bulk and PCM streams are not contiguous */ + pdi->intel_alh_id = (link_id * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; + + /* Program Stream config ALH register */ + conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id)); + + u32p_replace_bits(&conf, SDW_ALH_STRMZCFG_DMAT_VAL, SDW_ALH_STRMZCFG_DMAT); + u32p_replace_bits(&conf, pdi->ch_count - 1, SDW_ALH_STRMZCFG_CHN); + + intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf); +} + +static int intel_params_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + struct snd_pcm_hw_params *hw_params, + int link_id, int alh_stream_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_params_data params_data; + + params_data.substream = substream; + params_data.dai = dai; + params_data.hw_params = hw_params; + params_data.link_id = link_id; + params_data.alh_stream_id = alh_stream_id; + + if (res->ops && res->ops->params_stream && res->dev) + return res->ops->params_stream(res->dev, + ¶ms_data); + return -EIO; +} + +static int intel_free_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int link_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_free_data free_data; + + free_data.substream = substream; + free_data.dai = dai; + free_data.link_id = link_id; + + if (res->ops && res->ops->free_stream && res->dev) + return res->ops->free_stream(res->dev, + &free_data); + + return 0; +} + +/* + * bank switch routines + */ + +static int intel_pre_bank_switch(struct sdw_bus *bus) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_intel *sdw = cdns_to_intel(cdns); + + /* Write to register only for multi-link */ + if (!bus->multi_link) + return 0; + + intel_shim_sync_arm(sdw); + + return 0; +} + +static int intel_post_bank_switch(struct sdw_bus *bus) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_intel *sdw = cdns_to_intel(cdns); + void __iomem *shim = sdw->link_res->shim; + int sync_reg, ret; + + /* Write to register only for multi-link */ + if (!bus->multi_link) + return 0; + + mutex_lock(sdw->link_res->shim_lock); + + /* Read SYNC register */ + sync_reg = intel_readl(shim, SDW_SHIM_SYNC); + + /* + * post_bank_switch() ops is called from the bus in loop for + * all the Masters in the steam with the expectation that + * we trigger the bankswitch for the only first Master in the list + * and do nothing for the other Masters + * + * So, set the SYNCGO bit only if CMDSYNC bit is set for any Master. + */ + if (!(sync_reg & SDW_SHIM_SYNC_CMDSYNC_MASK)) { + ret = 0; + goto unlock; + } + + ret = intel_shim_sync_go_unlocked(sdw); +unlock: + mutex_unlock(sdw->link_res->shim_lock); + + if (ret < 0) + dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret); + + return ret; +} + +/* + * DAI routines + */ + +static int intel_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = pm_runtime_get_sync(cdns->dev); + if (ret < 0 && ret != -EACCES) { + dev_err_ratelimited(cdns->dev, + "pm_runtime_get_sync failed in %s, ret %d\n", + __func__, ret); + pm_runtime_put_noidle(cdns->dev); + return ret; + } + return 0; +} + +static int intel_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_cdns_dma_data *dma; + struct sdw_cdns_pdi *pdi; + struct sdw_stream_config sconfig; + struct sdw_port_config *pconfig; + int ch, dir; + int ret; + bool pcm = true; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) + return -EIO; + + ch = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + + if (dma->stream_type == SDW_STREAM_PDM) + pcm = false; + + if (pcm) + pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); + else + pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pdm, ch, dir, dai->id); + + if (!pdi) { + ret = -EINVAL; + goto error; + } + + /* do run-time configurations for SHIM, ALH and PDI/PORT */ + intel_pdi_shim_configure(sdw, pdi); + intel_pdi_alh_configure(sdw, pdi); + sdw_cdns_config_stream(cdns, ch, dir, pdi); + + /* store pdi and hw_params, may be needed in prepare step */ + dma->suspended = false; + dma->pdi = pdi; + dma->hw_params = params; + + /* Inform DSP about PDI stream number */ + ret = intel_params_stream(sdw, substream, dai, params, + sdw->instance, + pdi->intel_alh_id); + if (ret) + goto error; + + sconfig.direction = dir; + sconfig.ch_count = ch; + sconfig.frame_rate = params_rate(params); + sconfig.type = dma->stream_type; + + if (dma->stream_type == SDW_STREAM_PDM) { + sconfig.frame_rate *= 50; + sconfig.bps = 1; + } else { + sconfig.bps = snd_pcm_format_width(params_format(params)); + } + + /* Port configuration */ + pconfig = kcalloc(1, sizeof(*pconfig), GFP_KERNEL); + if (!pconfig) { + ret = -ENOMEM; + goto error; + } + + pconfig->num = pdi->num; + pconfig->ch_mask = (1 << ch) - 1; + + ret = sdw_stream_add_master(&cdns->bus, &sconfig, + pconfig, 1, dma->stream); + if (ret) + dev_err(cdns->dev, "add master to stream failed:%d\n", ret); + + kfree(pconfig); +error: + return ret; +} + +static int intel_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_cdns_dma_data *dma; + int ch, dir; + int ret = 0; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) { + dev_err(dai->dev, "failed to get dma data in %s", + __func__); + return -EIO; + } + + if (dma->suspended) { + dma->suspended = false; + + /* + * .prepare() is called after system resume, where we + * need to reinitialize the SHIM/ALH/Cadence IP. + * .prepare() is also called to deal with underflows, + * but in those cases we cannot touch ALH/SHIM + * registers + */ + + /* configure stream */ + ch = params_channels(dma->hw_params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + + intel_pdi_shim_configure(sdw, dma->pdi); + intel_pdi_alh_configure(sdw, dma->pdi); + sdw_cdns_config_stream(cdns, ch, dir, dma->pdi); + + /* Inform DSP about PDI stream number */ + ret = intel_params_stream(sdw, substream, dai, + dma->hw_params, + sdw->instance, + dma->pdi->intel_alh_id); + } + + return ret; +} + +static int +intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_cdns_dma_data *dma; + int ret; + + dma = snd_soc_dai_get_dma_data(dai, substream); + if (!dma) + return -EIO; + + /* + * The sdw stream state will transition to RELEASED when stream-> + * master_list is empty. So the stream state will transition to + * DEPREPARED for the first cpu-dai and to RELEASED for the last + * cpu-dai. + */ + ret = sdw_stream_remove_master(&cdns->bus, dma->stream); + if (ret < 0) { + dev_err(dai->dev, "remove master from stream %s failed: %d\n", + dma->stream->name, ret); + return ret; + } + + ret = intel_free_stream(sdw, substream, dai, sdw->instance); + if (ret < 0) { + dev_err(dai->dev, "intel_free_stream: failed %d", ret); + return ret; + } + + dma->hw_params = NULL; + dma->pdi = NULL; + + return 0; +} + +static void intel_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + + pm_runtime_mark_last_busy(cdns->dev); + pm_runtime_put_autosuspend(cdns->dev); +} + +static int intel_component_dais_suspend(struct snd_soc_component *component) +{ + struct sdw_cdns_dma_data *dma; + struct snd_soc_dai *dai; + + for_each_component_dais(component, dai) { + /* + * we don't have a .suspend dai_ops, and we don't have access + * to the substream, so let's mark both capture and playback + * DMA contexts as suspended + */ + dma = dai->playback_dma_data; + if (dma) + dma->suspended = true; + + dma = dai->capture_dma_data; + if (dma) + dma->suspended = true; + } + + return 0; +} + +static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + return cdns_set_sdw_stream(dai, stream, true, direction); +} + +static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + return cdns_set_sdw_stream(dai, stream, false, direction); +} + +static void *intel_get_sdw_stream(struct snd_soc_dai *dai, + int direction) +{ + struct sdw_cdns_dma_data *dma; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dma = dai->playback_dma_data; + else + dma = dai->capture_dma_data; + + if (!dma) + return ERR_PTR(-EINVAL); + + return dma->stream; +} + +static const struct snd_soc_dai_ops intel_pcm_dai_ops = { + .startup = intel_startup, + .hw_params = intel_hw_params, + .prepare = intel_prepare, + .hw_free = intel_hw_free, + .shutdown = intel_shutdown, + .set_stream = intel_pcm_set_sdw_stream, + .get_stream = intel_get_sdw_stream, +}; + +static const struct snd_soc_dai_ops intel_pdm_dai_ops = { + .startup = intel_startup, + .hw_params = intel_hw_params, + .prepare = intel_prepare, + .hw_free = intel_hw_free, + .shutdown = intel_shutdown, + .set_stream = intel_pdm_set_sdw_stream, + .get_stream = intel_get_sdw_stream, +}; + +static const struct snd_soc_component_driver dai_component = { + .name = "soundwire", + .suspend = intel_component_dais_suspend +}; + +static int intel_create_dai(struct sdw_cdns *cdns, + struct snd_soc_dai_driver *dais, + enum intel_pdi_type type, + u32 num, u32 off, u32 max_ch, bool pcm) +{ + int i; + + if (num == 0) + return 0; + + /* TODO: Read supported rates/formats from hardware */ + for (i = off; i < (off + num); i++) { + dais[i].name = devm_kasprintf(cdns->dev, GFP_KERNEL, + "SDW%d Pin%d", + cdns->instance, i); + if (!dais[i].name) + return -ENOMEM; + + if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) { + dais[i].playback.channels_min = 1; + dais[i].playback.channels_max = max_ch; + dais[i].playback.rates = SNDRV_PCM_RATE_48000; + dais[i].playback.formats = SNDRV_PCM_FMTBIT_S16_LE; + } + + if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) { + dais[i].capture.channels_min = 1; + dais[i].capture.channels_max = max_ch; + dais[i].capture.rates = SNDRV_PCM_RATE_48000; + dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE; + } + + if (pcm) + dais[i].ops = &intel_pcm_dai_ops; + else + dais[i].ops = &intel_pdm_dai_ops; + } + + return 0; +} + +static int intel_register_dai(struct sdw_intel *sdw) +{ + struct sdw_cdns *cdns = &sdw->cdns; + struct sdw_cdns_streams *stream; + struct snd_soc_dai_driver *dais; + int num_dai, ret, off = 0; + + /* DAIs are created based on total number of PDIs supported */ + num_dai = cdns->pcm.num_pdi + cdns->pdm.num_pdi; + + dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + + /* Create PCM DAIs */ + stream = &cdns->pcm; + + ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, cdns->pcm.num_in, + off, stream->num_ch_in, true); + if (ret) + return ret; + + off += cdns->pcm.num_in; + ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT, cdns->pcm.num_out, + off, stream->num_ch_out, true); + if (ret) + return ret; + + off += cdns->pcm.num_out; + ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pcm.num_bd, + off, stream->num_ch_bd, true); + if (ret) + return ret; + + /* Create PDM DAIs */ + stream = &cdns->pdm; + off += cdns->pcm.num_bd; + ret = intel_create_dai(cdns, dais, INTEL_PDI_IN, cdns->pdm.num_in, + off, stream->num_ch_in, false); + if (ret) + return ret; + + off += cdns->pdm.num_in; + ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT, cdns->pdm.num_out, + off, stream->num_ch_out, false); + if (ret) + return ret; + + off += cdns->pdm.num_out; + ret = intel_create_dai(cdns, dais, INTEL_PDI_BD, cdns->pdm.num_bd, + off, stream->num_ch_bd, false); + if (ret) + return ret; + + return snd_soc_register_component(cdns->dev, &dai_component, + dais, num_dai); +} + +static int sdw_master_read_intel_prop(struct sdw_bus *bus) +{ + struct sdw_master_prop *prop = &bus->prop; + struct fwnode_handle *link; + char name[32]; + u32 quirk_mask; + + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", bus->link_id); + + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Master node %s not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(link, + "intel-sdw-ip-clock", + &prop->mclk_freq); + + /* the values reported by BIOS are the 2x clock, not the bus clock */ + prop->mclk_freq /= 2; + + fwnode_property_read_u32(link, + "intel-quirk-mask", + &quirk_mask); + + if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE) + prop->hw_disabled = true; + + return 0; +} + +static int intel_prop_read(struct sdw_bus *bus) +{ + /* Initialize with default handler to read all DisCo properties */ + sdw_master_read_prop(bus); + + /* read Intel-specific properties */ + sdw_master_read_intel_prop(bus); + + return 0; +} + +static struct sdw_master_ops sdw_intel_ops = { + .read_prop = sdw_master_read_prop, + .xfer_msg = cdns_xfer_msg, + .xfer_msg_defer = cdns_xfer_msg_defer, + .reset_page_addr = cdns_reset_page_addr, + .set_bus_conf = cdns_bus_conf, + .pre_bank_switch = intel_pre_bank_switch, + .post_bank_switch = intel_post_bank_switch, +}; + +static int intel_init(struct sdw_intel *sdw) +{ + bool clock_stop; + + /* Initialize shim and controller */ + intel_link_power_up(sdw); + + clock_stop = sdw_cdns_is_clock_stop(&sdw->cdns); + + intel_shim_init(sdw, clock_stop); + + return 0; +} + +/* + * probe and init + */ +static int intel_master_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdw_intel *sdw; + struct sdw_cdns *cdns; + struct sdw_bus *bus; + int ret; + + sdw = devm_kzalloc(dev, sizeof(*sdw), GFP_KERNEL); + if (!sdw) + return -ENOMEM; + + cdns = &sdw->cdns; + bus = &cdns->bus; + + sdw->instance = pdev->id; + sdw->link_res = dev_get_platdata(dev); + cdns->dev = dev; + cdns->registers = sdw->link_res->registers; + cdns->instance = sdw->instance; + cdns->msg_count = 0; + + bus->link_id = pdev->id; + + sdw_cdns_probe(cdns); + + /* Set property read ops */ + sdw_intel_ops.read_prop = intel_prop_read; + bus->ops = &sdw_intel_ops; + + /* set driver data, accessed by snd_soc_dai_get_drvdata() */ + dev_set_drvdata(dev, cdns); + + /* use generic bandwidth allocation algorithm */ + sdw->cdns.bus.compute_params = sdw_compute_params; + + ret = sdw_bus_master_add(bus, dev, dev->fwnode); + if (ret) { + dev_err(dev, "sdw_bus_master_add fail: %d\n", ret); + return ret; + } + + if (bus->prop.hw_disabled) + dev_info(dev, + "SoundWire master %d is disabled, will be ignored\n", + bus->link_id); + /* + * Ignore BIOS err_threshold, it's a really bad idea when dealing + * with multiple hardware synchronized links + */ + bus->prop.err_threshold = 0; + + return 0; +} + +int intel_master_startup(struct platform_device *pdev) +{ + struct sdw_cdns_stream_config config; + struct device *dev = &pdev->dev; + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + int link_flags; + bool multi_link; + u32 clock_stop_quirks; + int ret; + + if (bus->prop.hw_disabled) { + dev_info(dev, + "SoundWire master %d is disabled, ignoring\n", + sdw->instance); + return 0; + } + + link_flags = md_flags >> (bus->link_id * 8); + multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK); + if (!multi_link) { + dev_dbg(dev, "Multi-link is disabled\n"); + bus->multi_link = false; + } else { + /* + * hardware-based synchronization is required regardless + * of the number of segments used by a stream: SSP-based + * synchronization is gated by gsync when the multi-master + * mode is set. + */ + bus->multi_link = true; + bus->hw_sync_min_links = 1; + } + + /* Initialize shim, controller */ + ret = intel_init(sdw); + if (ret) + goto err_init; + + /* Read the PDI config and initialize cadence PDI */ + intel_pdi_init(sdw, &config); + ret = sdw_cdns_pdi_init(cdns, config); + if (ret) + goto err_init; + + intel_pdi_ch_update(sdw); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts\n"); + goto err_init; + } + + /* + * follow recommended programming flows to avoid timeouts when + * gsync is enabled + */ + if (multi_link) + intel_shim_sync_arm(sdw); + + ret = sdw_cdns_init(cdns); + if (ret < 0) { + dev_err(dev, "unable to initialize Cadence IP\n"); + goto err_interrupt; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence\n"); + goto err_interrupt; + } + + if (multi_link) { + ret = intel_shim_sync_go(sdw); + if (ret < 0) { + dev_err(dev, "sync go failed: %d\n", ret); + goto err_interrupt; + } + } + + /* Register DAIs */ + ret = intel_register_dai(sdw); + if (ret) { + dev_err(dev, "DAI registration failed: %d\n", ret); + goto err_interrupt; + } + + intel_debugfs_init(sdw); + + /* Enable runtime PM */ + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) { + pm_runtime_set_autosuspend_delay(dev, + INTEL_MASTER_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_mark_last_busy(dev); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + } + + clock_stop_quirks = sdw->link_res->clock_stop_quirks; + if (clock_stop_quirks & SDW_INTEL_CLK_STOP_NOT_ALLOWED) { + /* + * To keep the clock running we need to prevent + * pm_runtime suspend from happening by increasing the + * reference count. + * This quirk is specified by the parent PCI device in + * case of specific latency requirements. It will have + * no effect if pm_runtime is disabled by the user via + * a module parameter for testing purposes. + */ + pm_runtime_get_noresume(dev); + } + + /* + * The runtime PM status of Slave devices is "Unsupported" + * until they report as ATTACHED. If they don't, e.g. because + * there are no Slave devices populated or if the power-on is + * delayed or dependent on a power switch, the Master will + * remain active and prevent its parent from suspending. + * + * Conditionally force the pm_runtime core to re-evaluate the + * Master status in the absence of any Slave activity. A quirk + * is provided to e.g. deal with Slaves that may be powered on + * with a delay. A more complete solution would require the + * definition of Master properties. + */ + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) + pm_runtime_idle(dev); + + return 0; + +err_interrupt: + sdw_cdns_enable_interrupt(cdns, false); +err_init: + return ret; +} + +static int intel_master_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + + /* + * Since pm_runtime is already disabled, we don't decrease + * the refcount when the clock_stop_quirk is + * SDW_INTEL_CLK_STOP_NOT_ALLOWED + */ + if (!bus->prop.hw_disabled) { + intel_debugfs_exit(sdw); + sdw_cdns_enable_interrupt(cdns, false); + snd_soc_unregister_component(dev); + } + sdw_bus_master_delete(bus); + + return 0; +} + +int intel_master_process_wakeen_event(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdw_intel *sdw; + struct sdw_bus *bus; + void __iomem *shim; + u16 wake_sts; + + sdw = platform_get_drvdata(pdev); + bus = &sdw->cdns.bus; + + if (bus->prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", bus->link_id); + return 0; + } + + shim = sdw->link_res->shim; + wake_sts = intel_readw(shim, SDW_SHIM_WAKESTS); + + if (!(wake_sts & BIT(sdw->instance))) + return 0; + + /* disable WAKEEN interrupt ASAP to prevent interrupt flood */ + intel_shim_wake(sdw, false); + + /* + * resume the Master, which will generate a bus reset and result in + * Slaves re-attaching and be re-enumerated. The SoundWire physical + * device which generated the wake will trigger an interrupt, which + * will in turn cause the corresponding Linux Slave device to be + * resumed and the Slave codec driver to check the status. + */ + pm_request_resume(dev); + + return 0; +} + +/* + * PM calls + */ + +#ifdef CONFIG_PM + +static int __maybe_unused intel_suspend(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + u32 clock_stop_quirks; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + if (pm_runtime_suspended(dev)) { + dev_dbg(dev, "%s: pm_runtime status: suspended\n", __func__); + + clock_stop_quirks = sdw->link_res->clock_stop_quirks; + + if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || + !clock_stop_quirks) && + !pm_runtime_suspended(dev->parent)) { + + /* + * if we've enabled clock stop, and the parent + * is still active, disable shim wake. The + * SHIM registers are not accessible if the + * parent is already pm_runtime suspended so + * it's too late to change that configuration + */ + + intel_shim_wake(sdw, false); + } + + return 0; + } + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + return 0; +} + +static int intel_suspend_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + u32 clock_stop_quirks; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + clock_stop_quirks = sdw->link_res->clock_stop_quirks; + + if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) { + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, false); + + } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || + !clock_stop_quirks) { + ret = sdw_cdns_clock_stop(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable clock stop on suspend\n"); + return ret; + } + + ret = sdw_cdns_enable_interrupt(cdns, false); + if (ret < 0) { + dev_err(dev, "cannot disable interrupts on suspend\n"); + return ret; + } + + ret = intel_link_power_down(sdw); + if (ret) { + dev_err(dev, "Link power down failed: %d", ret); + return ret; + } + + intel_shim_wake(sdw, true); + } else { + dev_err(dev, "%s clock_stop_quirks %x unsupported\n", + __func__, clock_stop_quirks); + ret = -EINVAL; + } + + return ret; +} + +static int __maybe_unused intel_resume(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + int link_flags; + bool multi_link; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + link_flags = md_flags >> (bus->link_id * 8); + multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK); + + if (pm_runtime_suspended(dev)) { + dev_dbg(dev, "%s: pm_runtime status was suspended, forcing active\n", __func__); + + /* follow required sequence from runtime_pm.rst */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_mark_last_busy(dev); + pm_runtime_enable(dev); + + link_flags = md_flags >> (bus->link_id * 8); + + if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) + pm_runtime_idle(dev); + } + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + /* + * make sure all Slaves are tagged as UNATTACHED and provide + * reason for reinitialization + */ + sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + /* + * follow recommended programming flows to avoid timeouts when + * gsync is enabled + */ + if (multi_link) + intel_shim_sync_arm(sdw); + + ret = sdw_cdns_init(&sdw->cdns); + if (ret < 0) { + dev_err(dev, "unable to initialize Cadence IP during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + if (multi_link) { + ret = intel_shim_sync_go(sdw); + if (ret < 0) { + dev_err(dev, "sync go failed during resume\n"); + return ret; + } + } + + /* + * after system resume, the pm_runtime suspend() may kick in + * during the enumeration, before any children device force the + * master device to remain active. Using pm_runtime_get() + * routines is not really possible, since it'd prevent the + * master from suspending. + * A reasonable compromise is to update the pm_runtime + * counters and delay the pm_runtime suspend by several + * seconds, by when all enumeration should be complete. + */ + pm_runtime_mark_last_busy(dev); + + return ret; +} + +static int intel_resume_runtime(struct device *dev) +{ + struct sdw_cdns *cdns = dev_get_drvdata(dev); + struct sdw_intel *sdw = cdns_to_intel(cdns); + struct sdw_bus *bus = &cdns->bus; + u32 clock_stop_quirks; + bool clock_stop0; + int link_flags; + bool multi_link; + int status; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(dev, "SoundWire master %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + link_flags = md_flags >> (bus->link_id * 8); + multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK); + + clock_stop_quirks = sdw->link_res->clock_stop_quirks; + + if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) { + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + /* + * make sure all Slaves are tagged as UNATTACHED and provide + * reason for reinitialization + */ + sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + /* + * follow recommended programming flows to avoid + * timeouts when gsync is enabled + */ + if (multi_link) + intel_shim_sync_arm(sdw); + + ret = sdw_cdns_init(&sdw->cdns); + if (ret < 0) { + dev_err(dev, "unable to initialize Cadence IP during resume\n"); + return ret; + } + + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + if (multi_link) { + ret = intel_shim_sync_go(sdw); + if (ret < 0) { + dev_err(dev, "sync go failed during resume\n"); + return ret; + } + } + } else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) { + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + /* + * An exception condition occurs for the CLK_STOP_BUS_RESET + * case if one or more masters remain active. In this condition, + * all the masters are powered on for they are in the same power + * domain. Master can preserve its context for clock stop0, so + * there is no need to clear slave status and reset bus. + */ + clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns); + + if (!clock_stop0) { + + /* + * make sure all Slaves are tagged as UNATTACHED and + * provide reason for reinitialization + */ + + status = SDW_UNATTACH_REQUEST_MASTER_RESET; + sdw_clear_slave_status(bus, status); + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + /* + * follow recommended programming flows to avoid + * timeouts when gsync is enabled + */ + if (multi_link) + intel_shim_sync_arm(sdw); + + /* + * Re-initialize the IP since it was powered-off + */ + sdw_cdns_init(&sdw->cdns); + + } else { + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + } + + ret = sdw_cdns_clock_restart(cdns, !clock_stop0); + if (ret < 0) { + dev_err(dev, "unable to restart clock during resume\n"); + return ret; + } + + if (!clock_stop0) { + ret = sdw_cdns_exit_reset(cdns); + if (ret < 0) { + dev_err(dev, "unable to exit bus reset sequence during resume\n"); + return ret; + } + + if (multi_link) { + ret = intel_shim_sync_go(sdw); + if (ret < 0) { + dev_err(sdw->cdns.dev, "sync go failed during resume\n"); + return ret; + } + } + } + } else if (!clock_stop_quirks) { + + clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns); + if (!clock_stop0) + dev_err(dev, "%s invalid configuration, clock was not stopped", __func__); + + ret = intel_init(sdw); + if (ret) { + dev_err(dev, "%s failed: %d", __func__, ret); + return ret; + } + + ret = sdw_cdns_enable_interrupt(cdns, true); + if (ret < 0) { + dev_err(dev, "cannot enable interrupts during resume\n"); + return ret; + } + + ret = sdw_cdns_clock_restart(cdns, false); + if (ret < 0) { + dev_err(dev, "unable to resume master during resume\n"); + return ret; + } + } else { + dev_err(dev, "%s clock_stop_quirks %x unsupported\n", + __func__, clock_stop_quirks); + ret = -EINVAL; + } + + return ret; +} + +#endif + +static const struct dev_pm_ops intel_pm = { + SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume) + SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL) +}; + +static struct platform_driver sdw_intel_drv = { + .probe = intel_master_probe, + .remove = intel_master_remove, + .driver = { + .name = "intel-sdw", + .pm = &intel_pm, + } +}; + +module_platform_driver(sdw_intel_drv); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:intel-sdw"); +MODULE_DESCRIPTION("Intel Soundwire Master Driver"); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h new file mode 100644 index 000000000..76820d0b9 --- /dev/null +++ b/drivers/soundwire/intel.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */ +/* Copyright(c) 2015-17 Intel Corporation. */ + +#ifndef __SDW_INTEL_LOCAL_H +#define __SDW_INTEL_LOCAL_H + +/** + * struct sdw_intel_link_res - Soundwire Intel link resource structure, + * typically populated by the controller driver. + * @pdev: platform_device + * @mmio_base: mmio base of SoundWire registers + * @registers: Link IO registers base + * @shim: Audio shim pointer + * @alh: ALH (Audio Link Hub) pointer + * @irq: Interrupt line + * @ops: Shim callback ops + * @dev: device implementing hw_params and free callbacks + * @shim_lock: mutex to handle access to shared SHIM registers + * @shim_mask: global pointer to check SHIM register initialization + * @clock_stop_quirks: mask defining requested behavior on pm_suspend + * @link_mask: global mask needed for power-up/down sequences + * @cdns: Cadence master descriptor + * @list: used to walk-through all masters exposed by the same controller + */ +struct sdw_intel_link_res { + struct platform_device *pdev; + void __iomem *mmio_base; /* not strictly needed, useful for debug */ + void __iomem *registers; + void __iomem *shim; + void __iomem *alh; + int irq; + const struct sdw_intel_ops *ops; + struct device *dev; + struct mutex *shim_lock; /* protect shared registers */ + u32 *shim_mask; + u32 clock_stop_quirks; + u32 link_mask; + struct sdw_cdns *cdns; + struct list_head list; +}; + +struct sdw_intel { + struct sdw_cdns cdns; + int instance; + struct sdw_intel_link_res *link_res; +#ifdef CONFIG_DEBUG_FS + struct dentry *debugfs; +#endif +}; + +#define SDW_INTEL_QUIRK_MASK_BUS_DISABLE BIT(1) + +int intel_master_startup(struct platform_device *pdev); +int intel_master_process_wakeen_event(struct platform_device *pdev); + +#endif /* __SDW_INTEL_LOCAL_H */ diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c new file mode 100644 index 000000000..bc8520eb3 --- /dev/null +++ b/drivers/soundwire/intel_init.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * SDW Intel Init Routines + * + * Initializes and creates SDW devices based on ACPI and Hardware values + */ + +#include <linux/acpi.h> +#include <linux/export.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_intel.h> +#include "cadence_master.h" +#include "intel.h" + +#define SDW_LINK_TYPE 4 /* from Intel ACPI documentation */ +#define SDW_MAX_LINKS 4 +#define SDW_SHIM_LCAP 0x0 +#define SDW_SHIM_BASE 0x2C000 +#define SDW_ALH_BASE 0x2C800 +#define SDW_LINK_BASE 0x30000 +#define SDW_LINK_SIZE 0x10000 + +static int ctrl_link_mask; +module_param_named(sdw_link_mask, ctrl_link_mask, int, 0444); +MODULE_PARM_DESC(sdw_link_mask, "Intel link mask (one bit per link)"); + +static bool is_link_enabled(struct fwnode_handle *fw_node, int i) +{ + struct fwnode_handle *link; + char name[32]; + u32 quirk_mask = 0; + + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", i); + + link = fwnode_get_named_child_node(fw_node, name); + if (!link) + return false; + + fwnode_property_read_u32(link, + "intel-quirk-mask", + &quirk_mask); + + if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE) + return false; + + return true; +} + +static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx) +{ + struct sdw_intel_link_res *link = ctx->links; + u32 link_mask; + int i; + + if (!link) + return 0; + + link_mask = ctx->link_mask; + + for (i = 0; i < ctx->count; i++, link++) { + if (!(link_mask & BIT(i))) + continue; + + if (link->pdev) { + pm_runtime_disable(&link->pdev->dev); + platform_device_unregister(link->pdev); + } + + if (!link->clock_stop_quirks) + pm_runtime_put_noidle(link->dev); + } + + return 0; +} + +static int +sdw_intel_scan_controller(struct sdw_intel_acpi_info *info) +{ + struct acpi_device *adev; + int ret, i; + u8 count; + + if (acpi_bus_get_device(info->handle, &adev)) + return -EINVAL; + + /* Found controller, find links supported */ + count = 0; + ret = fwnode_property_read_u8_array(acpi_fwnode_handle(adev), + "mipi-sdw-master-count", &count, 1); + + /* + * In theory we could check the number of links supported in + * hardware, but in that step we cannot assume SoundWire IP is + * powered. + * + * In addition, if the BIOS doesn't even provide this + * 'master-count' property then all the inits based on link + * masks will fail as well. + * + * We will check the hardware capabilities in the startup() step + */ + + if (ret) { + dev_err(&adev->dev, + "Failed to read mipi-sdw-master-count: %d\n", ret); + return -EINVAL; + } + + /* Check count is within bounds */ + if (count > SDW_MAX_LINKS) { + dev_err(&adev->dev, "Link count %d exceeds max %d\n", + count, SDW_MAX_LINKS); + return -EINVAL; + } + + if (!count) { + dev_warn(&adev->dev, "No SoundWire links detected\n"); + return -EINVAL; + } + dev_dbg(&adev->dev, "ACPI reports %d SDW Link devices\n", count); + + info->count = count; + info->link_mask = 0; + + for (i = 0; i < count; i++) { + if (ctrl_link_mask && !(ctrl_link_mask & BIT(i))) { + dev_dbg(&adev->dev, + "Link %d masked, will not be enabled\n", i); + continue; + } + + if (!is_link_enabled(acpi_fwnode_handle(adev), i)) { + dev_dbg(&adev->dev, + "Link %d not selected in firmware\n", i); + continue; + } + + info->link_mask |= BIT(i); + } + + return 0; +} + +#define HDA_DSP_REG_ADSPIC2 (0x10) +#define HDA_DSP_REG_ADSPIS2 (0x14) +#define HDA_DSP_REG_ADSPIC2_SNDW BIT(5) + +/** + * sdw_intel_enable_irq() - enable/disable Intel SoundWire IRQ + * @mmio_base: The mmio base of the control register + * @enable: true if enable + */ +void sdw_intel_enable_irq(void __iomem *mmio_base, bool enable) +{ + u32 val; + + val = readl(mmio_base + HDA_DSP_REG_ADSPIC2); + + if (enable) + val |= HDA_DSP_REG_ADSPIC2_SNDW; + else + val &= ~HDA_DSP_REG_ADSPIC2_SNDW; + + writel(val, mmio_base + HDA_DSP_REG_ADSPIC2); +} +EXPORT_SYMBOL_NS(sdw_intel_enable_irq, SOUNDWIRE_INTEL_INIT); + +irqreturn_t sdw_intel_thread(int irq, void *dev_id) +{ + struct sdw_intel_ctx *ctx = dev_id; + struct sdw_intel_link_res *link; + + list_for_each_entry(link, &ctx->link_list, list) + sdw_cdns_irq(irq, link->cdns); + + sdw_intel_enable_irq(ctx->mmio_base, true); + return IRQ_HANDLED; +} +EXPORT_SYMBOL_NS(sdw_intel_thread, SOUNDWIRE_INTEL_INIT); + +static struct sdw_intel_ctx +*sdw_intel_probe_controller(struct sdw_intel_res *res) +{ + struct platform_device_info pdevinfo; + struct platform_device *pdev; + struct sdw_intel_link_res *link; + struct sdw_intel_ctx *ctx; + struct acpi_device *adev; + struct sdw_slave *slave; + struct list_head *node; + struct sdw_bus *bus; + u32 link_mask; + int num_slaves = 0; + int count; + int i; + + if (!res) + return NULL; + + if (acpi_bus_get_device(res->handle, &adev)) + return NULL; + + if (!res->count) + return NULL; + + count = res->count; + dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count); + + ctx = devm_kzalloc(&adev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ctx->count = count; + ctx->links = devm_kcalloc(&adev->dev, ctx->count, + sizeof(*ctx->links), GFP_KERNEL); + if (!ctx->links) + return NULL; + + ctx->count = count; + ctx->mmio_base = res->mmio_base; + ctx->link_mask = res->link_mask; + ctx->handle = res->handle; + mutex_init(&ctx->shim_lock); + + link = ctx->links; + link_mask = ctx->link_mask; + + INIT_LIST_HEAD(&ctx->link_list); + + /* Create SDW Master devices */ + for (i = 0; i < count; i++, link++) { + if (!(link_mask & BIT(i))) { + dev_dbg(&adev->dev, + "Link %d masked, will not be enabled\n", i); + continue; + } + + link->mmio_base = res->mmio_base; + link->registers = res->mmio_base + SDW_LINK_BASE + + (SDW_LINK_SIZE * i); + link->shim = res->mmio_base + SDW_SHIM_BASE; + link->alh = res->mmio_base + SDW_ALH_BASE; + + link->ops = res->ops; + link->dev = res->dev; + + link->clock_stop_quirks = res->clock_stop_quirks; + link->shim_lock = &ctx->shim_lock; + link->shim_mask = &ctx->shim_mask; + link->link_mask = link_mask; + + memset(&pdevinfo, 0, sizeof(pdevinfo)); + + pdevinfo.parent = res->parent; + pdevinfo.name = "intel-sdw"; + pdevinfo.id = i; + pdevinfo.fwnode = acpi_fwnode_handle(adev); + pdevinfo.data = link; + pdevinfo.size_data = sizeof(*link); + + pdev = platform_device_register_full(&pdevinfo); + if (IS_ERR(pdev)) { + dev_err(&adev->dev, + "platform device creation failed: %ld\n", + PTR_ERR(pdev)); + goto err; + } + link->pdev = pdev; + link->cdns = platform_get_drvdata(pdev); + + list_add_tail(&link->list, &ctx->link_list); + bus = &link->cdns->bus; + /* Calculate number of slaves */ + list_for_each(node, &bus->slaves) + num_slaves++; + } + + ctx->ids = devm_kcalloc(&adev->dev, num_slaves, + sizeof(*ctx->ids), GFP_KERNEL); + if (!ctx->ids) + goto err; + + ctx->num_slaves = num_slaves; + i = 0; + list_for_each_entry(link, &ctx->link_list, list) { + bus = &link->cdns->bus; + list_for_each_entry(slave, &bus->slaves, node) { + ctx->ids[i].id = slave->id; + ctx->ids[i].link_id = bus->link_id; + i++; + } + } + + return ctx; + +err: + ctx->count = i; + sdw_intel_cleanup(ctx); + return NULL; +} + +static int +sdw_intel_startup_controller(struct sdw_intel_ctx *ctx) +{ + struct acpi_device *adev; + struct sdw_intel_link_res *link; + u32 caps; + u32 link_mask; + int i; + + if (acpi_bus_get_device(ctx->handle, &adev)) + return -EINVAL; + + /* Check SNDWLCAP.LCOUNT */ + caps = ioread32(ctx->mmio_base + SDW_SHIM_BASE + SDW_SHIM_LCAP); + caps &= GENMASK(2, 0); + + /* Check HW supported vs property value */ + if (caps < ctx->count) { + dev_err(&adev->dev, + "BIOS master count is larger than hardware capabilities\n"); + return -EINVAL; + } + + if (!ctx->links) + return -EINVAL; + + link = ctx->links; + link_mask = ctx->link_mask; + + /* Startup SDW Master devices */ + for (i = 0; i < ctx->count; i++, link++) { + if (!(link_mask & BIT(i))) + continue; + + intel_master_startup(link->pdev); + + if (!link->clock_stop_quirks) { + /* + * we need to prevent the parent PCI device + * from entering pm_runtime suspend, so that + * power rails to the SoundWire IP are not + * turned off. + */ + pm_runtime_get_noresume(link->dev); + } + } + + return 0; +} + +static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, + void *cdata, void **return_value) +{ + struct sdw_intel_acpi_info *info = cdata; + struct acpi_device *adev; + acpi_status status; + u64 adr; + + status = acpi_evaluate_integer(handle, METHOD_NAME__ADR, NULL, &adr); + if (ACPI_FAILURE(status)) + return AE_OK; /* keep going */ + + if (acpi_bus_get_device(handle, &adev)) { + pr_err("%s: Couldn't find ACPI handle\n", __func__); + return AE_NOT_FOUND; + } + + info->handle = handle; + + /* + * On some Intel platforms, multiple children of the HDAS + * device can be found, but only one of them is the SoundWire + * controller. The SNDW device is always exposed with + * Name(_ADR, 0x40000000), with bits 31..28 representing the + * SoundWire link so filter accordingly + */ + if (FIELD_GET(GENMASK(31, 28), adr) != SDW_LINK_TYPE) + return AE_OK; /* keep going */ + + /* device found, stop namespace walk */ + return AE_CTRL_TERMINATE; +} + +/** + * sdw_intel_acpi_scan() - SoundWire Intel init routine + * @parent_handle: ACPI parent handle + * @info: description of what firmware/DSDT tables expose + * + * This scans the namespace and queries firmware to figure out which + * links to enable. A follow-up use of sdw_intel_probe() and + * sdw_intel_startup() is required for creation of devices and bus + * startup + */ +int sdw_intel_acpi_scan(acpi_handle *parent_handle, + struct sdw_intel_acpi_info *info) +{ + acpi_status status; + + info->handle = NULL; + status = acpi_walk_namespace(ACPI_TYPE_DEVICE, + parent_handle, 1, + sdw_intel_acpi_cb, + NULL, info, NULL); + if (ACPI_FAILURE(status) || info->handle == NULL) + return -ENODEV; + + return sdw_intel_scan_controller(info); +} +EXPORT_SYMBOL_NS(sdw_intel_acpi_scan, SOUNDWIRE_INTEL_INIT); + +/** + * sdw_intel_probe() - SoundWire Intel probe routine + * @res: resource data + * + * This registers a platform device for each Master handled by the controller, + * and SoundWire Master and Slave devices will be created by the platform + * device probe. All the information necessary is stored in the context, and + * the res argument pointer can be freed after this step. + * This function will be called after sdw_intel_acpi_scan() by SOF probe. + */ +struct sdw_intel_ctx +*sdw_intel_probe(struct sdw_intel_res *res) +{ + return sdw_intel_probe_controller(res); +} +EXPORT_SYMBOL_NS(sdw_intel_probe, SOUNDWIRE_INTEL_INIT); + +/** + * sdw_intel_startup() - SoundWire Intel startup + * @ctx: SoundWire context allocated in the probe + * + * Startup Intel SoundWire controller. This function will be called after + * Intel Audio DSP is powered up. + */ +int sdw_intel_startup(struct sdw_intel_ctx *ctx) +{ + return sdw_intel_startup_controller(ctx); +} +EXPORT_SYMBOL_NS(sdw_intel_startup, SOUNDWIRE_INTEL_INIT); +/** + * sdw_intel_exit() - SoundWire Intel exit + * @ctx: SoundWire context allocated in the probe + * + * Delete the controller instances created and cleanup + */ +void sdw_intel_exit(struct sdw_intel_ctx *ctx) +{ + sdw_intel_cleanup(ctx); +} +EXPORT_SYMBOL_NS(sdw_intel_exit, SOUNDWIRE_INTEL_INIT); + +void sdw_intel_process_wakeen_event(struct sdw_intel_ctx *ctx) +{ + struct sdw_intel_link_res *link; + u32 link_mask; + int i; + + if (!ctx->links) + return; + + link = ctx->links; + link_mask = ctx->link_mask; + + /* Startup SDW Master devices */ + for (i = 0; i < ctx->count; i++, link++) { + if (!(link_mask & BIT(i))) + continue; + + intel_master_process_wakeen_event(link->pdev); + } +} +EXPORT_SYMBOL_NS(sdw_intel_process_wakeen_event, SOUNDWIRE_INTEL_INIT); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("Intel Soundwire Init Library"); diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c new file mode 100644 index 000000000..9b05c9e25 --- /dev/null +++ b/drivers/soundwire/master.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2019-2020 Intel Corporation. + +#include <linux/device.h> +#include <linux/acpi.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" + +/* + * The 3s value for autosuspend will only be used if there are no + * devices physically attached on a bus segment. In practice enabling + * the bus operation will result in children devices become active and + * the master device will only suspend when all its children are no + * longer active. + */ +#define SDW_MASTER_SUSPEND_DELAY_MS 3000 + +/* + * The sysfs for properties reflects the MIPI description as given + * in the MIPI DisCo spec + * + * Base file is: + * sdw-master-N + * |---- revision + * |---- clk_stop_modes + * |---- max_clk_freq + * |---- clk_freq + * |---- clk_gears + * |---- default_row + * |---- default_col + * |---- dynamic_shape + * |---- err_threshold + */ + +#define sdw_master_attr(field, format_string) \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sdw_master_device *md = dev_to_sdw_master_device(dev); \ + return sprintf(buf, format_string, md->bus->prop.field); \ +} \ +static DEVICE_ATTR_RO(field) + +sdw_master_attr(revision, "0x%x\n"); +sdw_master_attr(clk_stop_modes, "0x%x\n"); +sdw_master_attr(max_clk_freq, "%d\n"); +sdw_master_attr(default_row, "%d\n"); +sdw_master_attr(default_col, "%d\n"); +sdw_master_attr(default_frame_rate, "%d\n"); +sdw_master_attr(dynamic_frame, "%d\n"); +sdw_master_attr(err_threshold, "%d\n"); + +static ssize_t clock_frequencies_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_master_device *md = dev_to_sdw_master_device(dev); + ssize_t size = 0; + int i; + + for (i = 0; i < md->bus->prop.num_clk_freq; i++) + size += sprintf(buf + size, "%8d ", + md->bus->prop.clk_freq[i]); + size += sprintf(buf + size, "\n"); + + return size; +} +static DEVICE_ATTR_RO(clock_frequencies); + +static ssize_t clock_gears_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_master_device *md = dev_to_sdw_master_device(dev); + ssize_t size = 0; + int i; + + for (i = 0; i < md->bus->prop.num_clk_gears; i++) + size += sprintf(buf + size, "%8d ", + md->bus->prop.clk_gears[i]); + size += sprintf(buf + size, "\n"); + + return size; +} +static DEVICE_ATTR_RO(clock_gears); + +static struct attribute *master_node_attrs[] = { + &dev_attr_revision.attr, + &dev_attr_clk_stop_modes.attr, + &dev_attr_max_clk_freq.attr, + &dev_attr_default_row.attr, + &dev_attr_default_col.attr, + &dev_attr_default_frame_rate.attr, + &dev_attr_dynamic_frame.attr, + &dev_attr_err_threshold.attr, + &dev_attr_clock_frequencies.attr, + &dev_attr_clock_gears.attr, + NULL, +}; +ATTRIBUTE_GROUPS(master_node); + +static void sdw_master_device_release(struct device *dev) +{ + struct sdw_master_device *md = dev_to_sdw_master_device(dev); + + kfree(md); +} + +static const struct dev_pm_ops master_dev_pm = { + SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend, + pm_generic_runtime_resume, NULL) +}; + +struct device_type sdw_master_type = { + .name = "soundwire_master", + .release = sdw_master_device_release, + .pm = &master_dev_pm, +}; + +/** + * sdw_master_device_add() - create a Linux Master Device representation. + * @bus: SDW bus instance + * @parent: parent device + * @fwnode: firmware node handle + */ +int sdw_master_device_add(struct sdw_bus *bus, struct device *parent, + struct fwnode_handle *fwnode) +{ + struct sdw_master_device *md; + int ret; + + if (!parent) + return -EINVAL; + + md = kzalloc(sizeof(*md), GFP_KERNEL); + if (!md) + return -ENOMEM; + + md->dev.bus = &sdw_bus_type; + md->dev.type = &sdw_master_type; + md->dev.parent = parent; + md->dev.groups = master_node_groups; + md->dev.of_node = parent->of_node; + md->dev.fwnode = fwnode; + md->dev.dma_mask = parent->dma_mask; + + dev_set_name(&md->dev, "sdw-master-%d", bus->id); + + ret = device_register(&md->dev); + if (ret) { + dev_err(parent, "Failed to add master: ret %d\n", ret); + /* + * On err, don't free but drop ref as this will be freed + * when release method is invoked. + */ + put_device(&md->dev); + goto device_register_err; + } + + /* add shortcuts to improve code readability/compactness */ + md->bus = bus; + bus->dev = &md->dev; + bus->md = md; + + pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(&bus->md->dev); + pm_runtime_mark_last_busy(&bus->md->dev); + pm_runtime_set_active(&bus->md->dev); + pm_runtime_enable(&bus->md->dev); + pm_runtime_idle(&bus->md->dev); +device_register_err: + return ret; +} + +/** + * sdw_master_device_del() - delete a Linux Master Device representation. + * @bus: bus handle + * + * This function is the dual of sdw_master_device_add() + */ +int sdw_master_device_del(struct sdw_bus *bus) +{ + pm_runtime_disable(&bus->md->dev); + device_unregister(bus->dev); + + return 0; +} diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c new file mode 100644 index 000000000..55a9c51c8 --- /dev/null +++ b/drivers/soundwire/mipi_disco.c @@ -0,0 +1,387 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-17 Intel Corporation. + +/* + * MIPI Discovery And Configuration (DisCo) Specification for SoundWire + * specifies properties to be implemented for SoundWire Masters and Slaves. + * The DisCo spec doesn't mandate these properties. However, SDW bus cannot + * work without knowing these values. + * + * The helper functions read the Master and Slave properties. Implementers + * of Master or Slave drivers can use any of the below three mechanisms: + * a) Use these APIs here as .read_prop() callback for Master and Slave + * b) Implement own methods and set those as .read_prop(), but invoke + * APIs in this file for generic read and override the values with + * platform specific data + * c) Implement ones own methods which do not use anything provided + * here + */ + +#include <linux/device.h> +#include <linux/property.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw.h> +#include "bus.h" + +/** + * sdw_master_read_prop() - Read Master properties + * @bus: SDW bus instance + */ +int sdw_master_read_prop(struct sdw_bus *bus) +{ + struct sdw_master_prop *prop = &bus->prop; + struct fwnode_handle *link; + char name[32]; + int nval, i; + + device_property_read_u32(bus->dev, + "mipi-sdw-sw-interface-revision", + &prop->revision); + + /* Find master handle */ + snprintf(name, sizeof(name), + "mipi-sdw-link-%d-subproperties", bus->link_id); + + link = device_get_named_child_node(bus->dev, name); + if (!link) { + dev_err(bus->dev, "Master node %s not found\n", name); + return -EIO; + } + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode0-supported")) + prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE0); + + if (fwnode_property_read_bool(link, + "mipi-sdw-clock-stop-mode1-supported")) + prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE1); + + fwnode_property_read_u32(link, + "mipi-sdw-max-clock-frequency", + &prop->max_clk_freq); + + nval = fwnode_property_count_u32(link, "mipi-sdw-clock-frequencies-supported"); + if (nval > 0) { + prop->num_clk_freq = nval; + prop->clk_freq = devm_kcalloc(bus->dev, prop->num_clk_freq, + sizeof(*prop->clk_freq), + GFP_KERNEL); + if (!prop->clk_freq) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-clock-frequencies-supported", + prop->clk_freq, prop->num_clk_freq); + } + + /* + * Check the frequencies supported. If FW doesn't provide max + * freq, then populate here by checking values. + */ + if (!prop->max_clk_freq && prop->clk_freq) { + prop->max_clk_freq = prop->clk_freq[0]; + for (i = 1; i < prop->num_clk_freq; i++) { + if (prop->clk_freq[i] > prop->max_clk_freq) + prop->max_clk_freq = prop->clk_freq[i]; + } + } + + nval = fwnode_property_count_u32(link, "mipi-sdw-supported-clock-gears"); + if (nval > 0) { + prop->num_clk_gears = nval; + prop->clk_gears = devm_kcalloc(bus->dev, prop->num_clk_gears, + sizeof(*prop->clk_gears), + GFP_KERNEL); + if (!prop->clk_gears) + return -ENOMEM; + + fwnode_property_read_u32_array(link, + "mipi-sdw-supported-clock-gears", + prop->clk_gears, + prop->num_clk_gears); + } + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate", + &prop->default_frame_rate); + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size", + &prop->default_row); + + fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size", + &prop->default_col); + + prop->dynamic_frame = fwnode_property_read_bool(link, + "mipi-sdw-dynamic-frame-shape"); + + fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold", + &prop->err_threshold); + + return 0; +} +EXPORT_SYMBOL(sdw_master_read_prop); + +static int sdw_slave_read_dp0(struct sdw_slave *slave, + struct fwnode_handle *port, + struct sdw_dp0_prop *dp0) +{ + int nval; + + fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength", + &dp0->max_word); + + fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength", + &dp0->min_word); + + nval = fwnode_property_count_u32(port, "mipi-sdw-port-wordlength-configs"); + if (nval > 0) { + + dp0->num_words = nval; + dp0->words = devm_kcalloc(&slave->dev, + dp0->num_words, sizeof(*dp0->words), + GFP_KERNEL); + if (!dp0->words) + return -ENOMEM; + + fwnode_property_read_u32_array(port, + "mipi-sdw-port-wordlength-configs", + dp0->words, dp0->num_words); + } + + dp0->BRA_flow_controlled = fwnode_property_read_bool(port, + "mipi-sdw-bra-flow-controlled"); + + dp0->simple_ch_prep_sm = fwnode_property_read_bool(port, + "mipi-sdw-simplified-channel-prepare-sm"); + + dp0->imp_def_interrupts = fwnode_property_read_bool(port, + "mipi-sdw-imp-def-dp0-interrupts-supported"); + + return 0; +} + +static int sdw_slave_read_dpn(struct sdw_slave *slave, + struct sdw_dpn_prop *dpn, int count, int ports, + char *type) +{ + struct fwnode_handle *node; + u32 bit, i = 0; + int nval; + unsigned long addr; + char name[40]; + + addr = ports; + /* valid ports are 1 to 14 so apply mask */ + addr &= GENMASK(14, 1); + + for_each_set_bit(bit, &addr, 32) { + snprintf(name, sizeof(name), + "mipi-sdw-dp-%d-%s-subproperties", bit, type); + + dpn[i].num = bit; + + node = device_get_named_child_node(&slave->dev, name); + if (!node) { + dev_err(&slave->dev, "%s dpN not found\n", name); + return -EIO; + } + + fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength", + &dpn[i].max_word); + fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength", + &dpn[i].min_word); + + nval = fwnode_property_count_u32(node, "mipi-sdw-port-wordlength-configs"); + if (nval > 0) { + dpn[i].num_words = nval; + dpn[i].words = devm_kcalloc(&slave->dev, + dpn[i].num_words, + sizeof(*dpn[i].words), + GFP_KERNEL); + if (!dpn[i].words) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-port-wordlength-configs", + dpn[i].words, dpn[i].num_words); + } + + fwnode_property_read_u32(node, "mipi-sdw-data-port-type", + &dpn[i].type); + + fwnode_property_read_u32(node, + "mipi-sdw-max-grouping-supported", + &dpn[i].max_grouping); + + dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node, + "mipi-sdw-simplified-channelprepare-sm"); + + fwnode_property_read_u32(node, + "mipi-sdw-port-channelprepare-timeout", + &dpn[i].ch_prep_timeout); + + fwnode_property_read_u32(node, + "mipi-sdw-imp-def-dpn-interrupts-supported", + &dpn[i].imp_def_interrupts); + + fwnode_property_read_u32(node, "mipi-sdw-min-channel-number", + &dpn[i].min_ch); + + fwnode_property_read_u32(node, "mipi-sdw-max-channel-number", + &dpn[i].max_ch); + + nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list"); + if (nval > 0) { + dpn[i].num_channels = nval; + dpn[i].channels = devm_kcalloc(&slave->dev, + dpn[i].num_channels, + sizeof(*dpn[i].channels), + GFP_KERNEL); + if (!dpn[i].channels) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-number-list", + dpn[i].channels, dpn[i].num_channels); + } + + nval = fwnode_property_count_u32(node, "mipi-sdw-channel-combination-list"); + if (nval > 0) { + dpn[i].num_ch_combinations = nval; + dpn[i].ch_combinations = devm_kcalloc(&slave->dev, + dpn[i].num_ch_combinations, + sizeof(*dpn[i].ch_combinations), + GFP_KERNEL); + if (!dpn[i].ch_combinations) + return -ENOMEM; + + fwnode_property_read_u32_array(node, + "mipi-sdw-channel-combination-list", + dpn[i].ch_combinations, + dpn[i].num_ch_combinations); + } + + fwnode_property_read_u32(node, + "mipi-sdw-modes-supported", &dpn[i].modes); + + fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer", + &dpn[i].max_async_buffer); + + dpn[i].block_pack_mode = fwnode_property_read_bool(node, + "mipi-sdw-block-packing-mode"); + + fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type", + &dpn[i].port_encoding); + + /* TODO: Read audio mode */ + + i++; + } + + return 0; +} + +/** + * sdw_slave_read_prop() - Read Slave properties + * @slave: SDW Slave + */ +int sdw_slave_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + struct fwnode_handle *port; + int nval; + + device_property_read_u32(dev, "mipi-sdw-sw-interface-revision", + &prop->mipi_revision); + + prop->wake_capable = device_property_read_bool(dev, + "mipi-sdw-wake-up-unavailable"); + prop->wake_capable = !prop->wake_capable; + + prop->test_mode_capable = device_property_read_bool(dev, + "mipi-sdw-test-mode-supported"); + + prop->clk_stop_mode1 = false; + if (device_property_read_bool(dev, + "mipi-sdw-clock-stop-mode1-supported")) + prop->clk_stop_mode1 = true; + + prop->simple_clk_stop_capable = device_property_read_bool(dev, + "mipi-sdw-simplified-clockstopprepare-sm-supported"); + + device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout", + &prop->clk_stop_timeout); + + device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout", + &prop->ch_prep_timeout); + + device_property_read_u32(dev, + "mipi-sdw-clockstopprepare-hard-reset-behavior", + &prop->reset_behave); + + prop->high_PHY_capable = device_property_read_bool(dev, + "mipi-sdw-highPHY-capable"); + + prop->paging_support = device_property_read_bool(dev, + "mipi-sdw-paging-support"); + + prop->bank_delay_support = device_property_read_bool(dev, + "mipi-sdw-bank-delay-support"); + + device_property_read_u32(dev, + "mipi-sdw-port15-read-behavior", &prop->p15_behave); + + device_property_read_u32(dev, "mipi-sdw-master-count", + &prop->master_count); + + device_property_read_u32(dev, "mipi-sdw-source-port-list", + &prop->source_ports); + + device_property_read_u32(dev, "mipi-sdw-sink-port-list", + &prop->sink_ports); + + /* Read dp0 properties */ + port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties"); + if (!port) { + dev_dbg(dev, "DP0 node not found!!\n"); + } else { + prop->dp0_prop = devm_kzalloc(&slave->dev, + sizeof(*prop->dp0_prop), + GFP_KERNEL); + if (!prop->dp0_prop) + return -ENOMEM; + + sdw_slave_read_dp0(slave, port, prop->dp0_prop); + } + + /* + * Based on each DPn port, get source and sink dpn properties. + * Also, some ports can operate as both source or sink. + */ + + /* Allocate memory for set bits in port lists */ + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), + GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for source port(s) */ + sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval, + prop->source_ports, "source"); + + nval = hweight32(prop->sink_ports); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), + GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + /* Read dpn properties for sink port(s) */ + sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval, + prop->sink_ports, "sink"); + + return 0; +} +EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c new file mode 100644 index 000000000..ac7325879 --- /dev/null +++ b/drivers/soundwire/qcom.c @@ -0,0 +1,932 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019, Linaro Limited + +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/slimbus.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" + +#define SWRM_COMP_HW_VERSION 0x00 +#define SWRM_COMP_CFG_ADDR 0x04 +#define SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK BIT(1) +#define SWRM_COMP_CFG_ENABLE_MSK BIT(0) +#define SWRM_COMP_PARAMS 0x100 +#define SWRM_COMP_PARAMS_DOUT_PORTS_MASK GENMASK(4, 0) +#define SWRM_COMP_PARAMS_DIN_PORTS_MASK GENMASK(9, 5) +#define SWRM_INTERRUPT_STATUS 0x200 +#define SWRM_INTERRUPT_STATUS_RMSK GENMASK(16, 0) +#define SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED BIT(1) +#define SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS BIT(2) +#define SWRM_INTERRUPT_STATUS_CMD_ERROR BIT(7) +#define SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED BIT(10) +#define SWRM_INTERRUPT_MASK_ADDR 0x204 +#define SWRM_INTERRUPT_CLEAR 0x208 +#define SWRM_INTERRUPT_CPU_EN 0x210 +#define SWRM_CMD_FIFO_WR_CMD 0x300 +#define SWRM_CMD_FIFO_RD_CMD 0x304 +#define SWRM_CMD_FIFO_CMD 0x308 +#define SWRM_CMD_FIFO_STATUS 0x30C +#define SWRM_CMD_FIFO_CFG_ADDR 0x314 +#define SWRM_RD_WR_CMD_RETRIES 0x7 +#define SWRM_CMD_FIFO_RD_FIFO_ADDR 0x318 +#define SWRM_ENUMERATOR_CFG_ADDR 0x500 +#define SWRM_MCP_FRAME_CTRL_BANK_ADDR(m) (0x101C + 0x40 * (m)) +#define SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK GENMASK(2, 0) +#define SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK GENMASK(7, 3) +#define SWRM_MCP_CFG_ADDR 0x1048 +#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK GENMASK(21, 17) +#define SWRM_DEF_CMD_NO_PINGS 0x1f +#define SWRM_MCP_STATUS 0x104C +#define SWRM_MCP_STATUS_BANK_NUM_MASK BIT(0) +#define SWRM_MCP_SLV_STATUS 0x1090 +#define SWRM_MCP_SLV_STATUS_MASK GENMASK(1, 0) +#define SWRM_DP_PORT_CTRL_BANK(n, m) (0x1124 + 0x100 * (n - 1) + 0x40 * m) +#define SWRM_DP_BLOCK_CTRL3_BANK(n, m) (0x1138 + 0x100 * (n - 1) + 0x40 * m) +#define SWRM_DP_PORT_CTRL_EN_CHAN_SHFT 0x18 +#define SWRM_DP_PORT_CTRL_OFFSET2_SHFT 0x10 +#define SWRM_DP_PORT_CTRL_OFFSET1_SHFT 0x08 +#define SWRM_AHB_BRIDGE_WR_DATA_0 0xc85 +#define SWRM_AHB_BRIDGE_WR_ADDR_0 0xc89 +#define SWRM_AHB_BRIDGE_RD_ADDR_0 0xc8d +#define SWRM_AHB_BRIDGE_RD_DATA_0 0xc91 + +#define SWRM_REG_VAL_PACK(data, dev, id, reg) \ + ((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24)) + +#define SWRM_SPECIAL_CMD_ID 0xF +#define MAX_FREQ_NUM 1 +#define TIMEOUT_MS (2 * HZ) +#define QCOM_SWRM_MAX_RD_LEN 0xf +#define QCOM_SDW_MAX_PORTS 14 +#define DEFAULT_CLK_FREQ 9600000 +#define SWRM_MAX_DAIS 0xF + +struct qcom_swrm_port_config { + u8 si; + u8 off1; + u8 off2; + u8 bp_mode; +}; + +struct qcom_swrm_ctrl { + struct sdw_bus bus; + struct device *dev; + struct regmap *regmap; + void __iomem *mmio; + struct completion *comp; + struct work_struct slave_work; + /* read/write lock */ + spinlock_t comp_lock; + /* Port alloc/free lock */ + struct mutex port_lock; + struct clk *hclk; + u8 wr_cmd_id; + u8 rd_cmd_id; + int irq; + unsigned int version; + int num_din_ports; + int num_dout_ports; + int cols_index; + int rows_index; + unsigned long dout_port_mask; + unsigned long din_port_mask; + struct qcom_swrm_port_config pconfig[QCOM_SDW_MAX_PORTS]; + struct sdw_stream_runtime *sruntime[SWRM_MAX_DAIS]; + enum sdw_slave_status status[SDW_MAX_DEVICES]; + int (*reg_read)(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val); + int (*reg_write)(struct qcom_swrm_ctrl *ctrl, int reg, int val); +}; + +struct qcom_swrm_data { + u32 default_cols; + u32 default_rows; +}; + +static struct qcom_swrm_data swrm_v1_3_data = { + .default_rows = 48, + .default_cols = 16, +}; + +static struct qcom_swrm_data swrm_v1_5_data = { + .default_rows = 50, + .default_cols = 16, +}; + +#define to_qcom_sdw(b) container_of(b, struct qcom_swrm_ctrl, bus) + +static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, + u32 *val) +{ + struct regmap *wcd_regmap = ctrl->regmap; + int ret; + + /* pg register + offset */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_RD_ADDR_0, + (u8 *)®, 4); + if (ret < 0) + return SDW_CMD_FAIL; + + ret = regmap_bulk_read(wcd_regmap, SWRM_AHB_BRIDGE_RD_DATA_0, + val, 4); + if (ret < 0) + return SDW_CMD_FAIL; + + return SDW_CMD_OK; +} + +static int qcom_swrm_ahb_reg_write(struct qcom_swrm_ctrl *ctrl, + int reg, int val) +{ + struct regmap *wcd_regmap = ctrl->regmap; + int ret; + /* pg register + offset */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_DATA_0, + (u8 *)&val, 4); + if (ret) + return SDW_CMD_FAIL; + + /* write address register */ + ret = regmap_bulk_write(wcd_regmap, SWRM_AHB_BRIDGE_WR_ADDR_0, + (u8 *)®, 4); + if (ret) + return SDW_CMD_FAIL; + + return SDW_CMD_OK; +} + +static int qcom_swrm_cpu_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, + u32 *val) +{ + *val = readl(ctrl->mmio + reg); + return SDW_CMD_OK; +} + +static int qcom_swrm_cpu_reg_write(struct qcom_swrm_ctrl *ctrl, int reg, + int val) +{ + writel(val, ctrl->mmio + reg); + return SDW_CMD_OK; +} + +static int qcom_swrm_cmd_fifo_wr_cmd(struct qcom_swrm_ctrl *ctrl, u8 cmd_data, + u8 dev_addr, u16 reg_addr) +{ + DECLARE_COMPLETION_ONSTACK(comp); + unsigned long flags; + u32 val; + int ret; + + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = ∁ + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + val = SWRM_REG_VAL_PACK(cmd_data, dev_addr, + SWRM_SPECIAL_CMD_ID, reg_addr); + ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_WR_CMD, val); + if (ret) + goto err; + + ret = wait_for_completion_timeout(ctrl->comp, + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) + ret = SDW_CMD_IGNORED; + else + ret = SDW_CMD_OK; +err: + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = NULL; + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + return ret; +} + +static int qcom_swrm_cmd_fifo_rd_cmd(struct qcom_swrm_ctrl *ctrl, + u8 dev_addr, u16 reg_addr, + u32 len, u8 *rval) +{ + int i, ret; + u32 val; + DECLARE_COMPLETION_ONSTACK(comp); + unsigned long flags; + + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = ∁ + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + val = SWRM_REG_VAL_PACK(len, dev_addr, SWRM_SPECIAL_CMD_ID, reg_addr); + ret = ctrl->reg_write(ctrl, SWRM_CMD_FIFO_RD_CMD, val); + if (ret) + goto err; + + ret = wait_for_completion_timeout(ctrl->comp, + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + ret = SDW_CMD_IGNORED; + goto err; + } else { + ret = SDW_CMD_OK; + } + + for (i = 0; i < len; i++) { + ctrl->reg_read(ctrl, SWRM_CMD_FIFO_RD_FIFO_ADDR, &val); + rval[i] = val & 0xFF; + } + +err: + spin_lock_irqsave(&ctrl->comp_lock, flags); + ctrl->comp = NULL; + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + + return ret; +} + +static void qcom_swrm_get_device_status(struct qcom_swrm_ctrl *ctrl) +{ + u32 val; + int i; + + ctrl->reg_read(ctrl, SWRM_MCP_SLV_STATUS, &val); + + for (i = 0; i < SDW_MAX_DEVICES; i++) { + u32 s; + + s = (val >> (i * 2)); + s &= SWRM_MCP_SLV_STATUS_MASK; + ctrl->status[i] = s; + } +} + +static irqreturn_t qcom_swrm_irq_handler(int irq, void *dev_id) +{ + struct qcom_swrm_ctrl *ctrl = dev_id; + u32 sts, value; + unsigned long flags; + + ctrl->reg_read(ctrl, SWRM_INTERRUPT_STATUS, &sts); + + if (sts & SWRM_INTERRUPT_STATUS_CMD_ERROR) { + ctrl->reg_read(ctrl, SWRM_CMD_FIFO_STATUS, &value); + dev_err_ratelimited(ctrl->dev, + "CMD error, fifo status 0x%x\n", + value); + ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CMD, 0x1); + } + + if ((sts & SWRM_INTERRUPT_STATUS_NEW_SLAVE_ATTACHED) || + sts & SWRM_INTERRUPT_STATUS_CHANGE_ENUM_SLAVE_STATUS) + schedule_work(&ctrl->slave_work); + + /** + * clear the interrupt before complete() is called, as complete can + * schedule new read/writes which require interrupts, clearing the + * interrupt would avoid missing interrupts in such cases. + */ + ctrl->reg_write(ctrl, SWRM_INTERRUPT_CLEAR, sts); + + if (sts & SWRM_INTERRUPT_STATUS_SPECIAL_CMD_ID_FINISHED) { + spin_lock_irqsave(&ctrl->comp_lock, flags); + if (ctrl->comp) + complete(ctrl->comp); + spin_unlock_irqrestore(&ctrl->comp_lock, flags); + } + + return IRQ_HANDLED; +} +static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) +{ + u32 val; + + /* Clear Rows and Cols */ + val = FIELD_PREP(SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK, ctrl->rows_index); + val |= FIELD_PREP(SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK, ctrl->cols_index); + + ctrl->reg_write(ctrl, SWRM_MCP_FRAME_CTRL_BANK_ADDR(0), val); + + /* Disable Auto enumeration */ + ctrl->reg_write(ctrl, SWRM_ENUMERATOR_CFG_ADDR, 0); + + /* Mask soundwire interrupts */ + ctrl->reg_write(ctrl, SWRM_INTERRUPT_MASK_ADDR, + SWRM_INTERRUPT_STATUS_RMSK); + + /* Configure No pings */ + ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val); + u32p_replace_bits(&val, SWRM_DEF_CMD_NO_PINGS, SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK); + ctrl->reg_write(ctrl, SWRM_MCP_CFG_ADDR, val); + + /* Configure number of retries of a read/write cmd */ + ctrl->reg_write(ctrl, SWRM_CMD_FIFO_CFG_ADDR, SWRM_RD_WR_CMD_RETRIES); + + /* Set IRQ to PULSE */ + ctrl->reg_write(ctrl, SWRM_COMP_CFG_ADDR, + SWRM_COMP_CFG_IRQ_LEVEL_OR_PULSE_MSK | + SWRM_COMP_CFG_ENABLE_MSK); + + /* enable CPU IRQs */ + if (ctrl->mmio) { + ctrl->reg_write(ctrl, SWRM_INTERRUPT_CPU_EN, + SWRM_INTERRUPT_STATUS_RMSK); + } + return 0; +} + +static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus, + struct sdw_msg *msg) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + int ret, i, len; + + if (msg->flags == SDW_MSG_FLAG_READ) { + for (i = 0; i < msg->len;) { + if ((msg->len - i) < QCOM_SWRM_MAX_RD_LEN) + len = msg->len - i; + else + len = QCOM_SWRM_MAX_RD_LEN; + + ret = qcom_swrm_cmd_fifo_rd_cmd(ctrl, msg->dev_num, + msg->addr + i, len, + &msg->buf[i]); + if (ret) + return ret; + + i = i + len; + } + } else if (msg->flags == SDW_MSG_FLAG_WRITE) { + for (i = 0; i < msg->len; i++) { + ret = qcom_swrm_cmd_fifo_wr_cmd(ctrl, msg->buf[i], + msg->dev_num, + msg->addr + i); + if (ret) + return SDW_CMD_IGNORED; + } + } + + return SDW_CMD_OK; +} + +static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus) +{ + u32 reg = SWRM_MCP_FRAME_CTRL_BANK_ADDR(bus->params.next_bank); + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 val; + + ctrl->reg_read(ctrl, reg, &val); + + u32p_replace_bits(&val, ctrl->cols_index, SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK); + u32p_replace_bits(&val, ctrl->rows_index, SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK); + + return ctrl->reg_write(ctrl, reg, val); +} + +static int qcom_swrm_port_params(struct sdw_bus *bus, + struct sdw_port_params *p_params, + unsigned int bank) +{ + /* TBD */ + return 0; +} + +static int qcom_swrm_transport_params(struct sdw_bus *bus, + struct sdw_transport_params *params, + enum sdw_reg_bank bank) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 value; + int reg = SWRM_DP_PORT_CTRL_BANK((params->port_num), bank); + int ret; + + value = params->offset1 << SWRM_DP_PORT_CTRL_OFFSET1_SHFT; + value |= params->offset2 << SWRM_DP_PORT_CTRL_OFFSET2_SHFT; + value |= params->sample_interval - 1; + + ret = ctrl->reg_write(ctrl, reg, value); + + if (!ret && params->blk_pkg_mode) { + reg = SWRM_DP_BLOCK_CTRL3_BANK(params->port_num, bank); + + ret = ctrl->reg_write(ctrl, reg, 1); + } + + return ret; +} + +static int qcom_swrm_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, + unsigned int bank) +{ + u32 reg = SWRM_DP_PORT_CTRL_BANK(enable_ch->port_num, bank); + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + u32 val; + + ctrl->reg_read(ctrl, reg, &val); + + if (enable_ch->enable) + val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT); + else + val &= ~(0xff << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT); + + return ctrl->reg_write(ctrl, reg, val); +} + +static const struct sdw_master_port_ops qcom_swrm_port_ops = { + .dpn_set_port_params = qcom_swrm_port_params, + .dpn_set_port_transport_params = qcom_swrm_transport_params, + .dpn_port_enable_ch = qcom_swrm_port_enable, +}; + +static const struct sdw_master_ops qcom_swrm_ops = { + .xfer_msg = qcom_swrm_xfer_msg, + .pre_bank_switch = qcom_swrm_pre_bank_switch, +}; + +static int qcom_swrm_compute_params(struct sdw_bus *bus) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + struct qcom_swrm_port_config *pcfg; + int i = 0; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + pcfg = &ctrl->pconfig[p_rt->num - 1]; + p_rt->transport_params.port_num = p_rt->num; + p_rt->transport_params.sample_interval = pcfg->si + 1; + p_rt->transport_params.offset1 = pcfg->off1; + p_rt->transport_params.offset2 = pcfg->off2; + p_rt->transport_params.blk_pkg_mode = pcfg->bp_mode; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + pcfg = &ctrl->pconfig[i]; + p_rt->transport_params.port_num = p_rt->num; + p_rt->transport_params.sample_interval = + pcfg->si + 1; + p_rt->transport_params.offset1 = pcfg->off1; + p_rt->transport_params.offset2 = pcfg->off2; + p_rt->transport_params.blk_pkg_mode = pcfg->bp_mode; + i++; + } + } + } + + return 0; +} + +static u32 qcom_swrm_freq_tbl[MAX_FREQ_NUM] = { + DEFAULT_CLK_FREQ, +}; + +static void qcom_swrm_slave_wq(struct work_struct *work) +{ + struct qcom_swrm_ctrl *ctrl = + container_of(work, struct qcom_swrm_ctrl, slave_work); + + qcom_swrm_get_device_status(ctrl); + sdw_handle_slave_status(&ctrl->bus, ctrl->status); +} + + +static void qcom_swrm_stream_free_ports(struct qcom_swrm_ctrl *ctrl, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + unsigned long *port_mask; + + mutex_lock(&ctrl->port_lock); + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + if (m_rt->direction == SDW_DATA_DIR_RX) + port_mask = &ctrl->dout_port_mask; + else + port_mask = &ctrl->din_port_mask; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) + clear_bit(p_rt->num - 1, port_mask); + } + + mutex_unlock(&ctrl->port_lock); +} + +static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, + struct sdw_stream_runtime *stream, + struct snd_pcm_hw_params *params, + int direction) +{ + struct sdw_port_config pconfig[QCOM_SDW_MAX_PORTS]; + struct sdw_stream_config sconfig; + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + unsigned long *port_mask; + int i, maxport, pn, nports = 0, ret = 0; + + mutex_lock(&ctrl->port_lock); + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + if (m_rt->direction == SDW_DATA_DIR_RX) { + maxport = ctrl->num_dout_ports; + port_mask = &ctrl->dout_port_mask; + } else { + maxport = ctrl->num_din_ports; + port_mask = &ctrl->din_port_mask; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + /* Port numbers start from 1 - 14*/ + pn = find_first_zero_bit(port_mask, maxport); + if (pn > (maxport - 1)) { + dev_err(ctrl->dev, "All ports busy\n"); + ret = -EBUSY; + goto err; + } + set_bit(pn, port_mask); + pconfig[nports].num = pn + 1; + pconfig[nports].ch_mask = p_rt->ch_mask; + nports++; + } + } + } + + if (direction == SNDRV_PCM_STREAM_CAPTURE) + sconfig.direction = SDW_DATA_DIR_TX; + else + sconfig.direction = SDW_DATA_DIR_RX; + + /* hw parameters wil be ignored as we only support PDM */ + sconfig.ch_count = 1; + sconfig.frame_rate = params_rate(params); + sconfig.type = stream->type; + sconfig.bps = 1; + sdw_stream_add_master(&ctrl->bus, &sconfig, pconfig, + nports, stream); +err: + if (ret) { + for (i = 0; i < nports; i++) + clear_bit(pconfig[i].num - 1, port_mask); + } + + mutex_unlock(&ctrl->port_lock); + + return ret; +} + +static int qcom_swrm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id]; + int ret; + + ret = qcom_swrm_stream_alloc_ports(ctrl, sruntime, params, + substream->stream); + if (ret) + qcom_swrm_stream_free_ports(ctrl, sruntime); + + return ret; +} + +static int qcom_swrm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct sdw_stream_runtime *sruntime = ctrl->sruntime[dai->id]; + + qcom_swrm_stream_free_ports(ctrl, sruntime); + sdw_stream_remove_master(&ctrl->bus, sruntime); + + return 0; +} + +static int qcom_swrm_set_sdw_stream(struct snd_soc_dai *dai, + void *stream, int direction) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + ctrl->sruntime[dai->id] = stream; + + return 0; +} + +static void *qcom_swrm_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + return ctrl->sruntime[dai->id]; +} + +static int qcom_swrm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sruntime; + struct snd_soc_dai *codec_dai; + int ret, i; + + sruntime = sdw_alloc_stream(dai->name); + if (!sruntime) + return -ENOMEM; + + ctrl->sruntime[dai->id] = sruntime; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + ret = snd_soc_dai_set_stream(codec_dai, sruntime, + substream->stream); + if (ret < 0 && ret != -ENOTSUPP) { + dev_err(dai->dev, "Failed to set sdw stream on %s", + codec_dai->name); + sdw_release_stream(sruntime); + return ret; + } + } + + return 0; +} + +static void qcom_swrm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + + sdw_release_stream(ctrl->sruntime[dai->id]); + ctrl->sruntime[dai->id] = NULL; +} + +static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = { + .hw_params = qcom_swrm_hw_params, + .hw_free = qcom_swrm_hw_free, + .startup = qcom_swrm_startup, + .shutdown = qcom_swrm_shutdown, + .set_stream = qcom_swrm_set_sdw_stream, + .get_stream = qcom_swrm_get_sdw_stream, +}; + +static const struct snd_soc_component_driver qcom_swrm_dai_component = { + .name = "soundwire", +}; + +static int qcom_swrm_register_dais(struct qcom_swrm_ctrl *ctrl) +{ + int num_dais = ctrl->num_dout_ports + ctrl->num_din_ports; + struct snd_soc_dai_driver *dais; + struct snd_soc_pcm_stream *stream; + struct device *dev = ctrl->dev; + int i; + + /* PDM dais are only tested for now */ + dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + + for (i = 0; i < num_dais; i++) { + dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW Pin%d", i); + if (!dais[i].name) + return -ENOMEM; + + if (i < ctrl->num_dout_ports) + stream = &dais[i].playback; + else + stream = &dais[i].capture; + + stream->channels_min = 1; + stream->channels_max = 1; + stream->rates = SNDRV_PCM_RATE_48000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE; + + dais[i].ops = &qcom_swrm_pdm_dai_ops; + dais[i].id = i; + } + + return devm_snd_soc_register_component(ctrl->dev, + &qcom_swrm_dai_component, + dais, num_dais); +} + +static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl) +{ + struct device_node *np = ctrl->dev->of_node; + u8 off1[QCOM_SDW_MAX_PORTS]; + u8 off2[QCOM_SDW_MAX_PORTS]; + u8 si[QCOM_SDW_MAX_PORTS]; + u8 bp_mode[QCOM_SDW_MAX_PORTS] = { 0, }; + int i, ret, nports, val; + + ctrl->reg_read(ctrl, SWRM_COMP_PARAMS, &val); + + ctrl->num_dout_ports = FIELD_GET(SWRM_COMP_PARAMS_DOUT_PORTS_MASK, val); + ctrl->num_din_ports = FIELD_GET(SWRM_COMP_PARAMS_DIN_PORTS_MASK, val); + + ret = of_property_read_u32(np, "qcom,din-ports", &val); + if (ret) + return ret; + + if (val > ctrl->num_din_ports) + return -EINVAL; + + ctrl->num_din_ports = val; + + ret = of_property_read_u32(np, "qcom,dout-ports", &val); + if (ret) + return ret; + + if (val > ctrl->num_dout_ports) + return -EINVAL; + + ctrl->num_dout_ports = val; + + nports = ctrl->num_dout_ports + ctrl->num_din_ports; + + ret = of_property_read_u8_array(np, "qcom,ports-offset1", + off1, nports); + if (ret) + return ret; + + ret = of_property_read_u8_array(np, "qcom,ports-offset2", + off2, nports); + if (ret) + return ret; + + ret = of_property_read_u8_array(np, "qcom,ports-sinterval-low", + si, nports); + if (ret) + return ret; + + ret = of_property_read_u8_array(np, "qcom,ports-block-pack-mode", + bp_mode, nports); + for (i = 0; i < nports; i++) { + ctrl->pconfig[i].si = si[i]; + ctrl->pconfig[i].off1 = off1[i]; + ctrl->pconfig[i].off2 = off2[i]; + ctrl->pconfig[i].bp_mode = bp_mode[i]; + } + + return 0; +} + +static int qcom_swrm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sdw_master_prop *prop; + struct sdw_bus_params *params; + struct qcom_swrm_ctrl *ctrl; + const struct qcom_swrm_data *data; + int ret; + u32 val; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + data = of_device_get_match_data(dev); + ctrl->rows_index = sdw_find_row_index(data->default_rows); + ctrl->cols_index = sdw_find_col_index(data->default_cols); +#if IS_REACHABLE(CONFIG_SLIMBUS) + if (dev->parent->bus == &slimbus_bus) { +#else + if (false) { +#endif + ctrl->reg_read = qcom_swrm_ahb_reg_read; + ctrl->reg_write = qcom_swrm_ahb_reg_write; + ctrl->regmap = dev_get_regmap(dev->parent, NULL); + if (!ctrl->regmap) + return -EINVAL; + } else { + ctrl->reg_read = qcom_swrm_cpu_reg_read; + ctrl->reg_write = qcom_swrm_cpu_reg_write; + ctrl->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->mmio)) + return PTR_ERR(ctrl->mmio); + } + + ctrl->irq = of_irq_get(dev->of_node, 0); + if (ctrl->irq < 0) { + ret = ctrl->irq; + goto err_init; + } + + ctrl->hclk = devm_clk_get(dev, "iface"); + if (IS_ERR(ctrl->hclk)) { + ret = PTR_ERR(ctrl->hclk); + goto err_init; + } + + clk_prepare_enable(ctrl->hclk); + + ctrl->dev = dev; + dev_set_drvdata(&pdev->dev, ctrl); + spin_lock_init(&ctrl->comp_lock); + mutex_init(&ctrl->port_lock); + INIT_WORK(&ctrl->slave_work, qcom_swrm_slave_wq); + + ctrl->bus.ops = &qcom_swrm_ops; + ctrl->bus.port_ops = &qcom_swrm_port_ops; + ctrl->bus.compute_params = &qcom_swrm_compute_params; + + ret = qcom_swrm_get_port_config(ctrl); + if (ret) + goto err_clk; + + params = &ctrl->bus.params; + params->max_dr_freq = DEFAULT_CLK_FREQ; + params->curr_dr_freq = DEFAULT_CLK_FREQ; + params->col = data->default_cols; + params->row = data->default_rows; + ctrl->reg_read(ctrl, SWRM_MCP_STATUS, &val); + params->curr_bank = val & SWRM_MCP_STATUS_BANK_NUM_MASK; + params->next_bank = !params->curr_bank; + + prop = &ctrl->bus.prop; + prop->max_clk_freq = DEFAULT_CLK_FREQ; + prop->num_clk_gears = 0; + prop->num_clk_freq = MAX_FREQ_NUM; + prop->clk_freq = &qcom_swrm_freq_tbl[0]; + prop->default_col = data->default_cols; + prop->default_row = data->default_rows; + + ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version); + + ret = devm_request_threaded_irq(dev, ctrl->irq, NULL, + qcom_swrm_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_ONESHOT, + "soundwire", ctrl); + if (ret) { + dev_err(dev, "Failed to request soundwire irq\n"); + goto err_clk; + } + + ret = sdw_bus_master_add(&ctrl->bus, dev, dev->fwnode); + if (ret) { + dev_err(dev, "Failed to register Soundwire controller (%d)\n", + ret); + goto err_clk; + } + + qcom_swrm_init(ctrl); + ret = qcom_swrm_register_dais(ctrl); + if (ret) + goto err_master_add; + + dev_info(dev, "Qualcomm Soundwire controller v%x.%x.%x Registered\n", + (ctrl->version >> 24) & 0xff, (ctrl->version >> 16) & 0xff, + ctrl->version & 0xffff); + + return 0; + +err_master_add: + sdw_bus_master_delete(&ctrl->bus); +err_clk: + clk_disable_unprepare(ctrl->hclk); +err_init: + return ret; +} + +static int qcom_swrm_remove(struct platform_device *pdev) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev); + + sdw_bus_master_delete(&ctrl->bus); + clk_disable_unprepare(ctrl->hclk); + + return 0; +} + +static const struct of_device_id qcom_swrm_of_match[] = { + { .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data }, + { .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data }, + {/* sentinel */}, +}; + +MODULE_DEVICE_TABLE(of, qcom_swrm_of_match); + +static struct platform_driver qcom_swrm_driver = { + .probe = &qcom_swrm_probe, + .remove = &qcom_swrm_remove, + .driver = { + .name = "qcom-soundwire", + .of_match_table = qcom_swrm_of_match, + } +}; +module_platform_driver(qcom_swrm_driver); + +MODULE_DESCRIPTION("Qualcomm soundwire driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c new file mode 100644 index 000000000..a08f4081c --- /dev/null +++ b/drivers/soundwire/slave.c @@ -0,0 +1,243 @@ +// 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 "bus.h" +#include "sysfs_local.h" + +static void sdw_slave_release(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + kfree(slave); +} + +struct device_type sdw_slave_type = { + .name = "sdw_slave", + .release = sdw_slave_release, + .uevent = sdw_slave_uevent, +}; + +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(sizeof(*slave), GFP_KERNEL); + 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:link:mfg:part:class */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x", + bus->link_id, id->mfg_id, id->part_id, + id->class_id); + } else { + /* name shall be sdw:link:mfg:part:class:unique */ + dev_set_name(&slave->dev, "sdw:%x:%x:%x:%x:%x", + 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; + init_completion(&slave->probe_complete); + slave->probed = false; + slave->first_interrupt_done = false; + + 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); + + 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; +} + +#if IS_ENABLED(CONFIG_ACPI) + +static bool find_slave(struct sdw_bus *bus, + struct acpi_device *adev, + struct sdw_slave_id *id) +{ + unsigned long long addr; + unsigned int link_id; + acpi_status status; + + status = acpi_evaluate_integer(adev->handle, + METHOD_NAME__ADR, NULL, &addr); + + if (ACPI_FAILURE(status)) { + dev_err(bus->dev, "_ADR resolution failed: %x\n", + status); + 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; +} + +/* + * 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 *adev, *parent; + struct acpi_device *adev2, *parent2; + + parent = ACPI_COMPANION(bus->dev); + if (!parent) { + dev_err(bus->dev, "Can't find parent for acpi bind\n"); + return -ENODEV; + } + + list_for_each_entry(adev, &parent->children, node) { + struct sdw_slave_id id; + struct sdw_slave_id id2; + bool ignore_unique_id = true; + + if (!find_slave(bus, adev, &id)) + continue; + + /* brute-force O(N^2) search for duplicates */ + parent2 = parent; + list_for_each_entry(adev2, &parent2->children, node) { + + if (adev == adev2) + continue; + + if (!find_slave(bus, adev2, &id2)) + continue; + + if (id.sdw_version != id2.sdw_version || + id.mfg_id != id2.mfg_id || + id.part_id != id2.part_id || + id.class_id != id2.class_id) + continue; + + if (id.unique_id != id2.unique_id) { + dev_dbg(bus->dev, + "Valid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + ignore_unique_id = false; + } else { + dev_err(bus->dev, + "Invalid unique IDs %x %x for Slave mfg %x part %d\n", + id.unique_id, id2.unique_id, + id.mfg_id, id.part_id); + return -ENODEV; + } + } + + if (ignore_unique_id) + id.unique_id = SDW_IGNORED_UNIQUE_ID; + + /* + * don't error check for sdw_slave_add as we want to continue + * adding Slaves + */ + sdw_slave_add(bus, &id, acpi_fwnode_handle(adev)); + } + + return 0; +} + +#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; + + compat = of_get_property(node, "compatible", NULL); + if (!compat) + 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; +} diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c new file mode 100644 index 000000000..a377c3d02 --- /dev/null +++ b/drivers/soundwire/stream.c @@ -0,0 +1,1949 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2015-18 Intel Corporation. + +/* + * stream.c - SoundWire Bus stream operations. + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/soundwire/sdw.h> +#include <sound/soc.h> +#include "bus.h" + +/* + * Array of supported rows and columns as per MIPI SoundWire Specification 1.1 + * + * The rows are arranged as per the array index value programmed + * in register. The index 15 has dummy value 0 in order to fill hole. + */ +int sdw_rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147, + 96, 100, 120, 128, 150, 160, 250, 0, + 192, 200, 240, 256, 72, 144, 90, 180}; +EXPORT_SYMBOL(sdw_rows); + +int sdw_cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16}; +EXPORT_SYMBOL(sdw_cols); + +int sdw_find_col_index(int col) +{ + int i; + + for (i = 0; i < SDW_FRAME_COLS; i++) { + if (sdw_cols[i] == col) + return i; + } + + pr_warn("Requested column not found, selecting lowest column no: 2\n"); + return 0; +} +EXPORT_SYMBOL(sdw_find_col_index); + +int sdw_find_row_index(int row) +{ + int i; + + for (i = 0; i < SDW_FRAME_ROWS; i++) { + if (sdw_rows[i] == row) + return i; + } + + pr_warn("Requested row not found, selecting lowest row no: 48\n"); + return 0; +} +EXPORT_SYMBOL(sdw_find_row_index); + +static int _sdw_program_slave_port_params(struct sdw_bus *bus, + struct sdw_slave *slave, + struct sdw_transport_params *t_params, + enum sdw_dpn_type type) +{ + u32 addr1, addr2, addr3, addr4; + int ret; + u16 wbuf; + + if (bus->params.next_bank) { + addr1 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num); + addr3 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num); + addr4 = SDW_DPN_HCTRL_B1(t_params->port_num); + } else { + addr1 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num); + addr3 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num); + addr4 = SDW_DPN_HCTRL_B0(t_params->port_num); + } + + /* Program DPN_OffsetCtrl2 registers */ + ret = sdw_write(slave, addr1, t_params->offset2); + if (ret < 0) { + dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed\n"); + return ret; + } + + /* Program DPN_BlockCtrl3 register */ + ret = sdw_write(slave, addr2, t_params->blk_pkg_mode); + if (ret < 0) { + dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n"); + return ret; + } + + /* + * Data ports are FULL, SIMPLE and REDUCED. This function handles + * FULL and REDUCED only and beyond this point only FULL is + * handled, so bail out if we are not FULL data port type + */ + if (type != SDW_DPN_FULL) + return ret; + + /* Program DPN_SampleCtrl2 register */ + wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, t_params->sample_interval - 1); + + ret = sdw_write(slave, addr3, wbuf); + if (ret < 0) { + dev_err(bus->dev, "DPN_SampleCtrl2 register write failed\n"); + return ret; + } + + /* Program DPN_HCtrl register */ + wbuf = FIELD_PREP(SDW_DPN_HCTRL_HSTART, t_params->hstart); + wbuf |= FIELD_PREP(SDW_DPN_HCTRL_HSTOP, t_params->hstop); + + ret = sdw_write(slave, addr4, wbuf); + if (ret < 0) + dev_err(bus->dev, "DPN_HCtrl register write failed\n"); + + return ret; +} + +static int sdw_program_slave_port_params(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_port_params *p_params = &p_rt->port_params; + struct sdw_slave_prop *slave_prop = &s_rt->slave->prop; + u32 addr1, addr2, addr3, addr4, addr5, addr6; + struct sdw_dpn_prop *dpn_prop; + int ret; + u8 wbuf; + + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, + s_rt->direction, + t_params->port_num); + if (!dpn_prop) + return -EINVAL; + + addr1 = SDW_DPN_PORTCTRL(t_params->port_num); + addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num); + + if (bus->params.next_bank) { + addr3 = SDW_DPN_SAMPLECTRL1_B1(t_params->port_num); + addr4 = SDW_DPN_OFFSETCTRL1_B1(t_params->port_num); + addr5 = SDW_DPN_BLOCKCTRL2_B1(t_params->port_num); + addr6 = SDW_DPN_LANECTRL_B1(t_params->port_num); + + } else { + addr3 = SDW_DPN_SAMPLECTRL1_B0(t_params->port_num); + addr4 = SDW_DPN_OFFSETCTRL1_B0(t_params->port_num); + addr5 = SDW_DPN_BLOCKCTRL2_B0(t_params->port_num); + addr6 = SDW_DPN_LANECTRL_B0(t_params->port_num); + } + + /* Program DPN_PortCtrl register */ + wbuf = FIELD_PREP(SDW_DPN_PORTCTRL_DATAMODE, p_params->data_mode); + wbuf |= FIELD_PREP(SDW_DPN_PORTCTRL_FLOWMODE, p_params->flow_mode); + + ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_PortCtrl register write failed for port %d\n", + t_params->port_num); + return ret; + } + + if (!dpn_prop->read_only_wordlength) { + /* Program DPN_BlockCtrl1 register */ + ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1)); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_BlockCtrl1 register write failed for port %d\n", + t_params->port_num); + return ret; + } + } + + /* Program DPN_SampleCtrl1 register */ + wbuf = (t_params->sample_interval - 1) & SDW_DPN_SAMPLECTRL_LOW; + ret = sdw_write(s_rt->slave, addr3, wbuf); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_SampleCtrl1 register write failed for port %d\n", + t_params->port_num); + return ret; + } + + /* Program DPN_OffsetCtrl1 registers */ + ret = sdw_write(s_rt->slave, addr4, t_params->offset1); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_OffsetCtrl1 register write failed for port %d\n", + t_params->port_num); + return ret; + } + + /* Program DPN_BlockCtrl2 register*/ + if (t_params->blk_grp_ctrl_valid) { + ret = sdw_write(s_rt->slave, addr5, t_params->blk_grp_ctrl); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_BlockCtrl2 reg write failed for port %d\n", + t_params->port_num); + return ret; + } + } + + /* program DPN_LaneCtrl register */ + if (slave_prop->lane_control_support) { + ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "DPN_LaneCtrl register write failed for port %d\n", + t_params->port_num); + return ret; + } + } + + if (dpn_prop->type != SDW_DPN_SIMPLE) { + ret = _sdw_program_slave_port_params(bus, s_rt->slave, + t_params, dpn_prop->type); + if (ret < 0) + dev_err(&s_rt->slave->dev, + "Transport reg write failed for port: %d\n", + t_params->port_num); + } + + return ret; +} + +static int sdw_program_master_port_params(struct sdw_bus *bus, + struct sdw_port_runtime *p_rt) +{ + int ret; + + /* + * we need to set transport and port parameters for the port. + * Transport parameters refers to the sample interval, offsets and + * hstart/stop etc of the data. Port parameters refers to word + * length, flow mode etc of the port + */ + ret = bus->port_ops->dpn_set_port_transport_params(bus, + &p_rt->transport_params, + bus->params.next_bank); + if (ret < 0) + return ret; + + return bus->port_ops->dpn_set_port_params(bus, + &p_rt->port_params, + bus->params.next_bank); +} + +/** + * sdw_program_port_params() - Programs transport parameters of Master(s) + * and Slave(s) + * + * @m_rt: Master stream runtime + */ +static int sdw_program_port_params(struct sdw_master_runtime *m_rt) +{ + struct sdw_slave_runtime *s_rt = NULL; + struct sdw_bus *bus = m_rt->bus; + struct sdw_port_runtime *p_rt; + int ret = 0; + + /* Program transport & port parameters for Slave(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ret = sdw_program_slave_port_params(bus, s_rt, p_rt); + if (ret < 0) + return ret; + } + } + + /* Program transport & port parameters for Master(s) */ + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + ret = sdw_program_master_port_params(bus, p_rt); + if (ret < 0) + return ret; + } + + return 0; +} + +/** + * sdw_enable_disable_slave_ports: Enable/disable slave data port + * + * @bus: bus instance + * @s_rt: slave runtime + * @p_rt: port runtime + * @en: enable or disable operation + * + * This function only sets the enable/disable bits in the relevant bank, the + * actual enable/disable is done with a bank switch + */ +static int sdw_enable_disable_slave_ports(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt, + bool en) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + u32 addr; + int ret; + + if (bus->params.next_bank) + addr = SDW_DPN_CHANNELEN_B1(p_rt->num); + else + addr = SDW_DPN_CHANNELEN_B0(p_rt->num); + + /* + * Since bus doesn't support sharing a port across two streams, + * it is safe to reset this register + */ + if (en) + ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask); + else + ret = sdw_write(s_rt->slave, addr, 0x0); + + if (ret < 0) + dev_err(&s_rt->slave->dev, + "Slave chn_en reg write failed:%d port:%d\n", + ret, t_params->port_num); + + return ret; +} + +static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_port_runtime *p_rt, + bool en) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_bus *bus = m_rt->bus; + struct sdw_enable_ch enable_ch; + int ret; + + enable_ch.port_num = p_rt->num; + enable_ch.ch_mask = p_rt->ch_mask; + enable_ch.enable = en; + + /* Perform Master port channel(s) enable/disable */ + if (bus->port_ops->dpn_port_enable_ch) { + ret = bus->port_ops->dpn_port_enable_ch(bus, + &enable_ch, + bus->params.next_bank); + if (ret < 0) { + dev_err(bus->dev, + "Master chn_en write failed:%d port:%d\n", + ret, t_params->port_num); + return ret; + } + } else { + dev_err(bus->dev, + "dpn_port_enable_ch not supported, %s failed\n", + en ? "enable" : "disable"); + return -EINVAL; + } + + return 0; +} + +/** + * sdw_enable_disable_ports() - Enable/disable port(s) for Master and + * Slave(s) + * + * @m_rt: Master stream runtime + * @en: mode (enable/disable) + */ +static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en) +{ + struct sdw_port_runtime *s_port, *m_port; + struct sdw_slave_runtime *s_rt; + int ret = 0; + + /* Enable/Disable Slave port(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(s_port, &s_rt->port_list, port_node) { + ret = sdw_enable_disable_slave_ports(m_rt->bus, s_rt, + s_port, en); + if (ret < 0) + return ret; + } + } + + /* Enable/Disable Master port(s) */ + list_for_each_entry(m_port, &m_rt->port_list, port_node) { + ret = sdw_enable_disable_master_ports(m_rt, m_port, en); + if (ret < 0) + return ret; + } + + return 0; +} + +static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt, + struct sdw_prepare_ch prep_ch, + enum sdw_port_prep_ops cmd) +{ + const struct sdw_slave_ops *ops = s_rt->slave->ops; + int ret; + + if (ops->port_prep) { + ret = ops->port_prep(s_rt->slave, &prep_ch, cmd); + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "Slave Port Prep cmd %d failed: %d\n", + cmd, ret); + return ret; + } + } + + return 0; +} + +static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, + struct sdw_slave_runtime *s_rt, + struct sdw_port_runtime *p_rt, + bool prep) +{ + struct completion *port_ready; + struct sdw_dpn_prop *dpn_prop; + struct sdw_prepare_ch prep_ch; + bool intr = false; + int ret = 0, val; + u32 addr; + + prep_ch.num = p_rt->num; + prep_ch.ch_mask = p_rt->ch_mask; + + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, + s_rt->direction, + prep_ch.num); + if (!dpn_prop) { + dev_err(bus->dev, + "Slave Port:%d properties not found\n", prep_ch.num); + return -EINVAL; + } + + prep_ch.prepare = prep; + + prep_ch.bank = bus->params.next_bank; + + if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm || + bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) + intr = true; + + /* + * Enable interrupt before Port prepare. + * For Port de-prepare, it is assumed that port + * was prepared earlier + */ + if (prep && intr) { + ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, + dpn_prop->imp_def_interrupts); + if (ret < 0) + return ret; + } + + /* Inform slave about the impending port prepare */ + sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_PRE_PREP); + + /* Prepare Slave port implementing CP_SM */ + if (!dpn_prop->simple_ch_prep_sm) { + addr = SDW_DPN_PREPARECTRL(p_rt->num); + + if (prep) + ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask); + else + ret = sdw_write(s_rt->slave, addr, 0x0); + + if (ret < 0) { + dev_err(&s_rt->slave->dev, + "Slave prep_ctrl reg write failed\n"); + return ret; + } + + /* Wait for completion on port ready */ + port_ready = &s_rt->slave->port_ready[prep_ch.num]; + wait_for_completion_timeout(port_ready, + msecs_to_jiffies(dpn_prop->ch_prep_timeout)); + + val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num)); + if ((val < 0) || (val & p_rt->ch_mask)) { + ret = (val < 0) ? val : -ETIMEDOUT; + dev_err(&s_rt->slave->dev, + "Chn prep failed for port %d: %d\n", prep_ch.num, ret); + return ret; + } + } + + /* Inform slaves about ports prepared */ + sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_POST_PREP); + + /* Disable interrupt after Port de-prepare */ + if (!prep && intr) + ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, + dpn_prop->imp_def_interrupts); + + return ret; +} + +static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt, + struct sdw_port_runtime *p_rt, + bool prep) +{ + struct sdw_transport_params *t_params = &p_rt->transport_params; + struct sdw_bus *bus = m_rt->bus; + const struct sdw_master_port_ops *ops = bus->port_ops; + struct sdw_prepare_ch prep_ch; + int ret = 0; + + prep_ch.num = p_rt->num; + prep_ch.ch_mask = p_rt->ch_mask; + prep_ch.prepare = prep; /* Prepare/De-prepare */ + prep_ch.bank = bus->params.next_bank; + + /* Pre-prepare/Pre-deprepare port(s) */ + if (ops->dpn_port_prep) { + ret = ops->dpn_port_prep(bus, &prep_ch); + if (ret < 0) { + dev_err(bus->dev, "Port prepare failed for port:%d\n", + t_params->port_num); + return ret; + } + } + + return ret; +} + +/** + * sdw_prep_deprep_ports() - Prepare/De-prepare port(s) for Master(s) and + * Slave(s) + * + * @m_rt: Master runtime handle + * @prep: Prepare or De-prepare + */ +static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep) +{ + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + int ret = 0; + + /* Prepare/De-prepare Slave port(s) */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + ret = sdw_prep_deprep_slave_ports(m_rt->bus, s_rt, + p_rt, prep); + if (ret < 0) + return ret; + } + } + + /* Prepare/De-prepare Master port(s) */ + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + ret = sdw_prep_deprep_master_ports(m_rt, p_rt, prep); + if (ret < 0) + return ret; + } + + return ret; +} + +/** + * sdw_notify_config() - Notify bus configuration + * + * @m_rt: Master runtime handle + * + * This function notifies the Master(s) and Slave(s) of the + * new bus configuration. + */ +static int sdw_notify_config(struct sdw_master_runtime *m_rt) +{ + struct sdw_slave_runtime *s_rt; + struct sdw_bus *bus = m_rt->bus; + struct sdw_slave *slave; + int ret = 0; + + if (bus->ops->set_bus_conf) { + ret = bus->ops->set_bus_conf(bus, &bus->params); + if (ret < 0) + return ret; + } + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + slave = s_rt->slave; + + if (slave->ops->bus_config) { + ret = slave->ops->bus_config(slave, &bus->params); + if (ret < 0) { + dev_err(bus->dev, "Notify Slave: %d failed\n", + slave->dev_num); + return ret; + } + } + } + + return ret; +} + +/** + * sdw_program_params() - Program transport and port parameters for Master(s) + * and Slave(s) + * + * @bus: SDW bus instance + * @prepare: true if sdw_program_params() is called by _prepare. + */ +static int sdw_program_params(struct sdw_bus *bus, bool prepare) +{ + struct sdw_master_runtime *m_rt; + int ret = 0; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + + /* + * this loop walks through all master runtimes for a + * bus, but the ports can only be configured while + * explicitly preparing a stream or handling an + * already-prepared stream otherwise. + */ + if (!prepare && + m_rt->stream->state == SDW_STREAM_CONFIGURED) + continue; + + ret = sdw_program_port_params(m_rt); + if (ret < 0) { + dev_err(bus->dev, + "Program transport params failed: %d\n", ret); + return ret; + } + + ret = sdw_notify_config(m_rt); + if (ret < 0) { + dev_err(bus->dev, + "Notify bus config failed: %d\n", ret); + return ret; + } + + /* Enable port(s) on alternate bank for all active streams */ + if (m_rt->stream->state != SDW_STREAM_ENABLED) + continue; + + ret = sdw_enable_disable_ports(m_rt, true); + if (ret < 0) { + dev_err(bus->dev, "Enable channel failed: %d\n", ret); + return ret; + } + } + + return ret; +} + +static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count) +{ + int col_index, row_index; + bool multi_link; + struct sdw_msg *wr_msg; + u8 *wbuf; + int ret; + u16 addr; + + wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL); + if (!wr_msg) + return -ENOMEM; + + bus->defer_msg.msg = wr_msg; + + wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL); + if (!wbuf) { + ret = -ENOMEM; + goto error_1; + } + + /* Get row and column index to program register */ + col_index = sdw_find_col_index(bus->params.col); + row_index = sdw_find_row_index(bus->params.row); + wbuf[0] = col_index | (row_index << 3); + + if (bus->params.next_bank) + addr = SDW_SCP_FRAMECTRL_B1; + else + addr = SDW_SCP_FRAMECTRL_B0; + + sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM, + SDW_MSG_FLAG_WRITE, wbuf); + wr_msg->ssp_sync = true; + + /* + * Set the multi_link flag only when both the hardware supports + * and hardware-based sync is required + */ + multi_link = bus->multi_link && (m_rt_count >= bus->hw_sync_min_links); + + if (multi_link) + ret = sdw_transfer_defer(bus, wr_msg, &bus->defer_msg); + else + ret = sdw_transfer(bus, wr_msg); + + if (ret < 0) { + dev_err(bus->dev, "Slave frame_ctrl reg write failed\n"); + goto error; + } + + if (!multi_link) { + kfree(wr_msg); + kfree(wbuf); + bus->defer_msg.msg = NULL; + bus->params.curr_bank = !bus->params.curr_bank; + bus->params.next_bank = !bus->params.next_bank; + } + + return 0; + +error: + kfree(wbuf); +error_1: + kfree(wr_msg); + bus->defer_msg.msg = NULL; + return ret; +} + +/** + * sdw_ml_sync_bank_switch: Multilink register bank switch + * + * @bus: SDW bus instance + * @multi_link: whether this is a multi-link stream with hardware-based sync + * + * Caller function should free the buffers on error + */ +static int sdw_ml_sync_bank_switch(struct sdw_bus *bus, bool multi_link) +{ + unsigned long time_left; + + if (!multi_link) + return 0; + + /* Wait for completion of transfer */ + time_left = wait_for_completion_timeout(&bus->defer_msg.complete, + bus->bank_switch_timeout); + + if (!time_left) { + dev_err(bus->dev, "Controller Timed out on bank switch\n"); + return -ETIMEDOUT; + } + + bus->params.curr_bank = !bus->params.curr_bank; + bus->params.next_bank = !bus->params.next_bank; + + if (bus->defer_msg.msg) { + kfree(bus->defer_msg.msg->buf); + kfree(bus->defer_msg.msg); + } + + return 0; +} + +static int do_bank_switch(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + const struct sdw_master_ops *ops; + struct sdw_bus *bus; + bool multi_link = false; + int m_rt_count; + int ret = 0; + + m_rt_count = stream->m_rt_count; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + ops = bus->ops; + + if (bus->multi_link && m_rt_count >= bus->hw_sync_min_links) { + multi_link = true; + mutex_lock(&bus->msg_lock); + } + + /* Pre-bank switch */ + if (ops->pre_bank_switch) { + ret = ops->pre_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, + "Pre bank switch op failed: %d\n", ret); + goto msg_unlock; + } + } + + /* + * Perform Bank switch operation. + * For multi link cases, the actual bank switch is + * synchronized across all Masters and happens later as a + * part of post_bank_switch ops. + */ + ret = sdw_bank_switch(bus, m_rt_count); + if (ret < 0) { + dev_err(bus->dev, "Bank switch failed: %d\n", ret); + goto error; + } + } + + /* + * For multi link cases, it is expected that the bank switch is + * triggered by the post_bank_switch for the first Master in the list + * and for the other Masters the post_bank_switch() should return doing + * nothing. + */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + ops = bus->ops; + + /* Post-bank switch */ + if (ops->post_bank_switch) { + ret = ops->post_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, + "Post bank switch op failed: %d\n", + ret); + goto error; + } + } else if (multi_link) { + dev_err(bus->dev, + "Post bank switch ops not implemented\n"); + goto error; + } + + /* Set the bank switch timeout to default, if not set */ + if (!bus->bank_switch_timeout) + bus->bank_switch_timeout = DEFAULT_BANK_SWITCH_TIMEOUT; + + /* Check if bank switch was successful */ + ret = sdw_ml_sync_bank_switch(bus, multi_link); + if (ret < 0) { + dev_err(bus->dev, + "multi link bank switch failed: %d\n", ret); + goto error; + } + + if (multi_link) + mutex_unlock(&bus->msg_lock); + } + + return ret; + +error: + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + if (bus->defer_msg.msg) { + kfree(bus->defer_msg.msg->buf); + kfree(bus->defer_msg.msg); + } + } + +msg_unlock: + + if (multi_link) { + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + if (mutex_is_locked(&bus->msg_lock)) + mutex_unlock(&bus->msg_lock); + } + } + + return ret; +} + +/** + * sdw_release_stream() - Free the assigned stream runtime + * + * @stream: SoundWire stream runtime + * + * sdw_release_stream should be called only once per stream + */ +void sdw_release_stream(struct sdw_stream_runtime *stream) +{ + kfree(stream); +} +EXPORT_SYMBOL(sdw_release_stream); + +/** + * sdw_alloc_stream() - Allocate and return stream runtime + * + * @stream_name: SoundWire stream name + * + * Allocates a SoundWire stream runtime instance. + * sdw_alloc_stream should be called only once per stream. Typically + * invoked from ALSA/ASoC machine/platform driver. + */ +struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name) +{ + struct sdw_stream_runtime *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return NULL; + + stream->name = stream_name; + INIT_LIST_HEAD(&stream->master_list); + stream->state = SDW_STREAM_ALLOCATED; + stream->m_rt_count = 0; + + return stream; +} +EXPORT_SYMBOL(sdw_alloc_stream); + +static struct sdw_master_runtime +*sdw_find_master_rt(struct sdw_bus *bus, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + + /* Retrieve Bus handle if already available */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + if (m_rt->bus == bus) + return m_rt; + } + + return NULL; +} + +/** + * sdw_alloc_master_rt() - Allocates and initialize Master runtime handle + * + * @bus: SDW bus instance + * @stream_config: Stream configuration + * @stream: Stream runtime handle. + * + * This function is to be called with bus_lock held. + */ +static struct sdw_master_runtime +*sdw_alloc_master_rt(struct sdw_bus *bus, + struct sdw_stream_config *stream_config, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + + /* + * check if Master is already allocated (as a result of Slave adding + * it first), if so skip allocation and go to configure + */ + m_rt = sdw_find_master_rt(bus, stream); + if (m_rt) + goto stream_config; + + m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL); + if (!m_rt) + return NULL; + + /* Initialization of Master runtime handle */ + INIT_LIST_HEAD(&m_rt->port_list); + INIT_LIST_HEAD(&m_rt->slave_rt_list); + list_add_tail(&m_rt->stream_node, &stream->master_list); + + list_add_tail(&m_rt->bus_node, &bus->m_rt_list); + +stream_config: + m_rt->ch_count = stream_config->ch_count; + m_rt->bus = bus; + m_rt->stream = stream; + m_rt->direction = stream_config->direction; + + return m_rt; +} + +/** + * sdw_alloc_slave_rt() - Allocate and initialize Slave runtime handle. + * + * @slave: Slave handle + * @stream_config: Stream configuration + * @stream: Stream runtime handle + * + * This function is to be called with bus_lock held. + */ +static struct sdw_slave_runtime +*sdw_alloc_slave_rt(struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + struct sdw_stream_runtime *stream) +{ + struct sdw_slave_runtime *s_rt; + + s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL); + if (!s_rt) + return NULL; + + INIT_LIST_HEAD(&s_rt->port_list); + s_rt->ch_count = stream_config->ch_count; + s_rt->direction = stream_config->direction; + s_rt->slave = slave; + + return s_rt; +} + +static void sdw_master_port_release(struct sdw_bus *bus, + struct sdw_master_runtime *m_rt) +{ + struct sdw_port_runtime *p_rt, *_p_rt; + + list_for_each_entry_safe(p_rt, _p_rt, &m_rt->port_list, port_node) { + list_del(&p_rt->port_node); + kfree(p_rt); + } +} + +static void sdw_slave_port_release(struct sdw_bus *bus, + struct sdw_slave *slave, + struct sdw_stream_runtime *stream) +{ + struct sdw_port_runtime *p_rt, *_p_rt; + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + if (s_rt->slave != slave) + continue; + + list_for_each_entry_safe(p_rt, _p_rt, + &s_rt->port_list, port_node) { + list_del(&p_rt->port_node); + kfree(p_rt); + } + } + } +} + +/** + * sdw_release_slave_stream() - Free Slave(s) runtime handle + * + * @slave: Slave handle. + * @stream: Stream runtime handle. + * + * This function is to be called with bus_lock held. + */ +static void sdw_release_slave_stream(struct sdw_slave *slave, + struct sdw_stream_runtime *stream) +{ + struct sdw_slave_runtime *s_rt, *_s_rt; + struct sdw_master_runtime *m_rt; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + /* Retrieve Slave runtime handle */ + list_for_each_entry_safe(s_rt, _s_rt, + &m_rt->slave_rt_list, m_rt_node) { + if (s_rt->slave == slave) { + list_del(&s_rt->m_rt_node); + kfree(s_rt); + return; + } + } + } +} + +/** + * sdw_release_master_stream() - Free Master runtime handle + * + * @m_rt: Master runtime node + * @stream: Stream runtime handle. + * + * This function is to be called with bus_lock held + * It frees the Master runtime handle and associated Slave(s) runtime + * handle. If this is called first then sdw_release_slave_stream() will have + * no effect as Slave(s) runtime handle would already be freed up. + */ +static void sdw_release_master_stream(struct sdw_master_runtime *m_rt, + struct sdw_stream_runtime *stream) +{ + struct sdw_slave_runtime *s_rt, *_s_rt; + + list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) { + sdw_slave_port_release(s_rt->slave->bus, s_rt->slave, stream); + sdw_release_slave_stream(s_rt->slave, stream); + } + + list_del(&m_rt->stream_node); + list_del(&m_rt->bus_node); + kfree(m_rt); +} + +/** + * sdw_stream_remove_master() - Remove master from sdw_stream + * + * @bus: SDW Bus instance + * @stream: SoundWire stream + * + * This removes and frees port_rt and master_rt from a stream + */ +int sdw_stream_remove_master(struct sdw_bus *bus, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt, *_m_rt; + + mutex_lock(&bus->bus_lock); + + list_for_each_entry_safe(m_rt, _m_rt, + &stream->master_list, stream_node) { + if (m_rt->bus != bus) + continue; + + sdw_master_port_release(bus, m_rt); + sdw_release_master_stream(m_rt, stream); + stream->m_rt_count--; + } + + if (list_empty(&stream->master_list)) + stream->state = SDW_STREAM_RELEASED; + + mutex_unlock(&bus->bus_lock); + + return 0; +} +EXPORT_SYMBOL(sdw_stream_remove_master); + +/** + * sdw_stream_remove_slave() - Remove slave from sdw_stream + * + * @slave: SDW Slave instance + * @stream: SoundWire stream + * + * This removes and frees port_rt and slave_rt from a stream + */ +int sdw_stream_remove_slave(struct sdw_slave *slave, + struct sdw_stream_runtime *stream) +{ + mutex_lock(&slave->bus->bus_lock); + + sdw_slave_port_release(slave->bus, slave, stream); + sdw_release_slave_stream(slave, stream); + + mutex_unlock(&slave->bus->bus_lock); + + return 0; +} +EXPORT_SYMBOL(sdw_stream_remove_slave); + +/** + * sdw_config_stream() - Configure the allocated stream + * + * @dev: SDW device + * @stream: SoundWire stream + * @stream_config: Stream configuration for audio stream + * @is_slave: is API called from Slave or Master + * + * This function is to be called with bus_lock held. + */ +static int sdw_config_stream(struct device *dev, + struct sdw_stream_runtime *stream, + struct sdw_stream_config *stream_config, + bool is_slave) +{ + /* + * Update the stream rate, channel and bps based on data + * source. For more than one data source (multilink), + * match the rate, bps, stream type and increment number of channels. + * + * If rate/bps is zero, it means the values are not set, so skip + * comparison and allow the value to be set and stored in stream + */ + if (stream->params.rate && + stream->params.rate != stream_config->frame_rate) { + dev_err(dev, "rate not matching, stream:%s\n", stream->name); + return -EINVAL; + } + + if (stream->params.bps && + stream->params.bps != stream_config->bps) { + dev_err(dev, "bps not matching, stream:%s\n", stream->name); + return -EINVAL; + } + + stream->type = stream_config->type; + stream->params.rate = stream_config->frame_rate; + stream->params.bps = stream_config->bps; + + /* TODO: Update this check during Device-device support */ + if (is_slave) + stream->params.ch_count += stream_config->ch_count; + + return 0; +} + +static int sdw_is_valid_port_range(struct device *dev, + struct sdw_port_runtime *p_rt) +{ + if (!SDW_VALID_PORT_RANGE(p_rt->num)) { + dev_err(dev, + "SoundWire: Invalid port number :%d\n", p_rt->num); + return -EINVAL; + } + + return 0; +} + +static struct sdw_port_runtime +*sdw_port_alloc(struct device *dev, + struct sdw_port_config *port_config, + int port_index) +{ + struct sdw_port_runtime *p_rt; + + p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL); + if (!p_rt) + return NULL; + + p_rt->ch_mask = port_config[port_index].ch_mask; + p_rt->num = port_config[port_index].num; + + return p_rt; +} + +static int sdw_master_port_config(struct sdw_bus *bus, + struct sdw_master_runtime *m_rt, + struct sdw_port_config *port_config, + unsigned int num_ports) +{ + struct sdw_port_runtime *p_rt; + int i; + + /* Iterate for number of ports to perform initialization */ + for (i = 0; i < num_ports; i++) { + p_rt = sdw_port_alloc(bus->dev, port_config, i); + if (!p_rt) + return -ENOMEM; + + /* + * TODO: Check port capabilities for requested + * configuration (audio mode support) + */ + + list_add_tail(&p_rt->port_node, &m_rt->port_list); + } + + return 0; +} + +static int sdw_slave_port_config(struct sdw_slave *slave, + struct sdw_slave_runtime *s_rt, + struct sdw_port_config *port_config, + unsigned int num_config) +{ + struct sdw_port_runtime *p_rt; + int i, ret; + + /* Iterate for number of ports to perform initialization */ + for (i = 0; i < num_config; i++) { + p_rt = sdw_port_alloc(&slave->dev, port_config, i); + if (!p_rt) + return -ENOMEM; + + /* + * TODO: Check valid port range as defined by DisCo/ + * slave + */ + ret = sdw_is_valid_port_range(&slave->dev, p_rt); + if (ret < 0) { + kfree(p_rt); + return ret; + } + + /* + * TODO: Check port capabilities for requested + * configuration (audio mode support) + */ + + list_add_tail(&p_rt->port_node, &s_rt->port_list); + } + + return 0; +} + +/** + * sdw_stream_add_master() - Allocate and add master runtime to a stream + * + * @bus: SDW Bus instance + * @stream_config: Stream configuration for audio stream + * @port_config: Port configuration for audio stream + * @num_ports: Number of ports + * @stream: SoundWire stream + */ +int sdw_stream_add_master(struct sdw_bus *bus, + struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + int ret; + + mutex_lock(&bus->bus_lock); + + /* + * For multi link streams, add the second master only if + * the bus supports it. + * Check if bus->multi_link is set + */ + if (!bus->multi_link && stream->m_rt_count > 0) { + dev_err(bus->dev, + "Multilink not supported, link %d\n", bus->link_id); + ret = -EINVAL; + goto unlock; + } + + m_rt = sdw_alloc_master_rt(bus, stream_config, stream); + if (!m_rt) { + dev_err(bus->dev, + "Master runtime config failed for stream:%s\n", + stream->name); + ret = -ENOMEM; + goto unlock; + } + + ret = sdw_config_stream(bus->dev, stream, stream_config, false); + if (ret) + goto stream_error; + + ret = sdw_master_port_config(bus, m_rt, port_config, num_ports); + if (ret) + goto stream_error; + + stream->m_rt_count++; + + goto unlock; + +stream_error: + sdw_release_master_stream(m_rt, stream); +unlock: + mutex_unlock(&bus->bus_lock); + return ret; +} +EXPORT_SYMBOL(sdw_stream_add_master); + +/** + * sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream + * + * @slave: SDW Slave instance + * @stream_config: Stream configuration for audio stream + * @stream: SoundWire stream + * @port_config: Port configuration for audio stream + * @num_ports: Number of ports + * + * It is expected that Slave is added before adding Master + * to the Stream. + * + */ +int sdw_stream_add_slave(struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + struct sdw_port_config *port_config, + unsigned int num_ports, + struct sdw_stream_runtime *stream) +{ + struct sdw_slave_runtime *s_rt; + struct sdw_master_runtime *m_rt; + int ret; + + mutex_lock(&slave->bus->bus_lock); + + /* + * If this API is invoked by Slave first then m_rt is not valid. + * So, allocate m_rt and add Slave to it. + */ + m_rt = sdw_alloc_master_rt(slave->bus, stream_config, stream); + if (!m_rt) { + dev_err(&slave->dev, + "alloc master runtime failed for stream:%s\n", + stream->name); + ret = -ENOMEM; + goto error; + } + + s_rt = sdw_alloc_slave_rt(slave, stream_config, stream); + if (!s_rt) { + dev_err(&slave->dev, + "Slave runtime config failed for stream:%s\n", + stream->name); + ret = -ENOMEM; + goto stream_error; + } + + ret = sdw_config_stream(&slave->dev, stream, stream_config, true); + if (ret) { + /* + * sdw_release_master_stream will release s_rt in slave_rt_list in + * stream_error case, but s_rt is only added to slave_rt_list + * when sdw_config_stream is successful, so free s_rt explicitly + * when sdw_config_stream is failed. + */ + kfree(s_rt); + goto stream_error; + } + + list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list); + + ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports); + if (ret) + goto stream_error; + + /* + * Change stream state to CONFIGURED on first Slave add. + * Bus is not aware of number of Slave(s) in a stream at this + * point so cannot depend on all Slave(s) to be added in order to + * change stream state to CONFIGURED. + */ + stream->state = SDW_STREAM_CONFIGURED; + goto error; + +stream_error: + /* + * we hit error so cleanup the stream, release all Slave(s) and + * Master runtime + */ + sdw_release_master_stream(m_rt, stream); +error: + mutex_unlock(&slave->bus->bus_lock); + return ret; +} +EXPORT_SYMBOL(sdw_stream_add_slave); + +/** + * sdw_get_slave_dpn_prop() - Get Slave port capabilities + * + * @slave: Slave handle + * @direction: Data direction. + * @port_num: Port number + */ +struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, + enum sdw_data_direction direction, + unsigned int port_num) +{ + struct sdw_dpn_prop *dpn_prop; + u8 num_ports; + int i; + + if (direction == SDW_DATA_DIR_TX) { + num_ports = hweight32(slave->prop.source_ports); + dpn_prop = slave->prop.src_dpn_prop; + } else { + num_ports = hweight32(slave->prop.sink_ports); + dpn_prop = slave->prop.sink_dpn_prop; + } + + for (i = 0; i < num_ports; i++) { + if (dpn_prop[i].num == port_num) + return &dpn_prop[i]; + } + + return NULL; +} + +/** + * sdw_acquire_bus_lock: Acquire bus lock for all Master runtime(s) + * + * @stream: SoundWire stream + * + * Acquire bus_lock for each of the master runtime(m_rt) part of this + * stream to reconfigure the bus. + * NOTE: This function is called from SoundWire stream ops and is + * expected that a global lock is held before acquiring bus_lock. + */ +static void sdw_acquire_bus_lock(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + struct sdw_bus *bus = NULL; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + + mutex_lock(&bus->bus_lock); + } +} + +/** + * sdw_release_bus_lock: Release bus lock for all Master runtime(s) + * + * @stream: SoundWire stream + * + * Release the previously held bus_lock after reconfiguring the bus. + * NOTE: This function is called from SoundWire stream ops and is + * expected that a global lock is held before releasing bus_lock. + */ +static void sdw_release_bus_lock(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt = NULL; + struct sdw_bus *bus = NULL; + + /* Iterate for all Master(s) in Master list */ + list_for_each_entry_reverse(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + mutex_unlock(&bus->bus_lock); + } +} + +static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, + bool update_params) +{ + struct sdw_master_runtime *m_rt; + struct sdw_bus *bus = NULL; + struct sdw_master_prop *prop; + struct sdw_bus_params params; + int ret; + + /* Prepare Master(s) and Slave(s) port(s) associated with stream */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + prop = &bus->prop; + memcpy(¶ms, &bus->params, sizeof(params)); + + /* TODO: Support Asynchronous mode */ + if ((prop->max_clk_freq % stream->params.rate) != 0) { + dev_err(bus->dev, "Async mode not supported\n"); + return -EINVAL; + } + + if (!update_params) + goto program_params; + + /* Increment cumulative bus bandwidth */ + /* TODO: Update this during Device-Device support */ + bus->params.bandwidth += m_rt->stream->params.rate * + m_rt->ch_count * m_rt->stream->params.bps; + + /* Compute params */ + if (bus->compute_params) { + ret = bus->compute_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute params failed: %d", + ret); + return ret; + } + } + +program_params: + /* Program params */ + ret = sdw_program_params(bus, true); + if (ret < 0) { + dev_err(bus->dev, "Program params failed: %d\n", ret); + goto restore_params; + } + } + + if (!bus) { + pr_err("Configuration error in %s\n", __func__); + return -EINVAL; + } + + ret = do_bank_switch(stream); + if (ret < 0) { + dev_err(bus->dev, "Bank switch failed: %d\n", ret); + goto restore_params; + } + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + + /* Prepare port(s) on the new clock configuration */ + ret = sdw_prep_deprep_ports(m_rt, true); + if (ret < 0) { + dev_err(bus->dev, "Prepare port(s) failed ret = %d\n", + ret); + return ret; + } + } + + stream->state = SDW_STREAM_PREPARED; + + return ret; + +restore_params: + memcpy(&bus->params, ¶ms, sizeof(params)); + return ret; +} + +/** + * sdw_prepare_stream() - Prepare SoundWire stream + * + * @stream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +int sdw_prepare_stream(struct sdw_stream_runtime *stream) +{ + bool update_params = true; + int ret; + + if (!stream) { + pr_err("SoundWire: Handle not found for stream\n"); + return -EINVAL; + } + + sdw_acquire_bus_lock(stream); + + if (stream->state == SDW_STREAM_PREPARED) { + ret = 0; + goto state_err; + } + + if (stream->state != SDW_STREAM_CONFIGURED && + stream->state != SDW_STREAM_DEPREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + /* + * when the stream is DISABLED, this means sdw_prepare_stream() + * is called as a result of an underflow or a resume operation. + * In this case, the bus parameters shall not be recomputed, but + * still need to be re-applied + */ + if (stream->state == SDW_STREAM_DISABLED) + update_params = false; + + ret = _sdw_prepare_stream(stream, update_params); + +state_err: + sdw_release_bus_lock(stream); + return ret; +} +EXPORT_SYMBOL(sdw_prepare_stream); + +static int _sdw_enable_stream(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + struct sdw_bus *bus = NULL; + int ret; + + /* Enable Master(s) and Slave(s) port(s) associated with stream */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + + /* Program params */ + ret = sdw_program_params(bus, false); + if (ret < 0) { + dev_err(bus->dev, "Program params failed: %d\n", ret); + return ret; + } + + /* Enable port(s) */ + ret = sdw_enable_disable_ports(m_rt, true); + if (ret < 0) { + dev_err(bus->dev, + "Enable port(s) failed ret: %d\n", ret); + return ret; + } + } + + if (!bus) { + pr_err("Configuration error in %s\n", __func__); + return -EINVAL; + } + + ret = do_bank_switch(stream); + if (ret < 0) { + dev_err(bus->dev, "Bank switch failed: %d\n", ret); + return ret; + } + + stream->state = SDW_STREAM_ENABLED; + return 0; +} + +/** + * sdw_enable_stream() - Enable SoundWire stream + * + * @stream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +int sdw_enable_stream(struct sdw_stream_runtime *stream) +{ + int ret; + + if (!stream) { + pr_err("SoundWire: Handle not found for stream\n"); + return -EINVAL; + } + + sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + ret = _sdw_enable_stream(stream); + +state_err: + sdw_release_bus_lock(stream); + return ret; +} +EXPORT_SYMBOL(sdw_enable_stream); + +static int _sdw_disable_stream(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + int ret; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + struct sdw_bus *bus = m_rt->bus; + + /* Disable port(s) */ + ret = sdw_enable_disable_ports(m_rt, false); + if (ret < 0) { + dev_err(bus->dev, "Disable port(s) failed: %d\n", ret); + return ret; + } + } + stream->state = SDW_STREAM_DISABLED; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + struct sdw_bus *bus = m_rt->bus; + + /* Program params */ + ret = sdw_program_params(bus, false); + if (ret < 0) { + dev_err(bus->dev, "Program params failed: %d\n", ret); + return ret; + } + } + + ret = do_bank_switch(stream); + if (ret < 0) { + pr_err("Bank switch failed: %d\n", ret); + return ret; + } + + /* make sure alternate bank (previous current) is also disabled */ + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + struct sdw_bus *bus = m_rt->bus; + + /* Disable port(s) */ + ret = sdw_enable_disable_ports(m_rt, false); + if (ret < 0) { + dev_err(bus->dev, "Disable port(s) failed: %d\n", ret); + return ret; + } + } + + return 0; +} + +/** + * sdw_disable_stream() - Disable SoundWire stream + * + * @stream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +int sdw_disable_stream(struct sdw_stream_runtime *stream) +{ + int ret; + + if (!stream) { + pr_err("SoundWire: Handle not found for stream\n"); + return -EINVAL; + } + + sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_ENABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + ret = _sdw_disable_stream(stream); + +state_err: + sdw_release_bus_lock(stream); + return ret; +} +EXPORT_SYMBOL(sdw_disable_stream); + +static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt; + struct sdw_bus *bus; + int ret = 0; + + list_for_each_entry(m_rt, &stream->master_list, stream_node) { + bus = m_rt->bus; + /* De-prepare port(s) */ + ret = sdw_prep_deprep_ports(m_rt, false); + if (ret < 0) { + dev_err(bus->dev, + "De-prepare port(s) failed: %d\n", ret); + return ret; + } + + /* TODO: Update this during Device-Device support */ + bus->params.bandwidth -= m_rt->stream->params.rate * + m_rt->ch_count * m_rt->stream->params.bps; + + /* Compute params */ + if (bus->compute_params) { + ret = bus->compute_params(bus); + if (ret < 0) { + dev_err(bus->dev, "Compute params failed: %d", + ret); + return ret; + } + } + + /* Program params */ + ret = sdw_program_params(bus, false); + if (ret < 0) { + dev_err(bus->dev, "Program params failed: %d\n", ret); + return ret; + } + } + + stream->state = SDW_STREAM_DEPREPARED; + return do_bank_switch(stream); +} + +/** + * sdw_deprepare_stream() - Deprepare SoundWire stream + * + * @stream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +int sdw_deprepare_stream(struct sdw_stream_runtime *stream) +{ + int ret; + + if (!stream) { + pr_err("SoundWire: Handle not found for stream\n"); + return -EINVAL; + } + + sdw_acquire_bus_lock(stream); + + if (stream->state != SDW_STREAM_PREPARED && + stream->state != SDW_STREAM_DISABLED) { + pr_err("%s: %s: inconsistent state state %d\n", + __func__, stream->name, stream->state); + ret = -EINVAL; + goto state_err; + } + + ret = _sdw_deprepare_stream(stream); + +state_err: + sdw_release_bus_lock(stream); + return ret; +} +EXPORT_SYMBOL(sdw_deprepare_stream); + +static int set_stream(struct snd_pcm_substream *substream, + struct sdw_stream_runtime *sdw_stream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *dai; + int ret = 0; + int i; + + /* Set stream pointer on all DAIs */ + for_each_rtd_dais(rtd, i, dai) { + ret = snd_soc_dai_set_stream(dai, sdw_stream, substream->stream); + if (ret < 0) { + dev_err(rtd->dev, "failed to set stream pointer on dai %s", dai->name); + break; + } + } + + return ret; +} + +/** + * sdw_startup_stream() - Startup SoundWire stream + * + * @sdw_substream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +int sdw_startup_stream(void *sdw_substream) +{ + struct snd_pcm_substream *substream = sdw_substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sdw_stream; + char *name; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + name = kasprintf(GFP_KERNEL, "%s-Playback", substream->name); + else + name = kasprintf(GFP_KERNEL, "%s-Capture", substream->name); + + if (!name) + return -ENOMEM; + + sdw_stream = sdw_alloc_stream(name); + if (!sdw_stream) { + dev_err(rtd->dev, "alloc stream failed for substream DAI %s", substream->name); + ret = -ENOMEM; + goto error; + } + + ret = set_stream(substream, sdw_stream); + if (ret < 0) + goto release_stream; + return 0; + +release_stream: + sdw_release_stream(sdw_stream); + set_stream(substream, NULL); +error: + kfree(name); + return ret; +} +EXPORT_SYMBOL(sdw_startup_stream); + +/** + * sdw_shutdown_stream() - Shutdown SoundWire stream + * + * @sdw_substream: Soundwire stream + * + * Documentation/driver-api/soundwire/stream.rst explains this API in detail + */ +void sdw_shutdown_stream(void *sdw_substream) +{ + struct snd_pcm_substream *substream = sdw_substream; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct sdw_stream_runtime *sdw_stream; + struct snd_soc_dai *dai; + + /* Find stream from first CPU DAI */ + dai = asoc_rtd_to_cpu(rtd, 0); + + sdw_stream = snd_soc_dai_get_stream(dai, substream->stream); + + if (IS_ERR(sdw_stream)) { + dev_err(rtd->dev, "no stream found for DAI %s", dai->name); + return; + } + + /* release memory */ + kfree(sdw_stream->name); + sdw_release_stream(sdw_stream); + + /* clear DAI data */ + set_stream(substream, NULL); +} +EXPORT_SYMBOL(sdw_shutdown_stream); diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h new file mode 100644 index 000000000..7268bc24c --- /dev/null +++ b/drivers/soundwire/sysfs_local.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright(c) 2015-2020 Intel Corporation. */ + +#ifndef __SDW_SYSFS_LOCAL_H +#define __SDW_SYSFS_LOCAL_H + +/* + * SDW sysfs APIs - + */ + +/* basic attributes to report status of Slave (attachment, dev_num) */ +extern const struct attribute_group *sdw_slave_status_attr_groups[]; + +/* additional device-managed properties reported after driver probe */ +int sdw_slave_sysfs_init(struct sdw_slave *slave); +int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave); + +#endif /* __SDW_SYSFS_LOCAL_H */ diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c new file mode 100644 index 000000000..b48b6617a --- /dev/null +++ b/drivers/soundwire/sysfs_slave.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2015-2020 Intel Corporation. + +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" +#include "sysfs_local.h" + +/* + * Slave sysfs + */ + +/* + * The sysfs for Slave reflects the MIPI description as given + * in the MIPI DisCo spec. + * status and device_number come directly from the MIPI SoundWire + * 1.x specification. + * + * Base file is device + * |---- status + * |---- device_number + * |---- modalias + * |---- dev-properties + * |---- mipi_revision + * |---- wake_capable + * |---- test_mode_capable + * |---- clk_stop_mode1 + * |---- simple_clk_stop_capable + * |---- clk_stop_timeout + * |---- ch_prep_timeout + * |---- reset_behave + * |---- high_PHY_capable + * |---- paging_support + * |---- bank_delay_support + * |---- p15_behave + * |---- master_count + * |---- source_ports + * |---- sink_ports + * |---- dp0 + * |---- max_word + * |---- min_word + * |---- words + * |---- BRA_flow_controlled + * |---- simple_ch_prep_sm + * |---- imp_def_interrupts + * |---- dpN_<sink/src> + * |---- max_word + * |---- min_word + * |---- words + * |---- type + * |---- max_grouping + * |---- simple_ch_prep_sm + * |---- ch_prep_timeout + * |---- imp_def_interrupts + * |---- min_ch + * |---- max_ch + * |---- channels + * |---- ch_combinations + * |---- max_async_buffer + * |---- block_pack_mode + * |---- port_encoding + * + */ + +#define sdw_slave_attr(field, format_string) \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sdw_slave *slave = dev_to_sdw_dev(dev); \ + return sprintf(buf, format_string, slave->prop.field); \ +} \ +static DEVICE_ATTR_RO(field) + +sdw_slave_attr(mipi_revision, "0x%x\n"); +sdw_slave_attr(wake_capable, "%d\n"); +sdw_slave_attr(test_mode_capable, "%d\n"); +sdw_slave_attr(clk_stop_mode1, "%d\n"); +sdw_slave_attr(simple_clk_stop_capable, "%d\n"); +sdw_slave_attr(clk_stop_timeout, "%d\n"); +sdw_slave_attr(ch_prep_timeout, "%d\n"); +sdw_slave_attr(reset_behave, "%d\n"); +sdw_slave_attr(high_PHY_capable, "%d\n"); +sdw_slave_attr(paging_support, "%d\n"); +sdw_slave_attr(bank_delay_support, "%d\n"); +sdw_slave_attr(p15_behave, "%d\n"); +sdw_slave_attr(master_count, "%d\n"); +sdw_slave_attr(source_ports, "0x%x\n"); +sdw_slave_attr(sink_ports, "0x%x\n"); + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + return sdw_slave_modalias(slave, buf, 256); +} +static DEVICE_ATTR_RO(modalias); + +static struct attribute *slave_attrs[] = { + &dev_attr_modalias.attr, + NULL, +}; +ATTRIBUTE_GROUPS(slave); + +static struct attribute *slave_dev_attrs[] = { + &dev_attr_mipi_revision.attr, + &dev_attr_wake_capable.attr, + &dev_attr_test_mode_capable.attr, + &dev_attr_clk_stop_mode1.attr, + &dev_attr_simple_clk_stop_capable.attr, + &dev_attr_clk_stop_timeout.attr, + &dev_attr_ch_prep_timeout.attr, + &dev_attr_reset_behave.attr, + &dev_attr_high_PHY_capable.attr, + &dev_attr_paging_support.attr, + &dev_attr_bank_delay_support.attr, + &dev_attr_p15_behave.attr, + &dev_attr_master_count.attr, + &dev_attr_source_ports.attr, + &dev_attr_sink_ports.attr, + NULL, +}; + +/* + * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory + * for device-level properties + */ +static struct attribute_group sdw_slave_dev_attr_group = { + .attrs = slave_dev_attrs, + .name = "dev-properties", +}; + +/* + * DP0 sysfs + */ + +#define sdw_dp0_attr(field, format_string) \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sdw_slave *slave = dev_to_sdw_dev(dev); \ + return sprintf(buf, format_string, slave->prop.dp0_prop->field);\ +} \ +static DEVICE_ATTR_RO(field) + +sdw_dp0_attr(max_word, "%d\n"); +sdw_dp0_attr(min_word, "%d\n"); +sdw_dp0_attr(BRA_flow_controlled, "%d\n"); +sdw_dp0_attr(simple_ch_prep_sm, "%d\n"); +sdw_dp0_attr(imp_def_interrupts, "0x%x\n"); + +static ssize_t words_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + ssize_t size = 0; + int i; + + for (i = 0; i < slave->prop.dp0_prop->num_words; i++) + size += sprintf(buf + size, "%d ", + slave->prop.dp0_prop->words[i]); + size += sprintf(buf + size, "\n"); + + return size; +} +static DEVICE_ATTR_RO(words); + +static struct attribute *dp0_attrs[] = { + &dev_attr_max_word.attr, + &dev_attr_min_word.attr, + &dev_attr_words.attr, + &dev_attr_BRA_flow_controlled.attr, + &dev_attr_simple_ch_prep_sm.attr, + &dev_attr_imp_def_interrupts.attr, + NULL, +}; + +/* + * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory + * for dp0-level properties + */ +static const struct attribute_group dp0_group = { + .attrs = dp0_attrs, + .name = "dp0", +}; + +int sdw_slave_sysfs_init(struct sdw_slave *slave) +{ + int ret; + + ret = devm_device_add_groups(&slave->dev, slave_groups); + if (ret < 0) + return ret; + + ret = devm_device_add_group(&slave->dev, &sdw_slave_dev_attr_group); + if (ret < 0) + return ret; + + if (slave->prop.dp0_prop) { + ret = devm_device_add_group(&slave->dev, &dp0_group); + if (ret < 0) + return ret; + } + + if (slave->prop.source_ports || slave->prop.sink_ports) { + ret = sdw_slave_sysfs_dpn_init(slave); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * the status is shown in capital letters for UNATTACHED and RESERVED + * on purpose, to highligh users to the fact that these status values + * are not expected. + */ +static const char *const slave_status[] = { + [SDW_SLAVE_UNATTACHED] = "UNATTACHED", + [SDW_SLAVE_ATTACHED] = "Attached", + [SDW_SLAVE_ALERT] = "Alert", + [SDW_SLAVE_RESERVED] = "RESERVED", +}; + +static ssize_t status_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + return sprintf(buf, "%s\n", slave_status[slave->status]); +} +static DEVICE_ATTR_RO(status); + +static ssize_t device_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + + if (slave->status == SDW_SLAVE_UNATTACHED) + return sprintf(buf, "%s", "N/A"); + else + return sprintf(buf, "%d", slave->dev_num); +} +static DEVICE_ATTR_RO(device_number); + +static struct attribute *slave_status_attrs[] = { + &dev_attr_status.attr, + &dev_attr_device_number.attr, + NULL, +}; + +/* + * we don't use ATTRIBUTES_GROUP here since the group is used in a + * separate file and can't be handled as a static. + */ +static const struct attribute_group sdw_slave_status_attr_group = { + .attrs = slave_status_attrs, +}; + +const struct attribute_group *sdw_slave_status_attr_groups[] = { + &sdw_slave_status_attr_group, + NULL +}; diff --git a/drivers/soundwire/sysfs_slave_dpn.c b/drivers/soundwire/sysfs_slave_dpn.c new file mode 100644 index 000000000..c4b6543c0 --- /dev/null +++ b/drivers/soundwire/sysfs_slave_dpn.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright(c) 2015-2020 Intel Corporation. + +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include "bus.h" +#include "sysfs_local.h" + +struct dpn_attribute { + struct device_attribute dev_attr; + int N; + int dir; + const char *format_string; +}; + +/* + * Since we can't use ARRAY_SIZE, hard-code number of dpN attributes. + * This needs to be updated when adding new attributes - an error will be + * flagged on a mismatch. + */ +#define SDW_DPN_ATTRIBUTES 15 + +#define sdw_dpn_attribute_alloc(field) \ +static int field##_attribute_alloc(struct device *dev, \ + struct attribute **res, \ + int N, int dir, \ + const char *format_string) \ +{ \ + struct dpn_attribute *dpn_attr; \ + \ + dpn_attr = devm_kzalloc(dev, sizeof(*dpn_attr), GFP_KERNEL); \ + if (!dpn_attr) \ + return -ENOMEM; \ + dpn_attr->N = N; \ + dpn_attr->dir = dir; \ + sysfs_attr_init(&dpn_attr->dev_attr.attr); \ + dpn_attr->format_string = format_string; \ + dpn_attr->dev_attr.attr.name = __stringify(field); \ + dpn_attr->dev_attr.attr.mode = 0444; \ + dpn_attr->dev_attr.show = field##_show; \ + \ + *res = &dpn_attr->dev_attr.attr; \ + \ + return 0; \ +} + +#define sdw_dpn_attr(field) \ + \ +static ssize_t field##_dpn_show(struct sdw_slave *slave, \ + int N, \ + int dir, \ + const char *format_string, \ + char *buf) \ +{ \ + struct sdw_dpn_prop *dpn; \ + unsigned long mask; \ + int bit; \ + int i; \ + \ + if (dir) { \ + dpn = slave->prop.src_dpn_prop; \ + mask = slave->prop.source_ports; \ + } else { \ + dpn = slave->prop.sink_dpn_prop; \ + mask = slave->prop.sink_ports; \ + } \ + \ + i = 0; \ + for_each_set_bit(bit, &mask, 32) { \ + if (bit == N) { \ + return sprintf(buf, format_string, \ + dpn[i].field); \ + } \ + i++; \ + } \ + return -EINVAL; \ +} \ + \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sdw_slave *slave = dev_to_sdw_dev(dev); \ + struct dpn_attribute *dpn_attr = \ + container_of(attr, struct dpn_attribute, dev_attr); \ + \ + return field##_dpn_show(slave, \ + dpn_attr->N, dpn_attr->dir, \ + dpn_attr->format_string, \ + buf); \ +} \ +sdw_dpn_attribute_alloc(field) + +sdw_dpn_attr(imp_def_interrupts); +sdw_dpn_attr(max_word); +sdw_dpn_attr(min_word); +sdw_dpn_attr(type); +sdw_dpn_attr(max_grouping); +sdw_dpn_attr(simple_ch_prep_sm); +sdw_dpn_attr(ch_prep_timeout); +sdw_dpn_attr(max_ch); +sdw_dpn_attr(min_ch); +sdw_dpn_attr(max_async_buffer); +sdw_dpn_attr(block_pack_mode); +sdw_dpn_attr(port_encoding); + +#define sdw_dpn_array_attr(field) \ + \ +static ssize_t field##_dpn_show(struct sdw_slave *slave, \ + int N, \ + int dir, \ + const char *format_string, \ + char *buf) \ +{ \ + struct sdw_dpn_prop *dpn; \ + unsigned long mask; \ + ssize_t size = 0; \ + int bit; \ + int i; \ + int j; \ + \ + if (dir) { \ + dpn = slave->prop.src_dpn_prop; \ + mask = slave->prop.source_ports; \ + } else { \ + dpn = slave->prop.sink_dpn_prop; \ + mask = slave->prop.sink_ports; \ + } \ + \ + i = 0; \ + for_each_set_bit(bit, &mask, 32) { \ + if (bit == N) { \ + for (j = 0; j < dpn[i].num_##field; j++) \ + size += sprintf(buf + size, \ + format_string, \ + dpn[i].field[j]); \ + size += sprintf(buf + size, "\n"); \ + return size; \ + } \ + i++; \ + } \ + return -EINVAL; \ +} \ +static ssize_t field##_show(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct sdw_slave *slave = dev_to_sdw_dev(dev); \ + struct dpn_attribute *dpn_attr = \ + container_of(attr, struct dpn_attribute, dev_attr); \ + \ + return field##_dpn_show(slave, \ + dpn_attr->N, dpn_attr->dir, \ + dpn_attr->format_string, \ + buf); \ +} \ +sdw_dpn_attribute_alloc(field) + +sdw_dpn_array_attr(words); +sdw_dpn_array_attr(ch_combinations); +sdw_dpn_array_attr(channels); + +static int add_all_attributes(struct device *dev, int N, int dir) +{ + struct attribute **dpn_attrs; + struct attribute_group *dpn_group; + int i = 0; + int ret; + + /* allocate attributes, last one is NULL */ + dpn_attrs = devm_kcalloc(dev, SDW_DPN_ATTRIBUTES + 1, + sizeof(struct attribute *), + GFP_KERNEL); + if (!dpn_attrs) + return -ENOMEM; + + ret = max_word_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = min_word_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = words_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = type_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = max_grouping_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = simple_ch_prep_sm_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = ch_prep_timeout_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = imp_def_interrupts_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "0x%x\n"); + if (ret < 0) + return ret; + + ret = min_ch_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = max_ch_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = channels_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = ch_combinations_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = max_async_buffer_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = block_pack_mode_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + ret = port_encoding_attribute_alloc(dev, &dpn_attrs[i++], + N, dir, "%d\n"); + if (ret < 0) + return ret; + + /* paranoia check for editing mistakes */ + if (i != SDW_DPN_ATTRIBUTES) { + dev_err(dev, "mismatch in attributes, allocated %d got %d\n", + SDW_DPN_ATTRIBUTES, i); + return -EINVAL; + } + + dpn_group = devm_kzalloc(dev, sizeof(*dpn_group), GFP_KERNEL); + if (!dpn_group) + return -ENOMEM; + + dpn_group->attrs = dpn_attrs; + dpn_group->name = devm_kasprintf(dev, GFP_KERNEL, "dp%d_%s", + N, dir ? "src" : "sink"); + if (!dpn_group->name) + return -ENOMEM; + + ret = devm_device_add_group(dev, dpn_group); + if (ret < 0) + return ret; + + return 0; +} + +int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave) +{ + unsigned long mask; + int ret; + int i; + + mask = slave->prop.source_ports; + for_each_set_bit(i, &mask, 32) { + ret = add_all_attributes(&slave->dev, i, 1); + if (ret < 0) + return ret; + } + + mask = slave->prop.sink_ports; + for_each_set_bit(i, &mask, 32) { + ret = add_all_attributes(&slave->dev, i, 0); + if (ret < 0) + return ret; + } + + return 0; +} |