diff options
author | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-10-01 22:59:55 +0200 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2020-10-01 22:59:55 +0200 |
commit | 4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e (patch) | |
tree | caa439723e5fc5cdc473bdd519a0a5f4ac27d201 /drivers/soundwire | |
parent | c471bf4b11c7df0f0f9f42b5aeec424dc62d0c7e (diff) | |
parent | 0173f525b2c1b02a51784e2119d434593235aed1 (diff) | |
download | linux-4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e.tar.gz linux-4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e.tar.bz2 linux-4cb1a880e7f774e59c2ebb68187d4811ad9d0c4e.zip |
Merge tag 'soundwire-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire into char-misc-next
Vinod writes:
soundwire updates for 5.10-rc1
This round of update includes:
- Generic bandwidth allocation algorithm from Intel folks
- PM support for Intel chipsets
- Updates to Intel drivers which makes sdw usable on latest laptops
- Support for MMIO SDW controllers found in QC chipsets
- Update to subsystem to use helpers in bitfield.h to manage register
bits
* tag 'soundwire-5.10-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (66 commits)
soundwire: sysfs: add slave status and device number before probe
soundwire: bus: add enumerated Slave device to device list
soundwire: remove an unnecessary NULL check
soundwire: cadence: add data port test fail interrupt
soundwire: intel: enable test modes
soundwire: enable Data Port test modes
soundwire: intel: use {u32|u16}p_replace_bits
soundwire: cadence: use u32p_replace_bits
soundwire: qcom: get max rows and cols info from compatible
soundwire: qcom: add support to block packing mode
soundwire: qcom: clear BIT FIELDs before value set.
soundwire: Add generic bandwidth allocation algorithm
soundwire: cadence: add parity error injection through debugfs
soundwire: bus: export broadcast read/write capability for tests
ASoC: codecs: realtek-soundwire: ignore initial PARITY errors
soundwire: bus: use quirk to filter out invalid parity errors
soundwire: slave: add first_interrupt_done status
soundwire: bus: filter-out unwanted interrupt reports
ASoC/soundwire: bus: use property to set interrupt masks
soundwire: qcom: fix SLIBMUS/SLIMBUS typo
...
Diffstat (limited to 'drivers/soundwire')
-rw-r--r-- | drivers/soundwire/Kconfig | 7 | ||||
-rw-r--r-- | drivers/soundwire/Makefile | 3 | ||||
-rw-r--r-- | drivers/soundwire/bus.c | 120 | ||||
-rw-r--r-- | drivers/soundwire/bus.h | 52 | ||||
-rw-r--r-- | drivers/soundwire/bus_type.c | 9 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.c | 199 | ||||
-rw-r--r-- | drivers/soundwire/cadence_master.h | 5 | ||||
-rw-r--r-- | drivers/soundwire/generic_bandwidth_allocation.c | 425 | ||||
-rw-r--r-- | drivers/soundwire/intel.c | 803 | ||||
-rw-r--r-- | drivers/soundwire/intel.h | 4 | ||||
-rw-r--r-- | drivers/soundwire/intel_init.c | 22 | ||||
-rw-r--r-- | drivers/soundwire/master.c | 2 | ||||
-rw-r--r-- | drivers/soundwire/mipi_disco.c | 18 | ||||
-rw-r--r-- | drivers/soundwire/qcom.c | 118 | ||||
-rw-r--r-- | drivers/soundwire/slave.c | 13 | ||||
-rw-r--r-- | drivers/soundwire/stream.c | 45 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_local.h | 4 | ||||
-rw-r--r-- | drivers/soundwire/sysfs_slave.c | 58 |
18 files changed, 1671 insertions, 236 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index fa2b4ab92ed9..016e74230bb7 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -24,6 +24,7 @@ config SOUNDWIRE_CADENCE 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. @@ -33,11 +34,15 @@ config SOUNDWIRE_INTEL config SOUNDWIRE_QCOM tristate "Qualcomm SoundWire Master driver" - depends on SLIMBUS + 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 index 7c53ffae9f50..bf1e250d50dd 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -8,6 +8,9 @@ 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 diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index da0201693c24..8eaf31e76677 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -61,6 +61,12 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, 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); @@ -255,6 +261,21 @@ static int sdw_reset_page(struct sdw_bus *bus, u16 dev_num) 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 @@ -266,13 +287,7 @@ int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg) mutex_lock(&bus->msg_lock); - 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); + ret = sdw_transfer_unlocked(bus, msg); mutex_unlock(&bus->msg_lock); @@ -347,8 +362,8 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave, return -EINVAL; } - msg->addr_page1 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE1_MASK)); - msg->addr_page2 = (addr >> SDW_REG_SHIFT(SDW_SCP_ADDRPAGE2_MASK)); + 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; @@ -428,6 +443,39 @@ sdw_bwrite_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value) 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); + static int sdw_read_no_pm(struct sdw_slave *slave, u32 addr) { @@ -699,6 +747,15 @@ static int sdw_program_device_num(struct sdw_bus *bus) 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"); } @@ -1051,6 +1108,12 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave, 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 */ @@ -1184,13 +1247,13 @@ static int sdw_initialize_slave(struct sdw_slave *slave) return ret; /* - * Set bus clash, parity and SCP implementation - * defined interrupt mask - * TODO: Read implementation defined interrupt mask - * from Slave property + * 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 = SDW_SCP_INT1_IMPL_DEF | SDW_SCP_INT1_BUS_CLASH | - SDW_SCP_INT1_PARITY; + val = slave->prop.scp_int1_mask; /* Enable SCP interrupts */ ret = sdw_update(slave, SDW_SCP_INTMASK1, val, val); @@ -1362,6 +1425,8 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) 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); @@ -1394,12 +1459,18 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) * interrupt */ if (buf & SDW_SCP_INT1_PARITY) { - dev_err(&slave->dev, "Parity error detected\n"); + 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) { - dev_err(&slave->dev, "Bus clash error detected\n"); + 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; } @@ -1411,16 +1482,18 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) */ if (buf & SDW_SCP_INT1_IMPL_DEF) { - dev_dbg(&slave->dev, "Slave impl defined interrupt\n"); + 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; - slave_notify = true; } /* Check port 0 - 3 interrupts */ port = buf & SDW_SCP_INT1_PORT0_3; /* To get port number corresponding to bits, shift it */ - port = port >> SDW_REG_SHIFT(SDW_SCP_INT1_PORT0_3); + 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]); @@ -1468,6 +1541,9 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave) 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. @@ -1670,8 +1746,10 @@ void sdw_clear_slave_status(struct sdw_bus *bus, u32 request) if (!slave) continue; - if (slave->status != SDW_SLAVE_UNATTACHED) + 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; diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 82484f741168..2e049d39c6e5 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -19,6 +19,8 @@ static inline int sdw_acpi_find_slaves(struct sdw_bus *bus) 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); @@ -69,6 +71,7 @@ struct sdw_msg { }; #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]; @@ -154,9 +157,50 @@ int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg, 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) +static inline int sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val) { int tmp; @@ -168,6 +212,10 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 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 diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index 6fba55898cf0..575b9bad99d5 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -84,6 +84,15 @@ static int sdw_drv_probe(struct device *dev) 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; diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 24eafe0aa1c3..9fa55164354a 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -13,6 +13,7 @@ #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> @@ -50,11 +51,14 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); #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_OFFSET 3 +#define CDNS_MCP_FRAME_SHAPE_ROW_MASK GENMASK(7, 3) #define CDNS_MCP_CONFIG_UPDATE 0x18 #define CDNS_MCP_CONFIG_UPDATE_BIT BIT(0) @@ -129,8 +133,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); #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_H GENMASK(23, 16) -#define CDNS_MCP_CMD_REG_ADDR_L GENMASK(15, 8) +#define CDNS_MCP_CMD_REG_ADDR GENMASK(23, 8) #define CDNS_MCP_CMD_REG_DATA GENMASK(7, 0) #define CDNS_MCP_CMD_READ 2 @@ -172,6 +175,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); #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) @@ -367,6 +371,85 @@ static int cdns_hw_reset(void *data, u64 value) 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 @@ -378,6 +461,9 @@ void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root) 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); @@ -417,8 +503,7 @@ cdns_fill_msg_resp(struct sdw_cdns *cdns, /* fill response */ for (i = 0; i < count; i++) - msg->buf[i + offset] = cdns->response_buf[i] >> - SDW_REG_SHIFT(CDNS_MCP_RESP_RDATA); + msg->buf[i + offset] = FIELD_GET(CDNS_MCP_RESP_RDATA, cdns->response_buf[i]); return SDW_CMD_OK; } @@ -441,14 +526,15 @@ _cdns_xfer_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int cmd, addr = msg->addr; for (i = 0; i < count; i++) { - data = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR); - data |= cmd << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND); - data |= addr++ << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); + 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 |= msg->ssp_sync << SDW_REG_SHIFT(CDNS_MCP_CMD_SSP_TAG); + data |= FIELD_PREP(CDNS_MCP_CMD_SSP_TAG, msg->ssp_sync); cdns_writel(cdns, base, data); base += CDNS_MCP_CMD_WORD_LEN; } @@ -483,12 +569,12 @@ cdns_program_scp_addr(struct sdw_cdns *cdns, struct sdw_msg *msg) cdns->msg_count = CDNS_SCP_RX_FIFOLEVEL; } - data[0] = msg->dev_num << SDW_REG_SHIFT(CDNS_MCP_CMD_DEV_ADDR); - data[0] |= 0x3 << SDW_REG_SHIFT(CDNS_MCP_CMD_COMMAND); + 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] |= SDW_SCP_ADDRPAGE1 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); - data[1] |= SDW_SCP_ADDRPAGE2 << SDW_REG_SHIFT(CDNS_MCP_CMD_REG_ADDR_L); + 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; @@ -785,13 +871,35 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) 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; - schedule_work(&cdns->work); + + /* + * 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); @@ -900,7 +1008,9 @@ int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state) mask |= CDNS_MCP_INT_CTRL_CLASH | CDNS_MCP_INT_DATA_CLASH | CDNS_MCP_INT_PARITY; - /* no detection of port interrupts for now */ + /* 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; @@ -924,6 +1034,19 @@ update_masks: 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); @@ -1041,9 +1164,10 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols) int r; r = sdw_find_row_index(n_rows); - c = sdw_find_col_index(n_cols) & CDNS_MCP_FRAME_SHAPE_COL_MASK; + c = sdw_find_col_index(n_cols); - val = (r << CDNS_MCP_FRAME_SHAPE_ROW_OFFSET) | c; + val = FIELD_PREP(CDNS_MCP_FRAME_SHAPE_ROW_MASK, r); + val |= FIELD_PREP(CDNS_MCP_FRAME_SHAPE_COL_MASK, c); return val; } @@ -1170,12 +1294,9 @@ static int cdns_port_params(struct sdw_bus *bus, dpn_config = cdns_readl(cdns, dpn_config_off); - dpn_config |= ((p_params->bps - 1) << - SDW_REG_SHIFT(CDNS_DPN_CONFIG_WL)); - dpn_config |= (p_params->flow_mode << - SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_FLOW)); - dpn_config |= (p_params->data_mode << - SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_DAT)); + 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); @@ -1211,24 +1332,17 @@ static int cdns_transport_params(struct sdw_bus *bus, } dpn_config = cdns_readl(cdns, dpn_config_off); - - dpn_config |= (t_params->blk_grp_ctrl << - SDW_REG_SHIFT(CDNS_DPN_CONFIG_BGC)); - dpn_config |= (t_params->blk_pkg_mode << - SDW_REG_SHIFT(CDNS_DPN_CONFIG_BPM)); + 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); - dpn_offsetctrl |= (t_params->offset1 << - SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_1)); - dpn_offsetctrl |= (t_params->offset2 << - SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_2)); + 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); - dpn_hctrl |= (t_params->hstart << - SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTART)); - dpn_hctrl |= (t_params->hstop << SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTOP)); - dpn_hctrl |= (t_params->lane_ctrl << - SDW_REG_SHIFT(CDNS_DPN_HCTRL_LCTRL)); + 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)); @@ -1526,15 +1640,20 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns, { u32 offset, val = 0; - if (dir == SDW_DATA_DIR_RX) + 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, val); + cdns_updatel(cdns, offset, + CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED, + val); val = pdi->num; val |= CDNS_PDI_CONFIG_SOFT_RESET; - val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL); + 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); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 7638858397df..4d1aab5b5ec2 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -84,6 +84,8 @@ struct sdw_cdns_stream_config { * @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; @@ -92,6 +94,8 @@ struct sdw_cdns_dma_data { struct sdw_bus *bus; enum sdw_stream_type stream_type; int link_id; + struct snd_pcm_hw_params *hw_params; + bool suspended; }; /** @@ -129,6 +133,7 @@ struct sdw_cdns { bool link_up; unsigned int msg_count; + bool interrupt_enabled; struct work_struct work; diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c new file mode 100644 index 000000000000..0bdef38c9a30 --- /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 index a283670659a9..6a1e862b16c3 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -22,6 +22,24 @@ #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 @@ -45,7 +63,9 @@ #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) @@ -242,6 +262,42 @@ static int intel_reg_show(struct seq_file *s_file, void *data) } 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; @@ -254,6 +310,12 @@ static void intel_debugfs_init(struct sdw_intel *sdw) 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); } @@ -277,8 +339,8 @@ static int intel_link_power_up(struct sdw_intel *sdw) u32 *shim_mask = sdw->link_res->shim_mask; struct sdw_bus *bus = &sdw->cdns.bus; struct sdw_master_prop *prop = &bus->prop; - int spa_mask, cpa_mask; - int link_control; + u32 spa_mask, cpa_mask; + u32 link_control; int ret = 0; u32 syncprd; u32 sync_reg; @@ -301,33 +363,35 @@ static int intel_link_power_up(struct sdw_intel *sdw) 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); - sync_reg |= (syncprd << - SDW_REG_SHIFT(SDW_SHIM_SYNC_SYNCPRD)); + 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); - spa_mask = (SDW_SHIM_LCTL_SPA << link_id); - cpa_mask = (SDW_SHIM_LCTL_CPA << link_id); - link_control |= spa_mask; + /* Link power up sequence */ + link_control = intel_readl(shim, SDW_SHIM_LCTL); - 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; - } + /* 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; + } - if (!*shim_mask) { /* SyncCPU will change once link is active */ ret = intel_wait_bit(shim, SDW_SHIM_SYNC, SDW_SHIM_SYNC_SYNCCPU, 0); @@ -426,7 +490,7 @@ static int intel_shim_init(struct sdw_intel *sdw, bool clock_stop) intel_shim_glue_to_master_ip(sdw); - act |= 0x1 << SDW_REG_SHIFT(SDW_SHIM_CTMCTL_DOAIS); + 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); @@ -463,9 +527,9 @@ static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) mutex_unlock(sdw->link_res->shim_lock); } -static int __maybe_unused intel_link_power_down(struct sdw_intel *sdw) +static int intel_link_power_down(struct sdw_intel *sdw) { - int link_control, spa_mask, cpa_mask; + 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; @@ -475,24 +539,37 @@ static int __maybe_unused intel_link_power_down(struct sdw_intel *sdw) intel_shim_master_ip_to_glue(sdw); - /* Link power down sequence */ - link_control = intel_readl(shim, SDW_SHIM_LCTL); - spa_mask = ~(SDW_SHIM_LCTL_SPA << link_id); - cpa_mask = (SDW_SHIM_LCTL_CPA << link_id); - link_control &= spa_mask; - - ret = intel_clear_bit(shim, SDW_SHIM_LCTL, link_control, cpa_mask); - if (!(*shim_mask & BIT(link_id))) dev_err(sdw->cdns.dev, "%s: Unbalanced power-up/down calls\n", __func__); *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); + } + + link_control = intel_readl(shim, SDW_SHIM_LCTL); + mutex_unlock(sdw->link_res->shim_lock); - if (ret < 0) + if (ret < 0) { + dev_err(sdw->cdns.dev, "%s: could not power down link\n", __func__); + return ret; + } sdw->cdns.link_up = false; return 0; @@ -538,6 +615,19 @@ static int intel_shim_sync_go_unlocked(struct sdw_intel *sdw) 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 */ @@ -551,12 +641,9 @@ static void intel_pdi_init(struct sdw_intel *sdw, /* PCM Stream Capability */ pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id)); - config->pcm_bd = (pcm_cap & SDW_SHIM_PCMSCAP_BSS) >> - SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_BSS); - config->pcm_in = (pcm_cap & SDW_SHIM_PCMSCAP_ISS) >> - SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_ISS); - config->pcm_out = (pcm_cap & SDW_SHIM_PCMSCAP_OSS) >> - SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_OSS); + 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); @@ -564,12 +651,9 @@ static void intel_pdi_init(struct sdw_intel *sdw, /* PDM Stream Capability */ pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id)); - config->pdm_bd = (pdm_cap & SDW_SHIM_PDMSCAP_BSS) >> - SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_BSS); - config->pdm_in = (pdm_cap & SDW_SHIM_PDMSCAP_ISS) >> - SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_ISS); - config->pdm_out = (pdm_cap & SDW_SHIM_PDMSCAP_OSS) >> - SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_OSS); + 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); @@ -596,8 +680,7 @@ intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm) } else { count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id)); - count = ((count & SDW_SHIM_PDMSCAP_CPSS) >> - SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_CPSS)); + count = FIELD_GET(SDW_SHIM_PDMSCAP_CPSS, count); } /* zero based values for channel count in register */ @@ -671,10 +754,9 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) else pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR); - pdi_conf |= (pdi->intel_alh_id << - SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_STREAM)); - pdi_conf |= (pdi->l_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_LCHN)); - pdi_conf |= (pdi->h_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_HCHN)); + 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); } @@ -694,11 +776,8 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) /* Program Stream config ALH register */ conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id)); - conf |= (SDW_ALH_STRMZCFG_DMAT_VAL << - SDW_REG_SHIFT(SDW_ALH_STRMZCFG_DMAT)); - - conf |= ((pdi->ch_count - 1) << - SDW_REG_SHIFT(SDW_ALH_STRMZCFG_CHN)); + 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); } @@ -807,10 +886,17 @@ unlock: static int intel_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - /* - * TODO: add pm_runtime support here, the startup callback - * will make sure the IP is 'active' - */ + 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; } @@ -856,6 +942,10 @@ static int intel_hw_params(struct snd_pcm_substream *substream, 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, @@ -899,7 +989,11 @@ error: 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) { @@ -908,43 +1002,35 @@ static int intel_prepare(struct snd_pcm_substream *substream, return -EIO; } - return sdw_prepare_stream(dma->stream); -} + if (dma->suspended) { + dma->suspended = false; -static int intel_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct sdw_cdns_dma_data *dma; - int ret; - - 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; - } + /* + * .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 + */ - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - case SNDRV_PCM_TRIGGER_RESUME: - ret = sdw_enable_stream(dma->stream); - break; + /* 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; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_STOP: - ret = sdw_disable_stream(dma->stream); - break; + intel_pdi_shim_configure(sdw, dma->pdi); + intel_pdi_alh_configure(sdw, dma->pdi); + sdw_cdns_config_stream(cdns, ch, dir, dma->pdi); - default: - ret = -EINVAL; - break; + /* Inform DSP about PDI stream number */ + ret = intel_params_stream(sdw, substream, dai, + dma->hw_params, + sdw->instance, + dma->pdi->intel_alh_id); } - if (ret) - dev_err(dai->dev, - "%s trigger %d failed: %d", - __func__, cmd, ret); return ret; } @@ -960,12 +1046,12 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) if (!dma) return -EIO; - ret = sdw_deprepare_stream(dma->stream); - if (ret) { - dev_err(dai->dev, "sdw_deprepare_stream: failed %d", ret); - return ret; - } - + /* + * 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", @@ -979,13 +1065,42 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) 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, @@ -1011,7 +1126,7 @@ static void *intel_get_sdw_stream(struct snd_soc_dai *dai, dma = dai->capture_dma_data; if (!dma) - return NULL; + return ERR_PTR(-EINVAL); return dma->stream; } @@ -1020,7 +1135,6 @@ static const struct snd_soc_dai_ops intel_pcm_dai_ops = { .startup = intel_startup, .hw_params = intel_hw_params, .prepare = intel_prepare, - .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pcm_set_sdw_stream, @@ -1031,7 +1145,6 @@ static const struct snd_soc_dai_ops intel_pdm_dai_ops = { .startup = intel_startup, .hw_params = intel_hw_params, .prepare = intel_prepare, - .trigger = intel_trigger, .hw_free = intel_hw_free, .shutdown = intel_shutdown, .set_sdw_stream = intel_pdm_set_sdw_stream, @@ -1040,6 +1153,7 @@ static const struct snd_soc_dai_ops intel_pdm_dai_ops = { 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, @@ -1207,10 +1321,7 @@ static int intel_init(struct sdw_intel *sdw) intel_shim_init(sdw, clock_stop); - if (clock_stop) - return 0; - - return sdw_cdns_init(&sdw->cdns); + return 0; } /* @@ -1249,6 +1360,9 @@ static int intel_master_probe(struct platform_device *pdev) /* 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); @@ -1259,6 +1373,11 @@ static int intel_master_probe(struct platform_device *pdev) 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; } @@ -1270,6 +1389,9 @@ int intel_master_startup(struct platform_device *pdev) 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) { @@ -1279,7 +1401,23 @@ int intel_master_startup(struct platform_device *pdev) return 0; } - /* Initialize shim, controller and Cadence IP */ + 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; @@ -1298,12 +1436,33 @@ int intel_master_startup(struct platform_device *pdev) 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) { @@ -1314,6 +1473,47 @@ int intel_master_startup(struct platform_device *pdev) 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: @@ -1329,6 +1529,11 @@ static int intel_master_remove(struct platform_device *pdev) 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); @@ -1376,12 +1581,408 @@ int intel_master_process_wakeen_event(struct platform_device *pdev) 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); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index 4ea3d262d249..76820d0b9deb 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -17,6 +17,8 @@ * @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 */ @@ -31,6 +33,8 @@ struct sdw_intel_link_res { 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; }; diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 047252a91c9e..cabdadb09a1b 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -13,6 +13,7 @@ #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" @@ -68,8 +69,13 @@ static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx) if (!(link_mask & BIT(i))) continue; - if (link->pdev) + 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; @@ -246,8 +252,10 @@ static struct sdw_intel_ctx 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)); @@ -334,6 +342,16 @@ sdw_intel_startup_controller(struct sdw_intel_ctx *ctx) 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; @@ -365,7 +383,7 @@ static acpi_status sdw_intel_acpi_cb(acpi_handle handle, u32 level, * Name(_ADR, 0x40000000), with bits 31..28 representing the * SoundWire link so filter accordingly */ - if ((adr & GENMASK(31, 28)) >> 28 != SDW_LINK_TYPE) + if (FIELD_GET(GENMASK(31, 28), adr) != SDW_LINK_TYPE) return AE_OK; /* keep going */ /* device found, stop namespace walk */ diff --git a/drivers/soundwire/master.c b/drivers/soundwire/master.c index 5f0b2189defe..3488bb824e84 100644 --- a/drivers/soundwire/master.c +++ b/drivers/soundwire/master.c @@ -154,6 +154,7 @@ int sdw_master_device_add(struct sdw_bus *bus, struct device *parent, bus->dev = &md->dev; bus->md = md; + pm_runtime_enable(&bus->md->dev); device_register_err: return ret; } @@ -166,6 +167,7 @@ device_register_err: */ 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 index 4ae62b452b8c..55a9c51c84c1 100644 --- a/drivers/soundwire/mipi_disco.c +++ b/drivers/soundwire/mipi_disco.c @@ -289,7 +289,7 @@ 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 num_of_ports, nval, i, dp0 = 0; + int nval; device_property_read_u32(dev, "mipi-sdw-sw-interface-revision", &prop->mipi_revision); @@ -352,7 +352,6 @@ int sdw_slave_read_prop(struct sdw_slave *slave) return -ENOMEM; sdw_slave_read_dp0(slave, port, prop->dp0_prop); - dp0 = 1; } /* @@ -383,21 +382,6 @@ int sdw_slave_read_prop(struct sdw_slave *slave) sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval, prop->sink_ports, "sink"); - /* some ports are bidirectional so check total ports by ORing */ - nval = prop->source_ports | prop->sink_ports; - num_of_ports = hweight32(nval) + dp0; /* add DP0 */ - - /* Allocate port_ready based on num_of_ports */ - slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports, - sizeof(*slave->port_ready), - GFP_KERNEL); - if (!slave->port_ready) - return -ENOMEM; - - /* Initialize completion */ - for (i = 0; i < num_of_ports; i++) - init_completion(&slave->port_ready[i]); - return 0; } EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 915c2cf0c274..fbca4ebf63e9 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -34,6 +34,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 @@ -43,19 +44,17 @@ #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_ROW_CTRL_SHFT 3 #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_FRAME_CTRL_BANK_COL_CTRL_SHFT 0 #define SWRM_MCP_CFG_ADDR 0x1048 #define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK GENMASK(21, 17) -#define SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT 0x11 #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 @@ -67,11 +66,6 @@ #define SWRM_REG_VAL_PACK(data, dev, id, reg) \ ((reg) | ((id) << 16) | ((dev) << 20) | ((data) << 24)) -#define SWRM_MAX_ROW_VAL 0 /* Rows = 48 */ -#define SWRM_DEFAULT_ROWS 48 -#define SWRM_MIN_COL_VAL 0 /* Cols = 2 */ -#define SWRM_DEFAULT_COL 16 -#define SWRM_MAX_COL_VAL 7 #define SWRM_SPECIAL_CMD_ID 0xF #define MAX_FREQ_NUM 1 #define TIMEOUT_MS (2 * HZ) @@ -84,12 +78,14 @@ 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 */ @@ -103,6 +99,8 @@ struct qcom_swrm_ctrl { 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]; @@ -112,9 +110,24 @@ struct qcom_swrm_ctrl { 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_abh_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, +static int qcom_swrm_ahb_reg_read(struct qcom_swrm_ctrl *ctrl, int reg, u32 *val) { struct regmap *wcd_regmap = ctrl->regmap; @@ -154,6 +167,20 @@ static int qcom_swrm_ahb_reg_write(struct qcom_swrm_ctrl *ctrl, 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) { @@ -284,8 +311,8 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) u32 val; /* Clear Rows and Cols */ - val = (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT | - SWRM_MIN_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT); + 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); @@ -298,9 +325,7 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) /* Configure No pings */ ctrl->reg_read(ctrl, SWRM_MCP_CFG_ADDR, &val); - val &= ~SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_BMSK; - val |= (SWRM_DEF_CMD_NO_PINGS << - SWRM_MCP_CFG_MAX_NUM_OF_CMD_NO_PINGS_SHFT); + 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 */ @@ -310,6 +335,12 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) 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; } @@ -355,11 +386,8 @@ static int qcom_swrm_pre_bank_switch(struct sdw_bus *bus) ctrl->reg_read(ctrl, reg, &val); - val &= ~SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_BMSK; - val &= ~SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_BMSK; - - val |= (SWRM_MAX_ROW_VAL << SWRM_MCP_FRAME_CTRL_BANK_ROW_CTRL_SHFT | - SWRM_MAX_COL_VAL << SWRM_MCP_FRAME_CTRL_BANK_COL_CTRL_SHFT); + 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); } @@ -378,14 +406,22 @@ static int qcom_swrm_transport_params(struct sdw_bus *bus, { 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; - return ctrl->reg_write(ctrl, - SWRM_DP_PORT_CTRL_BANK((params->port_num), bank), - value); + 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, @@ -433,6 +469,7 @@ static int qcom_swrm_compute_params(struct sdw_bus *bus) 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) { @@ -443,6 +480,7 @@ static int qcom_swrm_compute_params(struct sdw_bus *bus) 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++; } } @@ -689,12 +727,13 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl) 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 = val & SWRM_COMP_PARAMS_DOUT_PORTS_MASK; - ctrl->num_din_ports = (val & SWRM_COMP_PARAMS_DIN_PORTS_MASK) >> 5; + 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) @@ -731,10 +770,13 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl) 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; @@ -746,6 +788,7 @@ static int qcom_swrm_probe(struct platform_device *pdev) 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; @@ -753,15 +796,25 @@ static int qcom_swrm_probe(struct platform_device *pdev) 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_ENABLED(CONFIG_SLIMBUS) if (dev->parent->bus == &slimbus_bus) { - ctrl->reg_read = qcom_swrm_abh_reg_read; +#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 { - /* Only WCD based SoundWire controller is supported */ - return -ENOTSUPP; + 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); @@ -795,8 +848,8 @@ static int qcom_swrm_probe(struct platform_device *pdev) params = &ctrl->bus.params; params->max_dr_freq = DEFAULT_CLK_FREQ; params->curr_dr_freq = DEFAULT_CLK_FREQ; - params->col = SWRM_DEFAULT_COL; - params->row = SWRM_DEFAULT_ROWS; + 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; @@ -806,8 +859,8 @@ static int qcom_swrm_probe(struct platform_device *pdev) prop->num_clk_gears = 0; prop->num_clk_freq = MAX_FREQ_NUM; prop->clk_freq = &qcom_swrm_freq_tbl[0]; - prop->default_col = SWRM_DEFAULT_COL; - prop->default_row = SWRM_DEFAULT_ROWS; + prop->default_col = data->default_cols; + prop->default_row = data->default_rows; ctrl->reg_read(ctrl, SWRM_COMP_HW_VERSION, &ctrl->version); @@ -858,7 +911,8 @@ static int qcom_swrm_remove(struct platform_device *pdev) } static const struct of_device_id qcom_swrm_of_match[] = { - { .compatible = "qcom,soundwire-v1.3.0", }, + { .compatible = "qcom,soundwire-v1.3.0", .data = &swrm_v1_3_data }, + { .compatible = "qcom,soundwire-v1.5.1", .data = &swrm_v1_5_data }, {/* sentinel */}, }; diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 0839445ee07b..a08f4081c1c4 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -6,6 +6,7 @@ #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) { @@ -20,11 +21,12 @@ struct device_type sdw_slave_type = { .uevent = sdw_slave_uevent, }; -static int sdw_slave_add(struct sdw_bus *bus, - struct sdw_slave_id *id, struct fwnode_handle *fwnode) +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) @@ -50,6 +52,7 @@ static int sdw_slave_add(struct sdw_bus *bus, 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); @@ -57,6 +60,10 @@ static int sdw_slave_add(struct sdw_bus *bus, 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); @@ -102,7 +109,7 @@ static bool find_slave(struct sdw_bus *bus, } /* Extract link id from ADR, Bit 51 to 48 (included) */ - link_id = (addr >> 48) & GENMASK(3, 0); + link_id = SDW_DISCO_LINK_ID(addr); /* Check for link_id match */ if (link_id != bus->link_id) diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 6e36deb505b1..1099b5d1262b 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -25,8 +25,10 @@ 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) { @@ -100,9 +102,7 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus, return ret; /* Program DPN_SampleCtrl2 register */ - wbuf = (t_params->sample_interval - 1); - wbuf &= SDW_DPN_SAMPLECTRL_HIGH; - wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH); + wbuf = FIELD_GET(SDW_DPN_SAMPLECTRL_HIGH, t_params->sample_interval - 1); ret = sdw_write(slave, addr3, wbuf); if (ret < 0) { @@ -111,9 +111,8 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus, } /* Program DPN_HCtrl register */ - wbuf = t_params->hstart; - wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART); - wbuf |= t_params->hstop; + 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) @@ -157,8 +156,8 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, } /* Program DPN_PortCtrl register */ - wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE); - wbuf |= p_params->flow_mode; + 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) { @@ -444,7 +443,8 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, prep_ch.bank = bus->params.next_bank; - if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm) + if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm || + bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) intr = true; /* @@ -689,9 +689,9 @@ static int sdw_bank_switch(struct sdw_bus *bus, int m_rt_count) /* * Set the multi_link flag only when both the hardware supports - * and there is a stream handled by multiple masters + * and hardware-based sync is required */ - multi_link = bus->multi_link && (m_rt_count > 1); + 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); @@ -761,13 +761,16 @@ static int do_bank_switch(struct sdw_stream_runtime *stream) 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) { + if (bus->multi_link && m_rt_count >= bus->hw_sync_min_links) { multi_link = true; mutex_lock(&bus->msg_lock); } @@ -788,7 +791,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream) * synchronized across all Masters and happens later as a * part of post_bank_switch ops. */ - ret = sdw_bank_switch(bus, stream->m_rt_count); + ret = sdw_bank_switch(bus, m_rt_count); if (ret < 0) { dev_err(bus->dev, "Bank switch failed: %d\n", ret); goto error; @@ -814,7 +817,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream) ret); goto error; } - } else if (bus->multi_link && stream->m_rt_count > 1) { + } else if (multi_link) { dev_err(bus->dev, "Post bank switch ops not implemented\n"); goto error; @@ -832,7 +835,7 @@ static int do_bank_switch(struct sdw_stream_runtime *stream) goto error; } - if (bus->multi_link) + if (multi_link) mutex_unlock(&bus->msg_lock); } @@ -1784,6 +1787,16 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) 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) { @@ -1913,7 +1926,7 @@ void sdw_shutdown_stream(void *sdw_substream) sdw_stream = snd_soc_dai_get_sdw_stream(dai, substream->stream); - if (!sdw_stream) { + if (IS_ERR(sdw_stream)) { dev_err(rtd->dev, "no stream found for DAI %s", dai->name); return; } diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h index ff60adee3c41..7268bc24c538 100644 --- a/drivers/soundwire/sysfs_local.h +++ b/drivers/soundwire/sysfs_local.h @@ -8,6 +8,10 @@ * 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); diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c index f510071b0add..b48b6617a396 100644 --- a/drivers/soundwire/sysfs_slave.c +++ b/drivers/soundwire/sysfs_slave.c @@ -16,9 +16,13 @@ /* * The sysfs for Slave reflects the MIPI description as given - * in the MIPI DisCo spec + * 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 @@ -212,3 +216,55 @@ int sdw_slave_sysfs_init(struct sdw_slave *slave) 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 +}; |