[PATCH V6 0/8] Add SoundWire support for AMD platforms
ACP IP(v6.x) block has two SoundWire manager instance support. This patchset adds support for AMD SoundWire manager driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
changes since v5: - replace loops logic with read_poll_timeout() throughout the code.
changes since v4: - fix nit-picks in the code. - update naming convention for control word and response buffer vairables. - drop pm_suspend check in prepare callback. - use return statement instead of returning ret variable.
changes since v3: - add usleep_range() in command/response implementation. - add usleep_range() in clock stop sequence. - modify usleep_range() values throughout the code. - remove unncessary debug statement from code. - update comment in probe() call. - fix timeout condition checks in the code. - drop "ret" variable in amd_disable_sdw_manager().
changes since v2: - Remove useless variable initializations. - Add helper function to interpret peripheral status. - Move runtime pm sequence to probe_work workqueue. - Use string "SoundWire" instead of "soundwire" in code. - Update comments in interrupt handler and probe sequence. - Rename "sdw_lock" as "acp_sdw_lock". - Remove __func__ from dev_dbg statements.
changes since v1: - Drop asoc tree based patches. will send asoc patches as a separate series. - Fixed double space errors. - Use dev instead of pci->dev. - Use SoundWire manager terminology. - Remove amd_sdw_compute_slave_ports() function and use exported sdw_compute_slave_ports() function. - Remove unused variable "num_ports" from amd_manager structure. - Drop startup and shutdown dai callbacks. - Drop reset_page_addr callback. - Use relative address offset to program SoundWire manager registers throughout the code. - Separate wake enable interrupt handling from slave status handling logic. - Use acp_mmio to program ACP common registers. - Use dai_runtime_array implementation in dai_ops. - Refactor port_ops callbacks. - Add comments in port_ops callbacks. - Add retry count logic in irq thread to address faulty case. - Add helper function to interpret command response. - Add generic bandwidth allocation dependency in Kconfig options. - Add comments for AMD SoundWire power modes. - Add missing timeout check in amd_init_sdw_manager callback. - Declare frameshape parameters in probe call. - Handle error case in clock stop sequence. - Add comments in pm_prepare and pm_ops callbacks.
Vijendar Mukunda (8): soundwire: export sdw_compute_slave_ports() function soundwire: amd: Add support for AMD Manager driver soundwire: amd: register SoundWire manager dai ops soundwire: amd: enable build for AMD SoundWire manager driver soundwire: amd: add SoundWire manager interrupt handling soundwire: amd: add runtime pm ops for AMD SoundWire manager driver soundwire: amd: handle SoundWire wake enable interrupt soundwire: amd: add pm_prepare callback and pm ops support
drivers/soundwire/Kconfig | 10 + drivers/soundwire/Makefile | 4 + drivers/soundwire/amd_manager.c | 1245 +++++++++++++++++ drivers/soundwire/amd_manager.h | 274 ++++ drivers/soundwire/bus.h | 9 + .../soundwire/generic_bandwidth_allocation.c | 12 +- include/linux/soundwire/sdw_amd.h | 109 ++ 7 files changed, 1654 insertions(+), 9 deletions(-) create mode 100644 drivers/soundwire/amd_manager.c create mode 100644 drivers/soundwire/amd_manager.h create mode 100644 include/linux/soundwire/sdw_amd.h
Export sdw_compute_slave_ports() function to use it in another soundwire manager module. Move sdw_transport_data structure to bus header file to export sdw_compute_slave_ports() function.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230201165944.3169125-1-Vijendar.Mukunda@amd.c... --- drivers/soundwire/bus.h | 9 +++++++++ drivers/soundwire/generic_bandwidth_allocation.c | 12 +++--------- 2 files changed, 12 insertions(+), 9 deletions(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 96927a143796..0ea0412842c5 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -144,6 +144,13 @@ struct sdw_master_runtime { struct list_head bus_node; };
+struct sdw_transport_data { + int hstart; + int hstop; + int block_offset; + int sub_block_offset; +}; + struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, enum sdw_data_direction direction, unsigned int port_num); @@ -212,5 +219,7 @@ int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 val
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); +void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, + struct sdw_transport_data *t_data);
#endif /* __SDW_BUS_H */ diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index f7c66083a4dd..39543048baa7 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -28,15 +28,8 @@ struct sdw_group { 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) +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; @@ -85,6 +78,7 @@ static void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, } } } +EXPORT_SYMBOL(sdw_compute_slave_ports);
static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, struct sdw_group_params *params,
AMD ACP(v6.x) IP block has two SoundWire manager devices. Add support for - Manager driver probe & remove sequence - Helper functions to enable/disable interrupts, Initialize sdw manager, enable sdw pads - Manager driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_manager.c | 698 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 251 +++++++++++ include/linux/soundwire/sdw_amd.h | 67 +++ 3 files changed, 1016 insertions(+) create mode 100644 drivers/soundwire/amd_manager.c create mode 100644 drivers/soundwire/amd_manager.h create mode 100644 include/linux/soundwire/sdw_amd.h
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c new file mode 100644 index 000000000000..a5cf6acd936c --- /dev/null +++ b/drivers/soundwire/amd_manager.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * SoundWire AMD Manager driver + * + * Copyright 2023 Advanced Micro Devices, Inc. + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_manager.h" + +#define DRV_NAME "amd_sdw_manager" + +#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus) + +static void amd_enable_sdw_pads(struct amd_sdw_manager *amd_manager) +{ + u32 sw_pad_pulldown_val; + u32 val; + + mutex_lock(amd_manager->acp_sdw_lock); + val = acp_reg_readl(amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN); + val |= amd_manager->reg_mask->sw_pad_enable_mask; + acp_reg_writel(val, amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN); + usleep_range(1000, 1500); + + sw_pad_pulldown_val = acp_reg_readl(amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL); + sw_pad_pulldown_val &= amd_manager->reg_mask->sw_pad_pulldown_mask; + acp_reg_writel(sw_pad_pulldown_val, amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL); + mutex_unlock(amd_manager->acp_sdw_lock); +} + +static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager) +{ + u32 val; + int ret; + + acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN); + ret = read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_EN_STATUS); + if (ret) + return ret; + + /* SoundWire manager bus reset */ + acp_reg_writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL); + ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US, + AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL); + if (ret) + return ret; + + acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL); + ret = read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_BUS_RESET_CTRL); + if (ret) { + dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n", + amd_manager->instance); + return ret; + } + + acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN); + return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_EN_STATUS); +} + +static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager) +{ + u32 val; + + acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN); + return read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_EN_STATUS); +} + +static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager) +{ + u32 val; + + acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN); + /* + * After invoking manager disable sequence, check whether + * manager has executed clock stop sequence. In this case, + * manager should ignore checking enable status register. + */ + val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + if (val) + return 0; + return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_EN_STATUS); +} + +static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{ + struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask; + u32 val; + + mutex_lock(amd_manager->acp_sdw_lock); + val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + val |= reg_mask->acp_sdw_intr_mask; + acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + mutex_unlock(amd_manager->acp_sdw_lock); + + acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); + acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); + acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK); +} + +static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{ + struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask; + u32 val; + + mutex_lock(amd_manager->acp_sdw_lock); + val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + val &= ~reg_mask->acp_sdw_intr_mask; + acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + mutex_unlock(amd_manager->acp_sdw_lock); + + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK); +} + +static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) +{ + u32 frame_size; + + frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index; + acp_reg_writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE); +} + +static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, u32 cmd_type, + struct sdw_msg *msg, int cmd_offset) +{ + u32 upper_data; + u32 lower_data = 0; + u16 addr; + u8 addr_upper, addr_lower; + u8 data = 0; + + addr = msg->addr + cmd_offset; + addr_upper = (addr & 0xFF00) >> 8; + addr_lower = addr & 0xFF; + + if (cmd_type == AMD_SDW_CMD_WRITE) + data = msg->buf[cmd_offset]; + + upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num); + upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type); + upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_upper); + lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_lower); + lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data); + + *upper_word = upper_data; + *lower_word = lower_data; +} + +static u64 amd_sdw_send_cmd_get_resp(struct amd_sdw_manager *amd_manager, u32 lower_word, + u32 upper_word) +{ + u64 resp; + u32 resp_lower, resp_upper; + u32 sts; + int ret; + + ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_CMD_BUSY), ACP_DELAY_US, + AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS); + if (ret) { + dev_err(amd_manager->dev, "SDW%x previous cmd status clear failed\n", + amd_manager->instance); + return ret; + } + + if (sts & AMD_SDW_IMM_RES_VALID) { + dev_err(amd_manager->dev, "SDW%x manager is in bad state\n", amd_manager->instance); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_IMM_CMD_STS); + } + acp_reg_writel(upper_word, amd_manager->mmio + ACP_SW_IMM_CMD_UPPER_WORD); + acp_reg_writel(lower_word, amd_manager->mmio + ACP_SW_IMM_CMD_LOWER_QWORD); + + ret = read_poll_timeout(acp_reg_readl, sts, (sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, + AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS); + if (ret) { + dev_err(amd_manager->dev, "SDW%x cmd response timeout occurred\n", + amd_manager->instance); + return ret; + } + resp_upper = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_UPPER_WORD); + resp_lower = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_LOWER_QWORD); + + acp_reg_writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP_SW_IMM_CMD_STS); + ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US, + AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS); + if (ret) { + dev_err(amd_manager->dev, "SDW%x cmd status retry failed\n", + amd_manager->instance); + return ret; + } + resp = resp_upper; + resp = (resp << 32) | resp_lower; + return resp; +} + +static enum sdw_command_response +amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg) +{ + struct sdw_msg scp_msg = {0}; + u64 response_buf[2] = {0}; + u32 upper_word = 0, lower_word = 0; + int nack = 0, no_ack = 0; + int index, timeout = 0; + + scp_msg.dev_num = msg->dev_num; + scp_msg.addr = SDW_SCP_ADDRPAGE1; + scp_msg.buf = &msg->addr_page1; + amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0); + response_buf[0] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word); + scp_msg.addr = SDW_SCP_ADDRPAGE2; + scp_msg.buf = &msg->addr_page2; + amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0); + response_buf[1] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word); + + for (index = 0; index < 2; index++) { + if (response_buf[index] == -ETIMEDOUT) { + dev_err(amd_manager->dev, "Program SCP cmd timeout\n"); + timeout = 1; + } else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) { + no_ack = 1; + if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) { + nack = 1; + dev_err(amd_manager->dev, "Program SCP NACK received\n"); + } + } + } + + if (timeout) { + dev_err_ratelimited(amd_manager->dev, + "SCP_addrpage command timeout for Slave %d\n", msg->dev_num); + return SDW_CMD_TIMEOUT; + } + + if (nack) { + dev_err_ratelimited(amd_manager->dev, + "SCP_addrpage NACKed for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_dbg_ratelimited(amd_manager->dev, + "SCP_addrpage ignored for Slave %d\n", msg->dev_num); + return SDW_CMD_IGNORED; + } + return SDW_CMD_OK; +} + +static int amd_prep_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg, int *cmd) +{ + int ret; + + if (msg->page) { + ret = amd_program_scp_addr(amd_manager, msg); + if (ret) { + msg->len = 0; + return ret; + } + } + switch (msg->flags) { + case SDW_MSG_FLAG_READ: + *cmd = AMD_SDW_CMD_READ; + break; + case SDW_MSG_FLAG_WRITE: + *cmd = AMD_SDW_CMD_WRITE; + break; + default: + dev_err(amd_manager->dev, "Invalid msg cmd: %d\n", msg->flags); + return -EINVAL; + } + return 0; +} + +static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *amd_manager, + struct sdw_msg *msg, u64 response, + int offset) +{ + int nack = 0, no_ack = 0; + int timeout = 0; + + if (response & AMD_SDW_MCP_RESP_ACK) { + if (msg->flags == SDW_MSG_FLAG_READ) + msg->buf[offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response); + } else { + no_ack = 1; + if (response == -ETIMEDOUT) { + timeout = 1; + } else if (response & AMD_SDW_MCP_RESP_NACK) { + nack = 1; + dev_err(amd_manager->dev, "Program SCP NACK received\n"); + } + } + + if (timeout) { + dev_err_ratelimited(amd_manager->dev, "command timeout for Slave %d\n", + msg->dev_num); + return SDW_CMD_TIMEOUT; + } + if (nack) { + dev_err_ratelimited(amd_manager->dev, + "command response NACK received for Slave %d\n", msg->dev_num); + return SDW_CMD_FAIL; + } + + if (no_ack) { + dev_err_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n", + msg->dev_num); + return SDW_CMD_IGNORED; + } + return SDW_CMD_OK; +} + +static unsigned int _amd_sdw_xfer_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg, + int cmd, int cmd_offset) +{ + u64 response; + u32 upper_word = 0, lower_word = 0; + + amd_sdw_ctl_word_prep(&lower_word, &upper_word, cmd, msg, cmd_offset); + response = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word); + return amd_sdw_fill_msg_resp(amd_manager, msg, response, cmd_offset); +} + +static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + int ret, i; + int cmd = 0; + + ret = amd_prep_msg(amd_manager, msg, &cmd); + if (ret) + return SDW_CMD_FAIL_OTHER; + for (i = 0; i < msg->len; i++) { + ret = _amd_sdw_xfer_msg(amd_manager, msg, cmd, i); + if (ret) + return ret; + } + return SDW_CMD_OK; +} + +static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + u64 response; + u32 slave_stat; + + response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0); + /* slave status from ping response */ + slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response); + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; + dev_dbg(amd_manager->dev, "slave_stat:0x%x\n", slave_stat); + return slave_stat; +} + +static int amd_sdw_compute_params(struct sdw_bus *bus) +{ + struct sdw_transport_data t_data = {0}; + struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + struct sdw_bus_params *b_params = &bus->params; + int port_bo, hstart, hstop, sample_int; + unsigned int rate, bps; + + port_bo = 0; + hstart = 1; + hstop = bus->params.col - 1; + t_data.hstop = hstop; + t_data.hstart = hstart; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + sample_int = (bus->params.curr_dr_freq / rate); + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + port_bo = (p_rt->num * 64) + 1; + dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n", + p_rt->num, hstart, hstop, port_bo); + 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_PKG_PER_PORT, 0x0); + + sdw_fill_port_params(&p_rt->port_params, + p_rt->num, bps, + SDW_PORT_FLOW_MODE_ISOCH, + b_params->m_data_mode); + t_data.hstart = hstart; + t_data.hstop = hstop; + t_data.block_offset = port_bo; + t_data.sub_block_offset = 0; + } + sdw_compute_slave_ports(m_rt, &t_data); + } + return 0; +} + +static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params, + unsigned int bank) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + u32 frame_fmt_reg, dpn_frame_fmt; + + dev_dbg(amd_manager->dev, "p_params->num:0x%x\n", p_params->num); + switch (amd_manager->instance) { + case ACP_SDW0: + frame_fmt_reg = sdw0_manager_dp_reg[p_params->num].frame_fmt_reg; + break; + case ACP_SDW1: + frame_fmt_reg = sdw1_manager_dp_reg[p_params->num].frame_fmt_reg; + break; + default: + return -EINVAL; + } + + dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg); + u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM); + u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM); + u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN); + acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg); + return 0; +} + +static int amd_sdw_transport_params(struct sdw_bus *bus, + struct sdw_transport_params *params, + enum sdw_reg_bank bank) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + u32 dpn_frame_fmt; + u32 dpn_sampleinterval; + u32 dpn_hctrl; + u32 dpn_offsetctrl; + u32 dpn_lanectrl; + u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg; + u32 offset_reg, lane_ctrl_ch_en_reg; + + switch (amd_manager->instance) { + case ACP_SDW0: + frame_fmt_reg = sdw0_manager_dp_reg[params->port_num].frame_fmt_reg; + sample_int_reg = sdw0_manager_dp_reg[params->port_num].sample_int_reg; + hctrl_dp0_reg = sdw0_manager_dp_reg[params->port_num].hctrl_dp0_reg; + offset_reg = sdw0_manager_dp_reg[params->port_num].offset_reg; + lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg; + break; + case ACP_SDW1: + frame_fmt_reg = sdw1_manager_dp_reg[params->port_num].frame_fmt_reg; + sample_int_reg = sdw1_manager_dp_reg[params->port_num].sample_int_reg; + hctrl_dp0_reg = sdw1_manager_dp_reg[params->port_num].hctrl_dp0_reg; + offset_reg = sdw1_manager_dp_reg[params->port_num].offset_reg; + lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg; + break; + default: + return -EINVAL; + } + acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP_SW_SSP_COUNTER); + + dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg); + u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE); + u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL); + u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM); + acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg); + + dpn_sampleinterval = params->sample_interval - 1; + acp_reg_writel(dpn_sampleinterval, amd_manager->mmio + sample_int_reg); + + dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop); + dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart); + acp_reg_writel(dpn_hctrl, amd_manager->mmio + hctrl_dp0_reg); + + dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1); + dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2); + acp_reg_writel(dpn_offsetctrl, amd_manager->mmio + offset_reg); + + /* + * lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask + * parameters. + */ + dpn_lanectrl = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg); + u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL); + acp_reg_writel(dpn_lanectrl, amd_manager->mmio + lane_ctrl_ch_en_reg); + return 0; +} + +static int amd_sdw_port_enable(struct sdw_bus *bus, + struct sdw_enable_ch *enable_ch, + unsigned int bank) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + u32 dpn_ch_enable; + u32 lane_ctrl_ch_en_reg; + + switch (amd_manager->instance) { + case ACP_SDW0: + lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; + break; + case ACP_SDW1: + lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; + break; + default: + return -EINVAL; + } + + /* + * lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask + * parameters. + */ + dpn_ch_enable = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg); + u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK); + if (enable_ch->enable) + acp_reg_writel(dpn_ch_enable, amd_manager->mmio + lane_ctrl_ch_en_reg); + else + acp_reg_writel(0, amd_manager->mmio + lane_ctrl_ch_en_reg); + return 0; +} + +static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{ + struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); + struct fwnode_handle *link; + struct sdw_master_prop *prop; + u32 quirk_mask = 0; + u32 wake_en_mask = 0; + u32 power_mode_mask = 0; + char name[32]; + + prop = &bus->prop; + /* Find manager 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, "Manager node %s not found\n", name); + return -EIO; + } + fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask); + if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE)) + prop->hw_disabled = true; + prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH | + SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY; + + fwnode_property_read_u32(link, "amd-sdw-wakeup-enable", &wake_en_mask); + amd_manager->wake_en_mask = wake_en_mask; + fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask); + amd_manager->power_mode_mask = power_mode_mask; + return 0; +} + +static int amd_prop_read(struct sdw_bus *bus) +{ + sdw_master_read_prop(bus); + sdw_master_read_amd_prop(bus); + return 0; +} + +static const struct sdw_master_port_ops amd_sdw_port_ops = { + .dpn_set_port_params = amd_sdw_port_params, + .dpn_set_port_transport_params = amd_sdw_transport_params, + .dpn_port_enable_ch = amd_sdw_port_enable, +}; + +static const struct sdw_master_ops amd_sdw_ops = { + .read_prop = amd_prop_read, + .xfer_msg = amd_sdw_xfer_msg, + .read_ping_status = amd_sdw_read_ping_status, +}; + +static void amd_sdw_probe_work(struct work_struct *work) +{ + struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, + probe_work); + struct sdw_master_prop *prop; + int ret; + + prop = &amd_manager->bus.prop; + if (!prop->hw_disabled) { + amd_enable_sdw_pads(amd_manager); + ret = amd_init_sdw_manager(amd_manager); + if (ret) + return; + amd_enable_sdw_interrupts(amd_manager); + ret = amd_enable_sdw_manager(amd_manager); + if (ret) + return; + amd_sdw_set_frameshape(amd_manager); + } +} + +static int amd_sdw_manager_probe(struct platform_device *pdev) +{ + const struct acp_sdw_pdata *pdata = pdev->dev.platform_data; + struct resource *res; + struct device *dev = &pdev->dev; + struct sdw_master_prop *prop; + struct sdw_bus_params *params; + struct amd_sdw_manager *amd_manager; + int ret; + + amd_manager = devm_kzalloc(dev, sizeof(struct amd_sdw_manager), GFP_KERNEL); + if (!amd_manager) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOMEM; + + amd_manager->acp_mmio = devm_ioremap(dev, res->start, resource_size(res)); + if (IS_ERR(amd_manager->mmio)) { + dev_err(dev, "mmio not found\n"); + return PTR_ERR(amd_manager->mmio); + } + amd_manager->instance = pdata->instance; + amd_manager->mmio = amd_manager->acp_mmio + + (amd_manager->instance * SDW_MANAGER_REG_OFFSET); + amd_manager->acp_sdw_lock = pdata->acp_sdw_lock; + amd_manager->cols_index = sdw_find_col_index(AMD_SDW_DEFAULT_COLUMNS); + amd_manager->rows_index = sdw_find_row_index(AMD_SDW_DEFAULT_ROWS); + amd_manager->dev = dev; + amd_manager->bus.ops = &amd_sdw_ops; + amd_manager->bus.port_ops = &amd_sdw_port_ops; + amd_manager->bus.compute_params = &amd_sdw_compute_params; + amd_manager->bus.clk_stop_timeout = 200; + amd_manager->bus.link_id = amd_manager->instance; + switch (amd_manager->instance) { + case ACP_SDW0: + amd_manager->num_dout_ports = AMD_SDW0_MAX_TX_PORTS; + amd_manager->num_din_ports = AMD_SDW0_MAX_RX_PORTS; + break; + case ACP_SDW1: + amd_manager->num_dout_ports = AMD_SDW1_MAX_TX_PORTS; + amd_manager->num_din_ports = AMD_SDW1_MAX_RX_PORTS; + break; + default: + return -EINVAL; + } + amd_manager->reg_mask = &sdw_manager_reg_mask_array[amd_manager->instance]; + params = &amd_manager->bus.params; + params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2; + params->col = AMD_SDW_DEFAULT_COLUMNS; + params->row = AMD_SDW_DEFAULT_ROWS; + prop = &amd_manager->bus.prop; + prop->clk_freq = &amd_sdw_freq_tbl[0]; + prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ; + + ret = sdw_bus_master_add(&amd_manager->bus, dev, dev->fwnode); + if (ret) { + dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret); + return ret; + } + dev_set_drvdata(dev, amd_manager); + INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); + /* + * Instead of having lengthy probe sequence, use deferred probe. + */ + schedule_work(&amd_manager->probe_work); + return 0; +} + +static int amd_sdw_manager_remove(struct platform_device *pdev) +{ + struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev); + + cancel_work_sync(&amd_manager->probe_work); + amd_disable_sdw_interrupts(amd_manager); + sdw_bus_master_delete(&amd_manager->bus); + return amd_disable_sdw_manager(amd_manager); +} + +static struct platform_driver amd_sdw_driver = { + .probe = &amd_sdw_manager_probe, + .remove = &amd_sdw_manager_remove, + .driver = { + .name = "amd_sdw_manager", + } +}; +module_platform_driver(amd_sdw_driver); + +MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD SoundWire driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h new file mode 100644 index 000000000000..0d4b8653877e --- /dev/null +++ b/drivers/soundwire/amd_manager.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __AMD_MANAGER_H +#define __AMD_MANAGER_H + +#include <linux/soundwire/sdw_amd.h> + +#define SDW_MANAGER_REG_OFFSET 0xC00 +#define AMD_SDW_DEFAULT_ROWS 50 +#define AMD_SDW_DEFAULT_COLUMNS 10 +#define ACP_PAD_PULLDOWN_CTRL 0x0001448 +#define ACP_SW_PAD_KEEPER_EN 0x0001454 +#define ACP_SW0_WAKE_EN 0x0001458 +#define ACP_EXTERNAL_INTR_CNTL0 0x0001A04 +#define ACP_EXTERNAL_INTR_STAT0 0x0001A0C +#define ACP_EXTERNAL_INTR_CNTL(i) (ACP_EXTERNAL_INTR_CNTL0 + ((i) * 4)) +#define ACP_EXTERNAL_INTR_STAT(i) (ACP_EXTERNAL_INTR_STAT0 + ((i) * 4)) +#define ACP_SW_WAKE_EN(i) (ACP_SW0_WAKE_EN + ((i) * 8)) + +#define ACP_SW_EN 0x0003000 +#define ACP_SW_EN_STATUS 0x0003004 +#define ACP_SW_FRAMESIZE 0x0003008 +#define ACP_SW_SSP_COUNTER 0x000300C +#define ACP_SW_AUDIO0_TX_EN 0x0003010 +#define ACP_SW_AUDIO0_TX_EN_STATUS 0x0003014 +#define ACP_SW_AUDIO0_TX_FRAME_FORMAT 0x0003018 +#define ACP_SW_AUDIO0_TX_SAMPLEINTERVAL 0x000301C +#define ACP_SW_AUDIO0_TX_HCTRL_DP0 0x0003020 +#define ACP_SW_AUDIO0_TX_HCTRL_DP1 0x0003024 +#define ACP_SW_AUDIO0_TX_HCTRL_DP2 0x0003028 +#define ACP_SW_AUDIO0_TX_HCTRL_DP3 0x000302C +#define ACP_SW_AUDIO0_TX_OFFSET_DP0 0x0003030 +#define ACP_SW_AUDIO0_TX_OFFSET_DP1 0x0003034 +#define ACP_SW_AUDIO0_TX_OFFSET_DP2 0x0003038 +#define ACP_SW_AUDIO0_TX_OFFSET_DP3 0x000303C +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0 0x0003040 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP1 0x0003044 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP2 0x0003048 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP3 0x000304C +#define ACP_SW_AUDIO1_TX_EN 0x0003050 +#define ACP_SW_AUDIO1_TX_EN_STATUS 0x0003054 +#define ACP_SW_AUDIO1_TX_FRAME_FORMAT 0x0003058 +#define ACP_SW_AUDIO1_TX_SAMPLEINTERVAL 0x000305C +#define ACP_SW_AUDIO1_TX_HCTRL 0x0003060 +#define ACP_SW_AUDIO1_TX_OFFSET 0x0003064 +#define ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0 0x0003068 +#define ACP_SW_AUDIO2_TX_EN 0x000306C +#define ACP_SW_AUDIO2_TX_EN_STATUS 0x0003070 +#define ACP_SW_AUDIO2_TX_FRAME_FORMAT 0x0003074 +#define ACP_SW_AUDIO2_TX_SAMPLEINTERVAL 0x0003078 +#define ACP_SW_AUDIO2_TX_HCTRL 0x000307C +#define ACP_SW_AUDIO2_TX_OFFSET 0x0003080 +#define ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0 0x0003084 +#define ACP_SW_AUDIO0_RX_EN 0x0003088 +#define ACP_SW_AUDIO0_RX_EN_STATUS 0x000308C +#define ACP_SW_AUDIO0_RX_FRAME_FORMAT 0x0003090 +#define ACP_SW_AUDIO0_RX_SAMPLEINTERVAL 0x0003094 +#define ACP_SW_AUDIO0_RX_HCTRL_DP0 0x0003098 +#define ACP_SW_AUDIO0_RX_HCTRL_DP1 0x000309C +#define ACP_SW_AUDIO0_RX_HCTRL_DP2 0x0003100 +#define ACP_SW_AUDIO0_RX_HCTRL_DP3 0x0003104 +#define ACP_SW_AUDIO0_RX_OFFSET_DP0 0x0003108 +#define ACP_SW_AUDIO0_RX_OFFSET_DP1 0x000310C +#define ACP_SW_AUDIO0_RX_OFFSET_DP2 0x0003110 +#define ACP_SW_AUDIO0_RX_OFFSET_DP3 0x0003114 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0 0x0003118 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP1 0x000311C +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP2 0x0003120 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP3 0x0003124 +#define ACP_SW_AUDIO1_RX_EN 0x0003128 +#define ACP_SW_AUDIO1_RX_EN_STATUS 0x000312C +#define ACP_SW_AUDIO1_RX_FRAME_FORMAT 0x0003130 +#define ACP_SW_AUDIO1_RX_SAMPLEINTERVAL 0x0003134 +#define ACP_SW_AUDIO1_RX_HCTRL 0x0003138 +#define ACP_SW_AUDIO1_RX_OFFSET 0x000313C +#define ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0 0x0003140 +#define ACP_SW_AUDIO2_RX_EN 0x0003144 +#define ACP_SW_AUDIO2_RX_EN_STATUS 0x0003148 +#define ACP_SW_AUDIO2_RX_FRAME_FORMAT 0x000314C +#define ACP_SW_AUDIO2_RX_SAMPLEINTERVAL 0x0003150 +#define ACP_SW_AUDIO2_RX_HCTRL 0x0003154 +#define ACP_SW_AUDIO2_RX_OFFSET 0x0003158 +#define ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0 0x000315C +#define ACP_SW_BPT_PORT_EN 0x0003160 +#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164 +#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168 +#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316C +#define ACP_SW_BPT_PORT_HCTRL 0x0003170 +#define ACP_SW_BPT_PORT_OFFSET 0x0003174 +#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178 +#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317C +#define ACP_SW_CLK_RESUME_CTRL 0x0003180 +#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184 +#define ACP_SW_BUS_RESET_CTRL 0x0003188 +#define ACP_SW_PRBS_ERR_STATUS 0x000318C +#define ACP_SW_IMM_CMD_UPPER_WORD 0x0003230 +#define ACP_SW_IMM_CMD_LOWER_QWORD 0x0003234 +#define ACP_SW_IMM_RESP_UPPER_WORD 0x0003238 +#define ACP_SW_IMM_RESP_LOWER_QWORD 0x000323C +#define ACP_SW_IMM_CMD_STS 0x0003240 +#define ACP_SW_BRA_BASE_ADDRESS 0x0003244 +#define ACP_SW_BRA_TRANSFER_SIZE 0x0003248 +#define ACP_SW_BRA_DMA_BUSY 0x000324C +#define ACP_SW_BRA_RESP 0x0003250 +#define ACP_SW_BRA_RESP_FRAME_ADDR 0x0003254 +#define ACP_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258 +#define ACP_SW_STATE_CHANGE_STATUS_0TO7 0x000325C +#define ACP_SW_STATE_CHANGE_STATUS_8TO11 0x0003260 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268 +#define ACP_SW_CLK_FREQUENCY_CTRL 0x000326C +#define ACP_SW_ERROR_INTR_MASK 0x0003270 +#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274 + +#define ACP_DELAY_US 10 +#define AMD_SDW_TIMEOUT 1000 +#define AMD_SDW_DEFAULT_CLK_FREQ 12000000 + +#define AMD_SDW_MCP_RESP_ACK BIT(0) +#define AMD_SDW_MCP_RESP_NACK BIT(1) +#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7) + +#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31) +#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12) +#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8) +#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0) +#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24) +#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK_ULL(39, 24) +#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2) +#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK_ULL(15, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4)) +#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1) + +#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 2000 +#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0) + +#define AMD_SDW_IMM_RES_VALID 1 +#define AMD_SDW_IMM_CMD_BUSY 2 +#define AMD_SDW_ENABLE 1 +#define AMD_SDW_DISABLE 0 +#define AMD_SDW_BUS_RESET_CLEAR_REQ 0 +#define AMD_SDW_BUS_RESET_REQ 1 +#define AMD_SDW_BUS_RESET_DONE 2 +#define AMD_SDW_BUS_BASE_FREQ 24000000 + +#define AMD_SDW0_EXT_INTR_MASK 0x200000 +#define AMD_SDW1_EXT_INTR_MASK 4 +#define AMD_SDW_IRQ_MASK_0TO7 0x77777777 +#define AMD_SDW_IRQ_MASK_8TO11 0x000D7777 +#define AMD_SDW_IRQ_ERROR_MASK 0xFF +#define AMD_SDW_MAX_FREQ_NUM 1 +#define AMD_SDW0_MAX_TX_PORTS 3 +#define AMD_SDW0_MAX_RX_PORTS 3 +#define AMD_SDW1_MAX_TX_PORTS 1 +#define AMD_SDW1_MAX_RX_PORTS 1 +#define AMD_SDW0_MAX_DAI 6 +#define AMD_SDW1_MAX_DAI 2 +#define AMD_SDW_SLAVE_0_ATTACHED 5 +#define AMD_SDW_SSP_COUNTER_VAL 3 + +#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0) +#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2) +#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4) +#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5) +#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7) +#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13) +#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4) +#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8) +#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0) +#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3) +#define AMD_SDW_STAT_MAX_RETRY_COUNT 100 +#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7F9F +#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7FFA +#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60 +#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5 +#define AMD_SDW0_PAD_KEEPER_EN_MASK 1 +#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 +#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E +#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF + +enum amd_sdw_cmd_type { + AMD_SDW_CMD_PING = 0, + AMD_SDW_CMD_READ = 2, + AMD_SDW_CMD_WRITE = 3, +}; + +static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = { + AMD_SDW_DEFAULT_CLK_FREQ, +}; + +struct sdw_manager_dp_reg { + u32 frame_fmt_reg; + u32 sample_int_reg; + u32 hctrl_dp0_reg; + u32 offset_reg; + u32 lane_ctrl_ch_en_reg; +}; + +static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { + {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0, + ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL, + ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL, + ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0, + ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL, + ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL, + ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0}, +}; + +static struct sdw_manager_dp_reg sdw1_manager_dp_reg[AMD_SDW1_MAX_DAI] = { + {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL, + ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0}, + {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL, + ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0} +}; + +static struct sdw_manager_reg_mask sdw_manager_reg_mask_array[2] = { + { + AMD_SDW0_PAD_KEEPER_EN_MASK, + AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK, + AMD_SDW0_EXT_INTR_MASK + }, + { + AMD_SDW1_PAD_KEEPER_EN_MASK, + AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK, + AMD_SDW1_EXT_INTR_MASK + } +}; + +static inline u32 acp_reg_readl(void __iomem *base_addr) +{ + return readl(base_addr); +} + +static inline void acp_reg_writel(u32 val, void __iomem *base_addr) +{ + writel(val, base_addr); +} +#endif diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h new file mode 100644 index 000000000000..c14a291a40e8 --- /dev/null +++ b/include/linux/soundwire/sdw_amd.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved. + */ + +#ifndef __SDW_AMD_H +#define __SDW_AMD_H + +#include <linux/soundwire/sdw.h> + +#define ACP_SDW0 0 +#define ACP_SDW1 1 + +struct acp_sdw_pdata { + u16 instance; + /* mutex to protect acp common register access */ + struct mutex *acp_sdw_lock; +}; + +struct sdw_manager_reg_mask { + u32 sw_pad_enable_mask; + u32 sw_pad_pulldown_mask; + u32 acp_sdw_intr_mask; +}; + +/** + * struct amd_sdw_manager - amd manager driver context + * @bus: bus handle + * @dev: linux device + * @mmio: SoundWire registers mmio base + * @acp_mmio: acp registers mmio base + * @reg_mask: register mask structure per manager instance + * @probe_work: SoundWire manager probe workqueue + * @acp_sdw_lock: mutex to protect acp share register access + * @num_din_ports: number of input ports + * @num_dout_ports: number of output ports + * @cols_index: Column index in frame shape + * @rows_index: Rows index in frame shape + * @instance: SoundWire manager instance + * @quirks: SoundWire manager quirks + * @wake_en_mask: wake enable mask per SoundWire manager + * @power_mode_mask: flag interprets amd SoundWire manager power mode + */ +struct amd_sdw_manager { + struct sdw_bus bus; + struct device *dev; + + void __iomem *mmio; + void __iomem *acp_mmio; + + struct sdw_manager_reg_mask *reg_mask; + struct work_struct probe_work; + /* mutex to protect acp common register access */ + struct mutex *acp_sdw_lock; + + int num_din_ports; + int num_dout_ports; + + int cols_index; + int rows_index; + + u32 instance; + u32 quirks; + u32 wake_en_mask; + u32 power_mode_mask; +}; +#endif
+static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- int ret;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- ret = read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
- if (ret)
return ret;
- /* SoundWire manager bus reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret)
return ret;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret) {
dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
amd_manager->instance);
return ret;
- }
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
ironically the change to use read_poll_timeout makes the code less clear IMHO, specifically because the success criteria are
'val', 'val & AMD_SDW_BUS_RESET_DONE', '!val', '!val'
It's hard to review and hard to spot potential issues. You may want to add comments on what you are trying to check. Same comment for all the rest of the code.
+static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- /*
* After invoking manager disable sequence, check whether
* manager has executed clock stop sequence. In this case,
* manager should ignore checking enable status register.
*/
- val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
- if (val)
return 0;
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val |= reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val &= ~reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) +{
- u32 frame_size;
- frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
- acp_reg_writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE);
+}
+static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, u32 cmd_type,
struct sdw_msg *msg, int cmd_offset)
+{
- u32 upper_data;
- u32 lower_data = 0;
- u16 addr;
- u8 addr_upper, addr_lower;
nit-pick: use the same convention for data and addr, e.g. upper_data, upper_addr. Same comment for the rest of the code.
- u8 data = 0;
- addr = msg->addr + cmd_offset;
- addr_upper = (addr & 0xFF00) >> 8;
- addr_lower = addr & 0xFF;
- if (cmd_type == AMD_SDW_CMD_WRITE)
data = msg->buf[cmd_offset];
- upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_upper);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_lower);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
- *upper_word = upper_data;
- *lower_word = lower_data;
+}
On 07/03/23 20:55, Pierre-Louis Bossart wrote:
+static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- int ret;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- ret = read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
- if (ret)
return ret;
- /* SoundWire manager bus reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret)
return ret;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret) {
dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
amd_manager->instance);
return ret;
- }
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
ironically the change to use read_poll_timeout makes the code less clear IMHO, specifically because the success criteria are
'val', 'val & AMD_SDW_BUS_RESET_DONE', '!val', '!val'
It's hard to review and hard to spot potential issues. You may want to add comments on what you are trying to check. Same comment for all the rest of the code.
I don't think it's really required to add comments for read_poll_timeout() API everywhere in the code. 'val', 'val & AMD_SDW_BUS_RESET_DONE', '!val', '!val' all these are break condition checks for register read operations. Rather than, I am happy to explain if any read_poll_timeout() logic in our code, hard to review.
+static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- /*
* After invoking manager disable sequence, check whether
* manager has executed clock stop sequence. In this case,
* manager should ignore checking enable status register.
*/
- val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
- if (val)
return 0;
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val |= reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val &= ~reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) +{
- u32 frame_size;
- frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
- acp_reg_writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE);
+}
+static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, u32 cmd_type,
struct sdw_msg *msg, int cmd_offset)
+{
- u32 upper_data;
- u32 lower_data = 0;
- u16 addr;
- u8 addr_upper, addr_lower;
nit-pick: use the same convention for data and addr, e.g. upper_data, upper_addr. Same comment for the rest of the code.
As it's not breaking anything, will fix it in supplement patch.
- u8 data = 0;
- addr = msg->addr + cmd_offset;
- addr_upper = (addr & 0xFF00) >> 8;
- addr_lower = addr & 0xFF;
- if (cmd_type == AMD_SDW_CMD_WRITE)
data = msg->buf[cmd_offset];
- upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_upper);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_lower);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
- *upper_word = upper_data;
- *lower_word = lower_data;
+}
On 07-03-23, 19:01, Vijendar Mukunda wrote:
AMD ACP(v6.x) IP block has two SoundWire manager devices. Add support for
- Manager driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw manager, enable sdw pads
- Manager driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 698 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 251 +++++++++++ include/linux/soundwire/sdw_amd.h | 67 +++ 3 files changed, 1016 insertions(+) create mode 100644 drivers/soundwire/amd_manager.c create mode 100644 drivers/soundwire/amd_manager.h create mode 100644 include/linux/soundwire/sdw_amd.h
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c new file mode 100644 index 000000000000..a5cf6acd936c --- /dev/null +++ b/drivers/soundwire/amd_manager.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- SoundWire AMD Manager driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_manager.h"
+#define DRV_NAME "amd_sdw_manager"
+#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
+static void amd_enable_sdw_pads(struct amd_sdw_manager *amd_manager) +{
- u32 sw_pad_pulldown_val;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
- val |= amd_manager->reg_mask->sw_pad_enable_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
- usleep_range(1000, 1500);
- sw_pad_pulldown_val = acp_reg_readl(amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
- sw_pad_pulldown_val &= amd_manager->reg_mask->sw_pad_pulldown_mask;
- acp_reg_writel(sw_pad_pulldown_val, amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
- mutex_unlock(amd_manager->acp_sdw_lock);
+}
+static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- int ret;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- ret = read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
- if (ret)
return ret;
- /* SoundWire manager bus reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret)
return ret;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret) {
dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
amd_manager->instance);
return ret;
- }
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- /*
* After invoking manager disable sequence, check whether
* manager has executed clock stop sequence. In this case,
* manager should ignore checking enable status register.
*/
- val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
- if (val)
return 0;
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val |= reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val &= ~reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) +{
- u32 frame_size;
- frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
- acp_reg_writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE);
+}
+static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, u32 cmd_type,
struct sdw_msg *msg, int cmd_offset)
+{
- u32 upper_data;
- u32 lower_data = 0;
- u16 addr;
- u8 addr_upper, addr_lower;
- u8 data = 0;
- addr = msg->addr + cmd_offset;
- addr_upper = (addr & 0xFF00) >> 8;
- addr_lower = addr & 0xFF;
- if (cmd_type == AMD_SDW_CMD_WRITE)
data = msg->buf[cmd_offset];
- upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_upper);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_lower);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
- *upper_word = upper_data;
- *lower_word = lower_data;
+}
+static u64 amd_sdw_send_cmd_get_resp(struct amd_sdw_manager *amd_manager, u32 lower_word,
u32 upper_word)
+{
- u64 resp;
- u32 resp_lower, resp_upper;
- u32 sts;
- int ret;
- ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_CMD_BUSY), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x previous cmd status clear failed\n",
amd_manager->instance);
return ret;
- }
- if (sts & AMD_SDW_IMM_RES_VALID) {
dev_err(amd_manager->dev, "SDW%x manager is in bad state\n", amd_manager->instance);
acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- }
- acp_reg_writel(upper_word, amd_manager->mmio + ACP_SW_IMM_CMD_UPPER_WORD);
- acp_reg_writel(lower_word, amd_manager->mmio + ACP_SW_IMM_CMD_LOWER_QWORD);
- ret = read_poll_timeout(acp_reg_readl, sts, (sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x cmd response timeout occurred\n",
amd_manager->instance);
return ret;
- }
- resp_upper = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_UPPER_WORD);
- resp_lower = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_LOWER_QWORD);
- acp_reg_writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x cmd status retry failed\n",
amd_manager->instance);
return ret;
- }
- resp = resp_upper;
- resp = (resp << 32) | resp_lower;
- return resp;
+}
+static enum sdw_command_response +amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg) +{
- struct sdw_msg scp_msg = {0};
- u64 response_buf[2] = {0};
- u32 upper_word = 0, lower_word = 0;
- int nack = 0, no_ack = 0;
- int index, timeout = 0;
- scp_msg.dev_num = msg->dev_num;
- scp_msg.addr = SDW_SCP_ADDRPAGE1;
- scp_msg.buf = &msg->addr_page1;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[0] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- scp_msg.addr = SDW_SCP_ADDRPAGE2;
- scp_msg.buf = &msg->addr_page2;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[1] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(amd_manager->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(amd_manager->dev, "Program SCP NACK received\n");
}
}
- }
- if (timeout) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(amd_manager->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
+static int amd_prep_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg, int *cmd) +{
- int ret;
- if (msg->page) {
ret = amd_program_scp_addr(amd_manager, msg);
if (ret) {
msg->len = 0;
return ret;
}
- }
- switch (msg->flags) {
- case SDW_MSG_FLAG_READ:
*cmd = AMD_SDW_CMD_READ;
break;
- case SDW_MSG_FLAG_WRITE:
*cmd = AMD_SDW_CMD_WRITE;
break;
*cmd = msg->flags + 2;
that should work too
- default:
dev_err(amd_manager->dev, "Invalid msg cmd: %d\n", msg->flags);
return -EINVAL;
- }
- return 0;
+}
+static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *amd_manager,
struct sdw_msg *msg, u64 response,
int offset)
+{
- int nack = 0, no_ack = 0;
- int timeout = 0;
- if (response & AMD_SDW_MCP_RESP_ACK) {
if (msg->flags == SDW_MSG_FLAG_READ)
msg->buf[offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response);
- } else {
no_ack = 1;
if (response == -ETIMEDOUT) {
timeout = 1;
} else if (response & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(amd_manager->dev, "Program SCP NACK received\n");
}
- }
- if (timeout) {
dev_err_ratelimited(amd_manager->dev, "command timeout for Slave %d\n",
msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(amd_manager->dev,
"command response NACK received for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_err_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n",
msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
I would have coded this differently. we already know where we detect timeout or nack, so why not return the error from these places (it is exactly once). With that no_ack becomes remaining case!
+static unsigned int _amd_sdw_xfer_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg,
int cmd, int cmd_offset)
+{
- u64 response;
- u32 upper_word = 0, lower_word = 0;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, cmd, msg, cmd_offset);
- response = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- return amd_sdw_fill_msg_resp(amd_manager, msg, response, cmd_offset);
+}
+static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- int ret, i;
- int cmd = 0;
- ret = amd_prep_msg(amd_manager, msg, &cmd);
- if (ret)
return SDW_CMD_FAIL_OTHER;
- for (i = 0; i < msg->len; i++) {
ret = _amd_sdw_xfer_msg(amd_manager, msg, cmd, i);
if (ret)
return ret;
- }
- return SDW_CMD_OK;
+}
+static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u64 response;
- u32 slave_stat;
- response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
- /* slave status from ping response */
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(amd_manager->dev, "slave_stat:0x%x\n", slave_stat);
- return slave_stat;
+}
+static int amd_sdw_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
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_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
sdw_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
+static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(amd_manager->dev, "p_params->num:0x%x\n", p_params->num);
- switch (amd_manager->instance) {
- case ACP_SDW0:
frame_fmt_reg = sdw0_manager_dp_reg[p_params->num].frame_fmt_reg;
break;
- case ACP_SDW1:
frame_fmt_reg = sdw1_manager_dp_reg[p_params->num].frame_fmt_reg;
break;
- default:
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
- acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
- return 0;
+}
+static int amd_sdw_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_ch_en_reg;
- switch (amd_manager->instance) {
- case ACP_SDW0:
frame_fmt_reg = sdw0_manager_dp_reg[params->port_num].frame_fmt_reg;
sample_int_reg = sdw0_manager_dp_reg[params->port_num].sample_int_reg;
hctrl_dp0_reg = sdw0_manager_dp_reg[params->port_num].hctrl_dp0_reg;
offset_reg = sdw0_manager_dp_reg[params->port_num].offset_reg;
lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
break;
- case ACP_SDW1:
frame_fmt_reg = sdw1_manager_dp_reg[params->port_num].frame_fmt_reg;
sample_int_reg = sdw1_manager_dp_reg[params->port_num].sample_int_reg;
hctrl_dp0_reg = sdw1_manager_dp_reg[params->port_num].hctrl_dp0_reg;
offset_reg = sdw1_manager_dp_reg[params->port_num].offset_reg;
lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP_SW_SSP_COUNTER);
- dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL);
- u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
- acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
- dpn_sampleinterval = params->sample_interval - 1;
- acp_reg_writel(dpn_sampleinterval, amd_manager->mmio + sample_int_reg);
- dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
- dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
- acp_reg_writel(dpn_hctrl, amd_manager->mmio + hctrl_dp0_reg);
- dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
- dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
- acp_reg_writel(dpn_offsetctrl, amd_manager->mmio + offset_reg);
- /*
* lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
* parameters.
*/
- dpn_lanectrl = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
- u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL);
- acp_reg_writel(dpn_lanectrl, amd_manager->mmio + lane_ctrl_ch_en_reg);
- return 0;
+}
+static int amd_sdw_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch,
unsigned int bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 dpn_ch_enable;
- u32 lane_ctrl_ch_en_reg;
- switch (amd_manager->instance) {
- case ACP_SDW0:
lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
break;
- case ACP_SDW1:
lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
break;
- default:
return -EINVAL;
- }
- /*
* lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
* parameters.
*/
- dpn_ch_enable = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
- u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
- if (enable_ch->enable)
acp_reg_writel(dpn_ch_enable, amd_manager->mmio + lane_ctrl_ch_en_reg);
- else
acp_reg_writel(0, amd_manager->mmio + lane_ctrl_ch_en_reg);
- return 0;
+}
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find manager 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, "Manager node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
- fwnode_property_read_u32(link, "amd-sdw-wakeup-enable", &wake_en_mask);
- amd_manager->wake_en_mask = wake_en_mask;
- fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask);
- amd_manager->power_mode_mask = power_mode_mask;
- return 0;
+}
+static int amd_prop_read(struct sdw_bus *bus) +{
- sdw_master_read_prop(bus);
- sdw_master_read_amd_prop(bus);
- return 0;
+}
+static const struct sdw_master_port_ops amd_sdw_port_ops = {
- .dpn_set_port_params = amd_sdw_port_params,
- .dpn_set_port_transport_params = amd_sdw_transport_params,
- .dpn_port_enable_ch = amd_sdw_port_enable,
+};
+static const struct sdw_master_ops amd_sdw_ops = {
- .read_prop = amd_prop_read,
- .xfer_msg = amd_sdw_xfer_msg,
- .read_ping_status = amd_sdw_read_ping_status,
+};
+static void amd_sdw_probe_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager,
probe_work);
- struct sdw_master_prop *prop;
- int ret;
- prop = &amd_manager->bus.prop;
- if (!prop->hw_disabled) {
amd_enable_sdw_pads(amd_manager);
ret = amd_init_sdw_manager(amd_manager);
if (ret)
return;
amd_enable_sdw_interrupts(amd_manager);
ret = amd_enable_sdw_manager(amd_manager);
if (ret)
return;
amd_sdw_set_frameshape(amd_manager);
- }
+}
+static int amd_sdw_manager_probe(struct platform_device *pdev) +{
- const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
- struct resource *res;
- struct device *dev = &pdev->dev;
- struct sdw_master_prop *prop;
- struct sdw_bus_params *params;
- struct amd_sdw_manager *amd_manager;
- int ret;
- amd_manager = devm_kzalloc(dev, sizeof(struct amd_sdw_manager), GFP_KERNEL);
- if (!amd_manager)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res)
return -ENOMEM;
- amd_manager->acp_mmio = devm_ioremap(dev, res->start, resource_size(res));
- if (IS_ERR(amd_manager->mmio)) {
dev_err(dev, "mmio not found\n");
return PTR_ERR(amd_manager->mmio);
- }
- amd_manager->instance = pdata->instance;
- amd_manager->mmio = amd_manager->acp_mmio +
(amd_manager->instance * SDW_MANAGER_REG_OFFSET);
- amd_manager->acp_sdw_lock = pdata->acp_sdw_lock;
- amd_manager->cols_index = sdw_find_col_index(AMD_SDW_DEFAULT_COLUMNS);
- amd_manager->rows_index = sdw_find_row_index(AMD_SDW_DEFAULT_ROWS);
- amd_manager->dev = dev;
- amd_manager->bus.ops = &amd_sdw_ops;
- amd_manager->bus.port_ops = &amd_sdw_port_ops;
- amd_manager->bus.compute_params = &amd_sdw_compute_params;
- amd_manager->bus.clk_stop_timeout = 200;
- amd_manager->bus.link_id = amd_manager->instance;
empty line here please and at least the end of switch-case
- switch (amd_manager->instance) {
- case ACP_SDW0:
amd_manager->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
amd_manager->num_din_ports = AMD_SDW0_MAX_RX_PORTS;
break;
- case ACP_SDW1:
amd_manager->num_dout_ports = AMD_SDW1_MAX_TX_PORTS;
amd_manager->num_din_ports = AMD_SDW1_MAX_RX_PORTS;
break;
- default:
return -EINVAL;
- }
- amd_manager->reg_mask = &sdw_manager_reg_mask_array[amd_manager->instance];
- params = &amd_manager->bus.params;
- params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->col = AMD_SDW_DEFAULT_COLUMNS;
- params->row = AMD_SDW_DEFAULT_ROWS;
- prop = &amd_manager->bus.prop;
- prop->clk_freq = &amd_sdw_freq_tbl[0];
- prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
- ret = sdw_bus_master_add(&amd_manager->bus, dev, dev->fwnode);
- if (ret) {
dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret);
return ret;
- }
- dev_set_drvdata(dev, amd_manager);
- INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work);
- /*
* Instead of having lengthy probe sequence, use deferred probe.
*/
- schedule_work(&amd_manager->probe_work);
- return 0;
+}
+static int amd_sdw_manager_remove(struct platform_device *pdev) +{
- struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev);
- cancel_work_sync(&amd_manager->probe_work);
- amd_disable_sdw_interrupts(amd_manager);
- sdw_bus_master_delete(&amd_manager->bus);
- return amd_disable_sdw_manager(amd_manager);
+}
+static struct platform_driver amd_sdw_driver = {
- .probe = &amd_sdw_manager_probe,
- .remove = &amd_sdw_manager_remove,
- .driver = {
.name = "amd_sdw_manager",
- }
+}; +module_platform_driver(amd_sdw_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD SoundWire driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h new file mode 100644 index 000000000000..0d4b8653877e --- /dev/null +++ b/drivers/soundwire/amd_manager.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved.
- */
+#ifndef __AMD_MANAGER_H +#define __AMD_MANAGER_H
+#include <linux/soundwire/sdw_amd.h>
+#define SDW_MANAGER_REG_OFFSET 0xC00
Please use lower case for hex values
+#define AMD_SDW_DEFAULT_ROWS 50 +#define AMD_SDW_DEFAULT_COLUMNS 10 +#define ACP_PAD_PULLDOWN_CTRL 0x0001448 +#define ACP_SW_PAD_KEEPER_EN 0x0001454 +#define ACP_SW0_WAKE_EN 0x0001458 +#define ACP_EXTERNAL_INTR_CNTL0 0x0001A04 +#define ACP_EXTERNAL_INTR_STAT0 0x0001A0C +#define ACP_EXTERNAL_INTR_CNTL(i) (ACP_EXTERNAL_INTR_CNTL0 + ((i) * 4)) +#define ACP_EXTERNAL_INTR_STAT(i) (ACP_EXTERNAL_INTR_STAT0 + ((i) * 4)) +#define ACP_SW_WAKE_EN(i) (ACP_SW0_WAKE_EN + ((i) * 8))
+#define ACP_SW_EN 0x0003000 +#define ACP_SW_EN_STATUS 0x0003004 +#define ACP_SW_FRAMESIZE 0x0003008 +#define ACP_SW_SSP_COUNTER 0x000300C +#define ACP_SW_AUDIO0_TX_EN 0x0003010 +#define ACP_SW_AUDIO0_TX_EN_STATUS 0x0003014 +#define ACP_SW_AUDIO0_TX_FRAME_FORMAT 0x0003018 +#define ACP_SW_AUDIO0_TX_SAMPLEINTERVAL 0x000301C +#define ACP_SW_AUDIO0_TX_HCTRL_DP0 0x0003020 +#define ACP_SW_AUDIO0_TX_HCTRL_DP1 0x0003024 +#define ACP_SW_AUDIO0_TX_HCTRL_DP2 0x0003028 +#define ACP_SW_AUDIO0_TX_HCTRL_DP3 0x000302C +#define ACP_SW_AUDIO0_TX_OFFSET_DP0 0x0003030 +#define ACP_SW_AUDIO0_TX_OFFSET_DP1 0x0003034 +#define ACP_SW_AUDIO0_TX_OFFSET_DP2 0x0003038 +#define ACP_SW_AUDIO0_TX_OFFSET_DP3 0x000303C +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0 0x0003040 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP1 0x0003044 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP2 0x0003048 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP3 0x000304C +#define ACP_SW_AUDIO1_TX_EN 0x0003050 +#define ACP_SW_AUDIO1_TX_EN_STATUS 0x0003054 +#define ACP_SW_AUDIO1_TX_FRAME_FORMAT 0x0003058 +#define ACP_SW_AUDIO1_TX_SAMPLEINTERVAL 0x000305C +#define ACP_SW_AUDIO1_TX_HCTRL 0x0003060 +#define ACP_SW_AUDIO1_TX_OFFSET 0x0003064 +#define ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0 0x0003068 +#define ACP_SW_AUDIO2_TX_EN 0x000306C +#define ACP_SW_AUDIO2_TX_EN_STATUS 0x0003070 +#define ACP_SW_AUDIO2_TX_FRAME_FORMAT 0x0003074 +#define ACP_SW_AUDIO2_TX_SAMPLEINTERVAL 0x0003078 +#define ACP_SW_AUDIO2_TX_HCTRL 0x000307C +#define ACP_SW_AUDIO2_TX_OFFSET 0x0003080 +#define ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0 0x0003084 +#define ACP_SW_AUDIO0_RX_EN 0x0003088 +#define ACP_SW_AUDIO0_RX_EN_STATUS 0x000308C +#define ACP_SW_AUDIO0_RX_FRAME_FORMAT 0x0003090 +#define ACP_SW_AUDIO0_RX_SAMPLEINTERVAL 0x0003094 +#define ACP_SW_AUDIO0_RX_HCTRL_DP0 0x0003098 +#define ACP_SW_AUDIO0_RX_HCTRL_DP1 0x000309C +#define ACP_SW_AUDIO0_RX_HCTRL_DP2 0x0003100 +#define ACP_SW_AUDIO0_RX_HCTRL_DP3 0x0003104 +#define ACP_SW_AUDIO0_RX_OFFSET_DP0 0x0003108 +#define ACP_SW_AUDIO0_RX_OFFSET_DP1 0x000310C +#define ACP_SW_AUDIO0_RX_OFFSET_DP2 0x0003110 +#define ACP_SW_AUDIO0_RX_OFFSET_DP3 0x0003114 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0 0x0003118 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP1 0x000311C +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP2 0x0003120 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP3 0x0003124 +#define ACP_SW_AUDIO1_RX_EN 0x0003128 +#define ACP_SW_AUDIO1_RX_EN_STATUS 0x000312C +#define ACP_SW_AUDIO1_RX_FRAME_FORMAT 0x0003130 +#define ACP_SW_AUDIO1_RX_SAMPLEINTERVAL 0x0003134 +#define ACP_SW_AUDIO1_RX_HCTRL 0x0003138 +#define ACP_SW_AUDIO1_RX_OFFSET 0x000313C +#define ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0 0x0003140 +#define ACP_SW_AUDIO2_RX_EN 0x0003144 +#define ACP_SW_AUDIO2_RX_EN_STATUS 0x0003148 +#define ACP_SW_AUDIO2_RX_FRAME_FORMAT 0x000314C +#define ACP_SW_AUDIO2_RX_SAMPLEINTERVAL 0x0003150 +#define ACP_SW_AUDIO2_RX_HCTRL 0x0003154 +#define ACP_SW_AUDIO2_RX_OFFSET 0x0003158 +#define ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0 0x000315C +#define ACP_SW_BPT_PORT_EN 0x0003160 +#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164 +#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168 +#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316C +#define ACP_SW_BPT_PORT_HCTRL 0x0003170 +#define ACP_SW_BPT_PORT_OFFSET 0x0003174 +#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178 +#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317C +#define ACP_SW_CLK_RESUME_CTRL 0x0003180 +#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184 +#define ACP_SW_BUS_RESET_CTRL 0x0003188 +#define ACP_SW_PRBS_ERR_STATUS 0x000318C +#define ACP_SW_IMM_CMD_UPPER_WORD 0x0003230 +#define ACP_SW_IMM_CMD_LOWER_QWORD 0x0003234 +#define ACP_SW_IMM_RESP_UPPER_WORD 0x0003238 +#define ACP_SW_IMM_RESP_LOWER_QWORD 0x000323C +#define ACP_SW_IMM_CMD_STS 0x0003240 +#define ACP_SW_BRA_BASE_ADDRESS 0x0003244 +#define ACP_SW_BRA_TRANSFER_SIZE 0x0003248 +#define ACP_SW_BRA_DMA_BUSY 0x000324C +#define ACP_SW_BRA_RESP 0x0003250 +#define ACP_SW_BRA_RESP_FRAME_ADDR 0x0003254 +#define ACP_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258 +#define ACP_SW_STATE_CHANGE_STATUS_0TO7 0x000325C +#define ACP_SW_STATE_CHANGE_STATUS_8TO11 0x0003260 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268 +#define ACP_SW_CLK_FREQUENCY_CTRL 0x000326C +#define ACP_SW_ERROR_INTR_MASK 0x0003270 +#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274
+#define ACP_DELAY_US 10 +#define AMD_SDW_TIMEOUT 1000 +#define AMD_SDW_DEFAULT_CLK_FREQ 12000000
+#define AMD_SDW_MCP_RESP_ACK BIT(0) +#define AMD_SDW_MCP_RESP_NACK BIT(1) +#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7)
+#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31) +#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12) +#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8) +#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0) +#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24) +#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK_ULL(39, 24) +#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2) +#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK_ULL(15, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4)) +#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1)
+#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 2000 +#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0)
+#define AMD_SDW_IMM_RES_VALID 1 +#define AMD_SDW_IMM_CMD_BUSY 2 +#define AMD_SDW_ENABLE 1 +#define AMD_SDW_DISABLE 0 +#define AMD_SDW_BUS_RESET_CLEAR_REQ 0 +#define AMD_SDW_BUS_RESET_REQ 1 +#define AMD_SDW_BUS_RESET_DONE 2 +#define AMD_SDW_BUS_BASE_FREQ 24000000
+#define AMD_SDW0_EXT_INTR_MASK 0x200000 +#define AMD_SDW1_EXT_INTR_MASK 4 +#define AMD_SDW_IRQ_MASK_0TO7 0x77777777 +#define AMD_SDW_IRQ_MASK_8TO11 0x000D7777 +#define AMD_SDW_IRQ_ERROR_MASK 0xFF +#define AMD_SDW_MAX_FREQ_NUM 1 +#define AMD_SDW0_MAX_TX_PORTS 3 +#define AMD_SDW0_MAX_RX_PORTS 3 +#define AMD_SDW1_MAX_TX_PORTS 1 +#define AMD_SDW1_MAX_RX_PORTS 1 +#define AMD_SDW0_MAX_DAI 6 +#define AMD_SDW1_MAX_DAI 2 +#define AMD_SDW_SLAVE_0_ATTACHED 5 +#define AMD_SDW_SSP_COUNTER_VAL 3
+#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0) +#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2) +#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4) +#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5) +#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7) +#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13) +#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4) +#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8) +#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0) +#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3) +#define AMD_SDW_STAT_MAX_RETRY_COUNT 100 +#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7F9F +#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7FFA +#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60 +#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5 +#define AMD_SDW0_PAD_KEEPER_EN_MASK 1 +#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 +#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E +#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF
+enum amd_sdw_cmd_type {
- AMD_SDW_CMD_PING = 0,
- AMD_SDW_CMD_READ = 2,
- AMD_SDW_CMD_WRITE = 3,
+};
+static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = {
- AMD_SDW_DEFAULT_CLK_FREQ,
+};
+struct sdw_manager_dp_reg {
- u32 frame_fmt_reg;
- u32 sample_int_reg;
- u32 hctrl_dp0_reg;
- u32 offset_reg;
- u32 lane_ctrl_ch_en_reg;
+};
+static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = {
- {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0,
ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
+};
+static struct sdw_manager_dp_reg sdw1_manager_dp_reg[AMD_SDW1_MAX_DAI] = {
- {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0}
+};
+static struct sdw_manager_reg_mask sdw_manager_reg_mask_array[2] = {
- {
AMD_SDW0_PAD_KEEPER_EN_MASK,
AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK,
AMD_SDW0_EXT_INTR_MASK
- },
- {
AMD_SDW1_PAD_KEEPER_EN_MASK,
AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK,
AMD_SDW1_EXT_INTR_MASK
- }
+};
+static inline u32 acp_reg_readl(void __iomem *base_addr) +{
- return readl(base_addr);
+}
+static inline void acp_reg_writel(u32 val, void __iomem *base_addr) +{
- writel(val, base_addr);
+}
aha, why cant we stick to readl/writel in the driver. Why use a wrapper which does nothing?
+#endif diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h new file mode 100644 index 000000000000..c14a291a40e8 --- /dev/null +++ b/include/linux/soundwire/sdw_amd.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved.
- */
+#ifndef __SDW_AMD_H +#define __SDW_AMD_H
+#include <linux/soundwire/sdw.h>
+#define ACP_SDW0 0 +#define ACP_SDW1 1
+struct acp_sdw_pdata {
- u16 instance;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
+};
+struct sdw_manager_reg_mask {
- u32 sw_pad_enable_mask;
- u32 sw_pad_pulldown_mask;
- u32 acp_sdw_intr_mask;
+};
+/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
- @dev: linux device
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
- @rows_index: Rows index in frame shape
- @instance: SoundWire manager instance
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
- */
+struct amd_sdw_manager {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- void __iomem *acp_mmio;
- struct sdw_manager_reg_mask *reg_mask;
- struct work_struct probe_work;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- u32 power_mode_mask;
+};
Does the manager need to be exposed to rest of kernel or users of this driver, is so why?
On 15/03/23 15:12, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
AMD ACP(v6.x) IP block has two SoundWire manager devices. Add support for
- Manager driver probe & remove sequence
- Helper functions to enable/disable interrupts, Initialize sdw manager, enable sdw pads
- Manager driver sdw_master_ops & port_ops callbacks
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 698 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 251 +++++++++++ include/linux/soundwire/sdw_amd.h | 67 +++ 3 files changed, 1016 insertions(+) create mode 100644 drivers/soundwire/amd_manager.c create mode 100644 drivers/soundwire/amd_manager.h create mode 100644 include/linux/soundwire/sdw_amd.h
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c new file mode 100644 index 000000000000..a5cf6acd936c --- /dev/null +++ b/drivers/soundwire/amd_manager.c @@ -0,0 +1,698 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- SoundWire AMD Manager driver
- Copyright 2023 Advanced Micro Devices, Inc.
- */
+#include <linux/completion.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/wait.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "bus.h" +#include "amd_manager.h"
+#define DRV_NAME "amd_sdw_manager"
+#define to_amd_sdw(b) container_of(b, struct amd_sdw_manager, bus)
+static void amd_enable_sdw_pads(struct amd_sdw_manager *amd_manager) +{
- u32 sw_pad_pulldown_val;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
- val |= amd_manager->reg_mask->sw_pad_enable_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_SW_PAD_KEEPER_EN);
- usleep_range(1000, 1500);
- sw_pad_pulldown_val = acp_reg_readl(amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
- sw_pad_pulldown_val &= amd_manager->reg_mask->sw_pad_pulldown_mask;
- acp_reg_writel(sw_pad_pulldown_val, amd_manager->acp_mmio + ACP_PAD_PULLDOWN_CTRL);
- mutex_unlock(amd_manager->acp_sdw_lock);
+}
+static int amd_init_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- int ret;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- ret = read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
- if (ret)
return ret;
- /* SoundWire manager bus reset */
- acp_reg_writel(AMD_SDW_BUS_RESET_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_BUS_RESET_DONE), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret)
return ret;
- acp_reg_writel(AMD_SDW_BUS_RESET_CLEAR_REQ, amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- ret = read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_BUS_RESET_CTRL);
- if (ret) {
dev_err(amd_manager->dev, "Failed to reset SoundWire manager instance%d\n",
amd_manager->instance);
return ret;
- }
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_enable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_ENABLE, amd_manager->mmio + ACP_SW_EN);
- return read_poll_timeout(acp_reg_readl, val, val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static int amd_disable_sdw_manager(struct amd_sdw_manager *amd_manager) +{
- u32 val;
- acp_reg_writel(AMD_SDW_DISABLE, amd_manager->mmio + ACP_SW_EN);
- /*
* After invoking manager disable sequence, check whether
* manager has executed clock stop sequence. In this case,
* manager should ignore checking enable status register.
*/
- val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
- if (val)
return 0;
- return read_poll_timeout(acp_reg_readl, val, !val, ACP_DELAY_US, AMD_SDW_TIMEOUT, false,
amd_manager->mmio + ACP_SW_EN_STATUS);
+}
+static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val |= reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(AMD_SDW_IRQ_ERROR_MASK, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) +{
- struct sdw_manager_reg_mask *reg_mask = amd_manager->reg_mask;
- u32 val;
- mutex_lock(amd_manager->acp_sdw_lock);
- val = acp_reg_readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- val &= ~reg_mask->acp_sdw_intr_mask;
- acp_reg_writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance));
- mutex_unlock(amd_manager->acp_sdw_lock);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK);
+}
+static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) +{
- u32 frame_size;
- frame_size = (amd_manager->rows_index << 3) | amd_manager->cols_index;
- acp_reg_writel(frame_size, amd_manager->mmio + ACP_SW_FRAMESIZE);
+}
+static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, u32 cmd_type,
struct sdw_msg *msg, int cmd_offset)
+{
- u32 upper_data;
- u32 lower_data = 0;
- u16 addr;
- u8 addr_upper, addr_lower;
- u8 data = 0;
- addr = msg->addr + cmd_offset;
- addr_upper = (addr & 0xFF00) >> 8;
- addr_lower = addr & 0xFF;
- if (cmd_type == AMD_SDW_CMD_WRITE)
data = msg->buf[cmd_offset];
- upper_data = FIELD_PREP(AMD_SDW_MCP_CMD_DEV_ADDR, msg->dev_num);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_COMMAND, cmd_type);
- upper_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_HIGH, addr_upper);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_ADDR_LOW, addr_lower);
- lower_data |= FIELD_PREP(AMD_SDW_MCP_CMD_REG_DATA, data);
- *upper_word = upper_data;
- *lower_word = lower_data;
+}
+static u64 amd_sdw_send_cmd_get_resp(struct amd_sdw_manager *amd_manager, u32 lower_word,
u32 upper_word)
+{
- u64 resp;
- u32 resp_lower, resp_upper;
- u32 sts;
- int ret;
- ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_CMD_BUSY), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x previous cmd status clear failed\n",
amd_manager->instance);
return ret;
- }
- if (sts & AMD_SDW_IMM_RES_VALID) {
dev_err(amd_manager->dev, "SDW%x manager is in bad state\n", amd_manager->instance);
acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- }
- acp_reg_writel(upper_word, amd_manager->mmio + ACP_SW_IMM_CMD_UPPER_WORD);
- acp_reg_writel(lower_word, amd_manager->mmio + ACP_SW_IMM_CMD_LOWER_QWORD);
- ret = read_poll_timeout(acp_reg_readl, sts, (sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x cmd response timeout occurred\n",
amd_manager->instance);
return ret;
- }
- resp_upper = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_UPPER_WORD);
- resp_lower = acp_reg_readl(amd_manager->mmio + ACP_SW_IMM_RESP_LOWER_QWORD);
- acp_reg_writel(AMD_SDW_IMM_RES_VALID, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- ret = read_poll_timeout(acp_reg_readl, sts, !(sts & AMD_SDW_IMM_RES_VALID), ACP_DELAY_US,
AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_IMM_CMD_STS);
- if (ret) {
dev_err(amd_manager->dev, "SDW%x cmd status retry failed\n",
amd_manager->instance);
return ret;
- }
- resp = resp_upper;
- resp = (resp << 32) | resp_lower;
- return resp;
+}
+static enum sdw_command_response +amd_program_scp_addr(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg) +{
- struct sdw_msg scp_msg = {0};
- u64 response_buf[2] = {0};
- u32 upper_word = 0, lower_word = 0;
- int nack = 0, no_ack = 0;
- int index, timeout = 0;
- scp_msg.dev_num = msg->dev_num;
- scp_msg.addr = SDW_SCP_ADDRPAGE1;
- scp_msg.buf = &msg->addr_page1;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[0] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- scp_msg.addr = SDW_SCP_ADDRPAGE2;
- scp_msg.buf = &msg->addr_page2;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, AMD_SDW_CMD_WRITE, &scp_msg, 0);
- response_buf[1] = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- for (index = 0; index < 2; index++) {
if (response_buf[index] == -ETIMEDOUT) {
dev_err(amd_manager->dev, "Program SCP cmd timeout\n");
timeout = 1;
} else if (!(response_buf[index] & AMD_SDW_MCP_RESP_ACK)) {
no_ack = 1;
if (response_buf[index] & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(amd_manager->dev, "Program SCP NACK received\n");
}
}
- }
- if (timeout) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage command timeout for Slave %d\n", msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(amd_manager->dev,
"SCP_addrpage NACKed for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_dbg_ratelimited(amd_manager->dev,
"SCP_addrpage ignored for Slave %d\n", msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
+static int amd_prep_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg, int *cmd) +{
- int ret;
- if (msg->page) {
ret = amd_program_scp_addr(amd_manager, msg);
if (ret) {
msg->len = 0;
return ret;
}
- }
- switch (msg->flags) {
- case SDW_MSG_FLAG_READ:
*cmd = AMD_SDW_CMD_READ;
break;
- case SDW_MSG_FLAG_WRITE:
*cmd = AMD_SDW_CMD_WRITE;
break;
*cmd = msg->flags + 2;
that should work too
will fix it.
- default:
dev_err(amd_manager->dev, "Invalid msg cmd: %d\n", msg->flags);
return -EINVAL;
- }
- return 0;
+}
+static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *amd_manager,
struct sdw_msg *msg, u64 response,
int offset)
+{
- int nack = 0, no_ack = 0;
- int timeout = 0;
- if (response & AMD_SDW_MCP_RESP_ACK) {
if (msg->flags == SDW_MSG_FLAG_READ)
msg->buf[offset] = FIELD_GET(AMD_SDW_MCP_RESP_RDATA, response);
- } else {
no_ack = 1;
if (response == -ETIMEDOUT) {
timeout = 1;
} else if (response & AMD_SDW_MCP_RESP_NACK) {
nack = 1;
dev_err(amd_manager->dev, "Program SCP NACK received\n");
}
- }
- if (timeout) {
dev_err_ratelimited(amd_manager->dev, "command timeout for Slave %d\n",
msg->dev_num);
return SDW_CMD_TIMEOUT;
- }
- if (nack) {
dev_err_ratelimited(amd_manager->dev,
"command response NACK received for Slave %d\n", msg->dev_num);
return SDW_CMD_FAIL;
- }
- if (no_ack) {
dev_err_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n",
msg->dev_num);
return SDW_CMD_IGNORED;
- }
- return SDW_CMD_OK;
+}
I would have coded this differently. we already know where we detect timeout or nack, so why not return the error from these places (it is exactly once). With that no_ack becomes remaining case!
Will fix it.
+static unsigned int _amd_sdw_xfer_msg(struct amd_sdw_manager *amd_manager, struct sdw_msg *msg,
int cmd, int cmd_offset)
+{
- u64 response;
- u32 upper_word = 0, lower_word = 0;
- amd_sdw_ctl_word_prep(&lower_word, &upper_word, cmd, msg, cmd_offset);
- response = amd_sdw_send_cmd_get_resp(amd_manager, lower_word, upper_word);
- return amd_sdw_fill_msg_resp(amd_manager, msg, response, cmd_offset);
+}
+static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- int ret, i;
- int cmd = 0;
- ret = amd_prep_msg(amd_manager, msg, &cmd);
- if (ret)
return SDW_CMD_FAIL_OTHER;
- for (i = 0; i < msg->len; i++) {
ret = _amd_sdw_xfer_msg(amd_manager, msg, cmd, i);
if (ret)
return ret;
- }
- return SDW_CMD_OK;
+}
+static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u64 response;
- u32 slave_stat;
- response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
- /* slave status from ping response */
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(amd_manager->dev, "slave_stat:0x%x\n", slave_stat);
- return slave_stat;
+}
+static int amd_sdw_compute_params(struct sdw_bus *bus) +{
- struct sdw_transport_data t_data = {0};
- struct sdw_master_runtime *m_rt;
- struct sdw_port_runtime *p_rt;
- struct sdw_bus_params *b_params = &bus->params;
- int port_bo, hstart, hstop, sample_int;
- unsigned int rate, bps;
- port_bo = 0;
- hstart = 1;
- hstop = bus->params.col - 1;
- t_data.hstop = hstop;
- t_data.hstart = hstart;
- list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (bus->params.curr_dr_freq / rate);
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
port_bo = (p_rt->num * 64) + 1;
dev_dbg(bus->dev, "p_rt->num=%d hstart=%d hstop=%d port_bo=%d\n",
p_rt->num, hstart, hstop, port_bo);
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_PKG_PER_PORT, 0x0);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = port_bo;
t_data.sub_block_offset = 0;
}
sdw_compute_slave_ports(m_rt, &t_data);
- }
- return 0;
+}
+static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params,
unsigned int bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 frame_fmt_reg, dpn_frame_fmt;
- dev_dbg(amd_manager->dev, "p_params->num:0x%x\n", p_params->num);
- switch (amd_manager->instance) {
- case ACP_SDW0:
frame_fmt_reg = sdw0_manager_dp_reg[p_params->num].frame_fmt_reg;
break;
- case ACP_SDW1:
frame_fmt_reg = sdw1_manager_dp_reg[p_params->num].frame_fmt_reg;
break;
- default:
return -EINVAL;
- }
- dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, p_params->flow_mode, AMD_DPN_FRAME_FMT_PFM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->data_mode, AMD_DPN_FRAME_FMT_PDM);
- u32p_replace_bits(&dpn_frame_fmt, p_params->bps - 1, AMD_DPN_FRAME_FMT_WORD_LEN);
- acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
- return 0;
+}
+static int amd_sdw_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *params,
enum sdw_reg_bank bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 dpn_frame_fmt;
- u32 dpn_sampleinterval;
- u32 dpn_hctrl;
- u32 dpn_offsetctrl;
- u32 dpn_lanectrl;
- u32 frame_fmt_reg, sample_int_reg, hctrl_dp0_reg;
- u32 offset_reg, lane_ctrl_ch_en_reg;
- switch (amd_manager->instance) {
- case ACP_SDW0:
frame_fmt_reg = sdw0_manager_dp_reg[params->port_num].frame_fmt_reg;
sample_int_reg = sdw0_manager_dp_reg[params->port_num].sample_int_reg;
hctrl_dp0_reg = sdw0_manager_dp_reg[params->port_num].hctrl_dp0_reg;
offset_reg = sdw0_manager_dp_reg[params->port_num].offset_reg;
lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
break;
- case ACP_SDW1:
frame_fmt_reg = sdw1_manager_dp_reg[params->port_num].frame_fmt_reg;
sample_int_reg = sdw1_manager_dp_reg[params->port_num].sample_int_reg;
hctrl_dp0_reg = sdw1_manager_dp_reg[params->port_num].hctrl_dp0_reg;
offset_reg = sdw1_manager_dp_reg[params->port_num].offset_reg;
lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
break;
- default:
return -EINVAL;
- }
- acp_reg_writel(AMD_SDW_SSP_COUNTER_VAL, amd_manager->mmio + ACP_SW_SSP_COUNTER);
- dpn_frame_fmt = acp_reg_readl(amd_manager->mmio + frame_fmt_reg);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_pkg_mode, AMD_DPN_FRAME_FMT_BLK_PKG_MODE);
- u32p_replace_bits(&dpn_frame_fmt, params->blk_grp_ctrl, AMD_DPN_FRAME_FMT_BLK_GRP_CTRL);
- u32p_replace_bits(&dpn_frame_fmt, SDW_STREAM_PCM, AMD_DPN_FRAME_FMT_PCM_OR_PDM);
- acp_reg_writel(dpn_frame_fmt, amd_manager->mmio + frame_fmt_reg);
- dpn_sampleinterval = params->sample_interval - 1;
- acp_reg_writel(dpn_sampleinterval, amd_manager->mmio + sample_int_reg);
- dpn_hctrl = FIELD_PREP(AMD_DPN_HCTRL_HSTOP, params->hstop);
- dpn_hctrl |= FIELD_PREP(AMD_DPN_HCTRL_HSTART, params->hstart);
- acp_reg_writel(dpn_hctrl, amd_manager->mmio + hctrl_dp0_reg);
- dpn_offsetctrl = FIELD_PREP(AMD_DPN_OFFSET_CTRL_1, params->offset1);
- dpn_offsetctrl |= FIELD_PREP(AMD_DPN_OFFSET_CTRL_2, params->offset2);
- acp_reg_writel(dpn_offsetctrl, amd_manager->mmio + offset_reg);
- /*
* lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
* parameters.
*/
- dpn_lanectrl = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
- u32p_replace_bits(&dpn_lanectrl, params->lane_ctrl, AMD_DPN_CH_EN_LCTRL);
- acp_reg_writel(dpn_lanectrl, amd_manager->mmio + lane_ctrl_ch_en_reg);
- return 0;
+}
+static int amd_sdw_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch,
unsigned int bank)
+{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- u32 dpn_ch_enable;
- u32 lane_ctrl_ch_en_reg;
- switch (amd_manager->instance) {
- case ACP_SDW0:
lane_ctrl_ch_en_reg = sdw0_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
break;
- case ACP_SDW1:
lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
break;
- default:
return -EINVAL;
- }
- /*
* lane_ctrl_ch_en_reg will be used to program lane_ctrl and ch_mask
* parameters.
*/
- dpn_ch_enable = acp_reg_readl(amd_manager->mmio + lane_ctrl_ch_en_reg);
- u32p_replace_bits(&dpn_ch_enable, enable_ch->ch_mask, AMD_DPN_CH_EN_CHMASK);
- if (enable_ch->enable)
acp_reg_writel(dpn_ch_enable, amd_manager->mmio + lane_ctrl_ch_en_reg);
- else
acp_reg_writel(0, amd_manager->mmio + lane_ctrl_ch_en_reg);
- return 0;
+}
+static int sdw_master_read_amd_prop(struct sdw_bus *bus) +{
- struct amd_sdw_manager *amd_manager = to_amd_sdw(bus);
- struct fwnode_handle *link;
- struct sdw_master_prop *prop;
- u32 quirk_mask = 0;
- u32 wake_en_mask = 0;
- u32 power_mode_mask = 0;
- char name[32];
- prop = &bus->prop;
- /* Find manager 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, "Manager node %s not found\n", name);
return -EIO;
- }
- fwnode_property_read_u32(link, "amd-sdw-enable", &quirk_mask);
- if (!(quirk_mask & AMD_SDW_QUIRK_MASK_BUS_ENABLE))
prop->hw_disabled = true;
- prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH |
SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY;
- fwnode_property_read_u32(link, "amd-sdw-wakeup-enable", &wake_en_mask);
- amd_manager->wake_en_mask = wake_en_mask;
- fwnode_property_read_u32(link, "amd-sdw-power-mode", &power_mode_mask);
- amd_manager->power_mode_mask = power_mode_mask;
- return 0;
+}
+static int amd_prop_read(struct sdw_bus *bus) +{
- sdw_master_read_prop(bus);
- sdw_master_read_amd_prop(bus);
- return 0;
+}
+static const struct sdw_master_port_ops amd_sdw_port_ops = {
- .dpn_set_port_params = amd_sdw_port_params,
- .dpn_set_port_transport_params = amd_sdw_transport_params,
- .dpn_port_enable_ch = amd_sdw_port_enable,
+};
+static const struct sdw_master_ops amd_sdw_ops = {
- .read_prop = amd_prop_read,
- .xfer_msg = amd_sdw_xfer_msg,
- .read_ping_status = amd_sdw_read_ping_status,
+};
+static void amd_sdw_probe_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager,
probe_work);
- struct sdw_master_prop *prop;
- int ret;
- prop = &amd_manager->bus.prop;
- if (!prop->hw_disabled) {
amd_enable_sdw_pads(amd_manager);
ret = amd_init_sdw_manager(amd_manager);
if (ret)
return;
amd_enable_sdw_interrupts(amd_manager);
ret = amd_enable_sdw_manager(amd_manager);
if (ret)
return;
amd_sdw_set_frameshape(amd_manager);
- }
+}
+static int amd_sdw_manager_probe(struct platform_device *pdev) +{
- const struct acp_sdw_pdata *pdata = pdev->dev.platform_data;
- struct resource *res;
- struct device *dev = &pdev->dev;
- struct sdw_master_prop *prop;
- struct sdw_bus_params *params;
- struct amd_sdw_manager *amd_manager;
- int ret;
- amd_manager = devm_kzalloc(dev, sizeof(struct amd_sdw_manager), GFP_KERNEL);
- if (!amd_manager)
return -ENOMEM;
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res)
return -ENOMEM;
- amd_manager->acp_mmio = devm_ioremap(dev, res->start, resource_size(res));
- if (IS_ERR(amd_manager->mmio)) {
dev_err(dev, "mmio not found\n");
return PTR_ERR(amd_manager->mmio);
- }
- amd_manager->instance = pdata->instance;
- amd_manager->mmio = amd_manager->acp_mmio +
(amd_manager->instance * SDW_MANAGER_REG_OFFSET);
- amd_manager->acp_sdw_lock = pdata->acp_sdw_lock;
- amd_manager->cols_index = sdw_find_col_index(AMD_SDW_DEFAULT_COLUMNS);
- amd_manager->rows_index = sdw_find_row_index(AMD_SDW_DEFAULT_ROWS);
- amd_manager->dev = dev;
- amd_manager->bus.ops = &amd_sdw_ops;
- amd_manager->bus.port_ops = &amd_sdw_port_ops;
- amd_manager->bus.compute_params = &amd_sdw_compute_params;
- amd_manager->bus.clk_stop_timeout = 200;
- amd_manager->bus.link_id = amd_manager->instance;
empty line here please and at least the end of switch-case
will fix it.
- switch (amd_manager->instance) {
- case ACP_SDW0:
amd_manager->num_dout_ports = AMD_SDW0_MAX_TX_PORTS;
amd_manager->num_din_ports = AMD_SDW0_MAX_RX_PORTS;
break;
- case ACP_SDW1:
amd_manager->num_dout_ports = AMD_SDW1_MAX_TX_PORTS;
amd_manager->num_din_ports = AMD_SDW1_MAX_RX_PORTS;
break;
- default:
return -EINVAL;
- }
- amd_manager->reg_mask = &sdw_manager_reg_mask_array[amd_manager->instance];
- params = &amd_manager->bus.params;
- params->max_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->curr_dr_freq = AMD_SDW_DEFAULT_CLK_FREQ * 2;
- params->col = AMD_SDW_DEFAULT_COLUMNS;
- params->row = AMD_SDW_DEFAULT_ROWS;
- prop = &amd_manager->bus.prop;
- prop->clk_freq = &amd_sdw_freq_tbl[0];
- prop->mclk_freq = AMD_SDW_BUS_BASE_FREQ;
- ret = sdw_bus_master_add(&amd_manager->bus, dev, dev->fwnode);
- if (ret) {
dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret);
return ret;
- }
- dev_set_drvdata(dev, amd_manager);
- INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work);
- /*
* Instead of having lengthy probe sequence, use deferred probe.
*/
- schedule_work(&amd_manager->probe_work);
- return 0;
+}
+static int amd_sdw_manager_remove(struct platform_device *pdev) +{
- struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev);
- cancel_work_sync(&amd_manager->probe_work);
- amd_disable_sdw_interrupts(amd_manager);
- sdw_bus_master_delete(&amd_manager->bus);
- return amd_disable_sdw_manager(amd_manager);
+}
+static struct platform_driver amd_sdw_driver = {
- .probe = &amd_sdw_manager_probe,
- .remove = &amd_sdw_manager_remove,
- .driver = {
.name = "amd_sdw_manager",
- }
+}; +module_platform_driver(amd_sdw_driver);
+MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); +MODULE_DESCRIPTION("AMD SoundWire driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h new file mode 100644 index 000000000000..0d4b8653877e --- /dev/null +++ b/drivers/soundwire/amd_manager.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved.
- */
+#ifndef __AMD_MANAGER_H +#define __AMD_MANAGER_H
+#include <linux/soundwire/sdw_amd.h>
+#define SDW_MANAGER_REG_OFFSET 0xC00
Please use lower case for hex values
Will fix it.
+#define AMD_SDW_DEFAULT_ROWS 50 +#define AMD_SDW_DEFAULT_COLUMNS 10 +#define ACP_PAD_PULLDOWN_CTRL 0x0001448 +#define ACP_SW_PAD_KEEPER_EN 0x0001454 +#define ACP_SW0_WAKE_EN 0x0001458 +#define ACP_EXTERNAL_INTR_CNTL0 0x0001A04 +#define ACP_EXTERNAL_INTR_STAT0 0x0001A0C +#define ACP_EXTERNAL_INTR_CNTL(i) (ACP_EXTERNAL_INTR_CNTL0 + ((i) * 4)) +#define ACP_EXTERNAL_INTR_STAT(i) (ACP_EXTERNAL_INTR_STAT0 + ((i) * 4)) +#define ACP_SW_WAKE_EN(i) (ACP_SW0_WAKE_EN + ((i) * 8))
+#define ACP_SW_EN 0x0003000 +#define ACP_SW_EN_STATUS 0x0003004 +#define ACP_SW_FRAMESIZE 0x0003008 +#define ACP_SW_SSP_COUNTER 0x000300C +#define ACP_SW_AUDIO0_TX_EN 0x0003010 +#define ACP_SW_AUDIO0_TX_EN_STATUS 0x0003014 +#define ACP_SW_AUDIO0_TX_FRAME_FORMAT 0x0003018 +#define ACP_SW_AUDIO0_TX_SAMPLEINTERVAL 0x000301C +#define ACP_SW_AUDIO0_TX_HCTRL_DP0 0x0003020 +#define ACP_SW_AUDIO0_TX_HCTRL_DP1 0x0003024 +#define ACP_SW_AUDIO0_TX_HCTRL_DP2 0x0003028 +#define ACP_SW_AUDIO0_TX_HCTRL_DP3 0x000302C +#define ACP_SW_AUDIO0_TX_OFFSET_DP0 0x0003030 +#define ACP_SW_AUDIO0_TX_OFFSET_DP1 0x0003034 +#define ACP_SW_AUDIO0_TX_OFFSET_DP2 0x0003038 +#define ACP_SW_AUDIO0_TX_OFFSET_DP3 0x000303C +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0 0x0003040 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP1 0x0003044 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP2 0x0003048 +#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP3 0x000304C +#define ACP_SW_AUDIO1_TX_EN 0x0003050 +#define ACP_SW_AUDIO1_TX_EN_STATUS 0x0003054 +#define ACP_SW_AUDIO1_TX_FRAME_FORMAT 0x0003058 +#define ACP_SW_AUDIO1_TX_SAMPLEINTERVAL 0x000305C +#define ACP_SW_AUDIO1_TX_HCTRL 0x0003060 +#define ACP_SW_AUDIO1_TX_OFFSET 0x0003064 +#define ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0 0x0003068 +#define ACP_SW_AUDIO2_TX_EN 0x000306C +#define ACP_SW_AUDIO2_TX_EN_STATUS 0x0003070 +#define ACP_SW_AUDIO2_TX_FRAME_FORMAT 0x0003074 +#define ACP_SW_AUDIO2_TX_SAMPLEINTERVAL 0x0003078 +#define ACP_SW_AUDIO2_TX_HCTRL 0x000307C +#define ACP_SW_AUDIO2_TX_OFFSET 0x0003080 +#define ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0 0x0003084 +#define ACP_SW_AUDIO0_RX_EN 0x0003088 +#define ACP_SW_AUDIO0_RX_EN_STATUS 0x000308C +#define ACP_SW_AUDIO0_RX_FRAME_FORMAT 0x0003090 +#define ACP_SW_AUDIO0_RX_SAMPLEINTERVAL 0x0003094 +#define ACP_SW_AUDIO0_RX_HCTRL_DP0 0x0003098 +#define ACP_SW_AUDIO0_RX_HCTRL_DP1 0x000309C +#define ACP_SW_AUDIO0_RX_HCTRL_DP2 0x0003100 +#define ACP_SW_AUDIO0_RX_HCTRL_DP3 0x0003104 +#define ACP_SW_AUDIO0_RX_OFFSET_DP0 0x0003108 +#define ACP_SW_AUDIO0_RX_OFFSET_DP1 0x000310C +#define ACP_SW_AUDIO0_RX_OFFSET_DP2 0x0003110 +#define ACP_SW_AUDIO0_RX_OFFSET_DP3 0x0003114 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0 0x0003118 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP1 0x000311C +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP2 0x0003120 +#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP3 0x0003124 +#define ACP_SW_AUDIO1_RX_EN 0x0003128 +#define ACP_SW_AUDIO1_RX_EN_STATUS 0x000312C +#define ACP_SW_AUDIO1_RX_FRAME_FORMAT 0x0003130 +#define ACP_SW_AUDIO1_RX_SAMPLEINTERVAL 0x0003134 +#define ACP_SW_AUDIO1_RX_HCTRL 0x0003138 +#define ACP_SW_AUDIO1_RX_OFFSET 0x000313C +#define ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0 0x0003140 +#define ACP_SW_AUDIO2_RX_EN 0x0003144 +#define ACP_SW_AUDIO2_RX_EN_STATUS 0x0003148 +#define ACP_SW_AUDIO2_RX_FRAME_FORMAT 0x000314C +#define ACP_SW_AUDIO2_RX_SAMPLEINTERVAL 0x0003150 +#define ACP_SW_AUDIO2_RX_HCTRL 0x0003154 +#define ACP_SW_AUDIO2_RX_OFFSET 0x0003158 +#define ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0 0x000315C +#define ACP_SW_BPT_PORT_EN 0x0003160 +#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164 +#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168 +#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316C +#define ACP_SW_BPT_PORT_HCTRL 0x0003170 +#define ACP_SW_BPT_PORT_OFFSET 0x0003174 +#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178 +#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317C +#define ACP_SW_CLK_RESUME_CTRL 0x0003180 +#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184 +#define ACP_SW_BUS_RESET_CTRL 0x0003188 +#define ACP_SW_PRBS_ERR_STATUS 0x000318C +#define ACP_SW_IMM_CMD_UPPER_WORD 0x0003230 +#define ACP_SW_IMM_CMD_LOWER_QWORD 0x0003234 +#define ACP_SW_IMM_RESP_UPPER_WORD 0x0003238 +#define ACP_SW_IMM_RESP_LOWER_QWORD 0x000323C +#define ACP_SW_IMM_CMD_STS 0x0003240 +#define ACP_SW_BRA_BASE_ADDRESS 0x0003244 +#define ACP_SW_BRA_TRANSFER_SIZE 0x0003248 +#define ACP_SW_BRA_DMA_BUSY 0x000324C +#define ACP_SW_BRA_RESP 0x0003250 +#define ACP_SW_BRA_RESP_FRAME_ADDR 0x0003254 +#define ACP_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258 +#define ACP_SW_STATE_CHANGE_STATUS_0TO7 0x000325C +#define ACP_SW_STATE_CHANGE_STATUS_8TO11 0x0003260 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264 +#define ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268 +#define ACP_SW_CLK_FREQUENCY_CTRL 0x000326C +#define ACP_SW_ERROR_INTR_MASK 0x0003270 +#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274
+#define ACP_DELAY_US 10 +#define AMD_SDW_TIMEOUT 1000 +#define AMD_SDW_DEFAULT_CLK_FREQ 12000000
+#define AMD_SDW_MCP_RESP_ACK BIT(0) +#define AMD_SDW_MCP_RESP_NACK BIT(1) +#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7)
+#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31) +#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12) +#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8) +#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0) +#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24) +#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7) +#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK_ULL(39, 24) +#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2) +#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK_ULL(15, 0) +#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4)) +#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1)
+#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 2000 +#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0)
+#define AMD_SDW_IMM_RES_VALID 1 +#define AMD_SDW_IMM_CMD_BUSY 2 +#define AMD_SDW_ENABLE 1 +#define AMD_SDW_DISABLE 0 +#define AMD_SDW_BUS_RESET_CLEAR_REQ 0 +#define AMD_SDW_BUS_RESET_REQ 1 +#define AMD_SDW_BUS_RESET_DONE 2 +#define AMD_SDW_BUS_BASE_FREQ 24000000
+#define AMD_SDW0_EXT_INTR_MASK 0x200000 +#define AMD_SDW1_EXT_INTR_MASK 4 +#define AMD_SDW_IRQ_MASK_0TO7 0x77777777 +#define AMD_SDW_IRQ_MASK_8TO11 0x000D7777 +#define AMD_SDW_IRQ_ERROR_MASK 0xFF +#define AMD_SDW_MAX_FREQ_NUM 1 +#define AMD_SDW0_MAX_TX_PORTS 3 +#define AMD_SDW0_MAX_RX_PORTS 3 +#define AMD_SDW1_MAX_TX_PORTS 1 +#define AMD_SDW1_MAX_RX_PORTS 1 +#define AMD_SDW0_MAX_DAI 6 +#define AMD_SDW1_MAX_DAI 2 +#define AMD_SDW_SLAVE_0_ATTACHED 5 +#define AMD_SDW_SSP_COUNTER_VAL 3
+#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0) +#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2) +#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4) +#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5) +#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7) +#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13) +#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0) +#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4) +#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0) +#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8) +#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0) +#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3) +#define AMD_SDW_STAT_MAX_RETRY_COUNT 100 +#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7F9F +#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7FFA +#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60 +#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5 +#define AMD_SDW0_PAD_KEEPER_EN_MASK 1 +#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 +#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E +#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF
+enum amd_sdw_cmd_type {
- AMD_SDW_CMD_PING = 0,
- AMD_SDW_CMD_READ = 2,
- AMD_SDW_CMD_WRITE = 3,
+};
+static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = {
- AMD_SDW_DEFAULT_CLK_FREQ,
+};
+struct sdw_manager_dp_reg {
- u32 frame_fmt_reg;
- u32 sample_int_reg;
- u32 hctrl_dp0_reg;
- u32 offset_reg;
- u32 lane_ctrl_ch_en_reg;
+};
+static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = {
- {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0,
ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
+};
+static struct sdw_manager_dp_reg sdw1_manager_dp_reg[AMD_SDW1_MAX_DAI] = {
- {ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
- {ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0}
+};
+static struct sdw_manager_reg_mask sdw_manager_reg_mask_array[2] = {
- {
AMD_SDW0_PAD_KEEPER_EN_MASK,
AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK,
AMD_SDW0_EXT_INTR_MASK
- },
- {
AMD_SDW1_PAD_KEEPER_EN_MASK,
AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK,
AMD_SDW1_EXT_INTR_MASK
- }
+};
+static inline u32 acp_reg_readl(void __iomem *base_addr) +{
- return readl(base_addr);
+}
+static inline void acp_reg_writel(u32 val, void __iomem *base_addr) +{
- writel(val, base_addr);
+}
aha, why cant we stick to readl/writel in the driver. Why use a wrapper which does nothing?
Yes wrapper is not really required. Will fix it.
+#endif diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h new file mode 100644 index 000000000000..c14a291a40e8 --- /dev/null +++ b/include/linux/soundwire/sdw_amd.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/*
- Copyright (C) 2023 Advanced Micro Devices, Inc. All rights reserved.
- */
+#ifndef __SDW_AMD_H +#define __SDW_AMD_H
+#include <linux/soundwire/sdw.h>
+#define ACP_SDW0 0 +#define ACP_SDW1 1
+struct acp_sdw_pdata {
- u16 instance;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
+};
+struct sdw_manager_reg_mask {
- u32 sw_pad_enable_mask;
- u32 sw_pad_pulldown_mask;
- u32 acp_sdw_intr_mask;
+};
+/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
- @dev: linux device
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
- @rows_index: Rows index in frame shape
- @instance: SoundWire manager instance
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
- */
+struct amd_sdw_manager {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- void __iomem *acp_mmio;
- struct sdw_manager_reg_mask *reg_mask;
- struct work_struct probe_work;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- u32 power_mode_mask;
+};
Does the manager need to be exposed to rest of kernel or users of this driver, is so why?
Currently, amd_manager structure being used in ACP PCI driver (parent driver) and Soundwire DMA driver.
In ACP PCI driver, IRQ handler we will use amd_manager structure to schedule workqueue based on soundwire manager instance. In Soundwire DMA driver, we need to retrieve amd_manager instance. As per our design, we have fixed mapping. We need to use same set of DMA registers based on CPU DAI ID. Â i.e if AUDIO0 TX port is selected in amd_manager driver then we need to use AUDIO0 TX registers for DMA programming. we have included comments for describing mapping in amd_manager.h file.
On 16-03-23, 19:28, Mukunda,Vijendar wrote:
On 15/03/23 15:12, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
+/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
- @dev: linux device
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
- @rows_index: Rows index in frame shape
- @instance: SoundWire manager instance
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
- */
+struct amd_sdw_manager {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- void __iomem *acp_mmio;
- struct sdw_manager_reg_mask *reg_mask;
- struct work_struct probe_work;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- u32 power_mode_mask;
+};
Does the manager need to be exposed to rest of kernel or users of this driver, is so why?
Currently, amd_manager structure being used in ACP PCI driver (parent driver) and Soundwire DMA driver.
In ACP PCI driver, IRQ handler we will use amd_manager structure to schedule workqueue based on soundwire manager instance. In Soundwire DMA driver, we need to retrieve amd_manager instance. As per our design, we have fixed mapping. We need to use same set of DMA registers based on CPU DAI ID. Â i.e if AUDIO0 TX port is selected in amd_manager driver then we need to use AUDIO0 TX registers for DMA programming. we have included comments for describing mapping in amd_manager.h file.
Sorry not sure I follow, can you elaborate which members of above struct are used by PCI driver?
On 17/03/23 19:04, Vinod Koul wrote:
On 16-03-23, 19:28, Mukunda,Vijendar wrote:
On 15/03/23 15:12, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
+/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
- @dev: linux device
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
- @rows_index: Rows index in frame shape
- @instance: SoundWire manager instance
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
- */
+struct amd_sdw_manager {
- struct sdw_bus bus;
- struct device *dev;
- void __iomem *mmio;
- void __iomem *acp_mmio;
- struct sdw_manager_reg_mask *reg_mask;
- struct work_struct probe_work;
- /* mutex to protect acp common register access */
- struct mutex *acp_sdw_lock;
- int num_din_ports;
- int num_dout_ports;
- int cols_index;
- int rows_index;
- u32 instance;
- u32 quirks;
- u32 wake_en_mask;
- u32 power_mode_mask;
+};
Does the manager need to be exposed to rest of kernel or users of this driver, is so why?
Currently, amd_manager structure being used in ACP PCI driver (parent driver) and Soundwire DMA driver.
In ACP PCI driver, IRQ handler we will use amd_manager structure to schedule workqueue based on soundwire manager instance. In Soundwire DMA driver, we need to retrieve amd_manager instance. As per our design, we have fixed mapping. We need to use same set of DMA registers based on CPU DAI ID. Â i.e if AUDIO0 TX port is selected in amd_manager driver then we need to use AUDIO0 TX registers for DMA programming. we have included comments for describing mapping in amd_manager.h file.
Sorry not sure I follow, can you elaborate which members of above struct are used by PCI driver?
ACP PCI driver uses "amd_sdw_irq_thread" structure member which was added in below patch. https://www.spinics.net/lists/alsa-devel/msg155118.html
amd_manager structure "instance" member will be used by ACP Soundwire DMA driver.
Register dai ops for SoundWire manager instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-4-Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_manager.c | 182 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 18 +++ include/linux/soundwire/sdw_amd.h | 18 +++ 3 files changed, 218 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index a5cf6acd936c..dd7fd4036d89 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -581,6 +581,182 @@ static const struct sdw_master_ops amd_sdw_ops = { .read_ping_status = amd_sdw_read_ping_status, };
+static int amd_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dai_runtime *dai_runtime; + struct sdw_stream_config sconfig; + struct sdw_port_config *pconfig; + int ch, dir; + int ret; + + dai_runtime = amd_manager->dai_runtime_array[dai->id]; + if (!dai_runtime) + return -EIO; + + ch = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = SDW_DATA_DIR_RX; + else + dir = SDW_DATA_DIR_TX; + dev_dbg(amd_manager->dev, "dir:%d dai->id:0x%x\n", dir, dai->id); + + sconfig.direction = dir; + sconfig.ch_count = ch; + sconfig.frame_rate = params_rate(params); + sconfig.type = dai_runtime->stream_type; + + sconfig.bps = snd_pcm_format_width(params_format(params)); + + /* Port configuration */ + pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); + if (!pconfig) { + ret = -ENOMEM; + goto error; + } + + pconfig->num = dai->id; + pconfig->ch_mask = (1 << ch) - 1; + ret = sdw_stream_add_master(&amd_manager->bus, &sconfig, + pconfig, 1, dai_runtime->stream); + if (ret) + dev_err(amd_manager->dev, "add manager to stream failed:%d\n", ret); + + kfree(pconfig); +error: + return ret; +} + +static int amd_sdw_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dai_runtime *dai_runtime; + int ret; + + dai_runtime = amd_manager->dai_runtime_array[dai->id]; + if (!dai_runtime) + return -EIO; + + ret = sdw_stream_remove_master(&amd_manager->bus, dai_runtime->stream); + if (ret < 0) + dev_err(dai->dev, "remove manager from stream %s failed: %d\n", + dai_runtime->stream->name, ret); + return ret; +} + +static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dai_runtime *dai_runtime; + + dai_runtime = amd_manager->dai_runtime_array[dai->id]; + if (stream) { + /* first paranoia check */ + if (dai_runtime) { + dev_err(dai->dev, + "dai_runtime already allocated for dai %s\n", + dai->name); + return -EINVAL; + } + + /* allocate and set dai_runtime info */ + dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL); + if (!dai_runtime) + return -ENOMEM; + + dai_runtime->stream_type = SDW_STREAM_PCM; + dai_runtime->bus = &amd_manager->bus; + dai_runtime->stream = stream; + amd_manager->dai_runtime_array[dai->id] = dai_runtime; + } else { + /* second paranoia check */ + if (!dai_runtime) { + dev_err(dai->dev, + "dai_runtime not allocated for dai %s\n", + dai->name); + return -EINVAL; + } + + /* for NULL stream we release allocated dai_runtime */ + kfree(dai_runtime); + amd_manager->dai_runtime_array[dai->id] = NULL; + } + return 0; +} + +static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{ + return amd_set_sdw_stream(dai, stream, direction); +} + +static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{ + struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai); + struct sdw_amd_dai_runtime *dai_runtime; + + dai_runtime = amd_manager->dai_runtime_array[dai->id]; + if (!dai_runtime) + return ERR_PTR(-EINVAL); + + return dai_runtime->stream; +} + +static const struct snd_soc_dai_ops amd_sdw_dai_ops = { + .hw_params = amd_sdw_hw_params, + .hw_free = amd_sdw_hw_free, + .set_stream = amd_pcm_set_sdw_stream, + .get_stream = amd_get_sdw_stream, +}; + +static const struct snd_soc_component_driver amd_sdw_dai_component = { + .name = "soundwire", +}; + +static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) +{ + struct sdw_amd_dai_runtime **dai_runtime_array; + struct snd_soc_dai_driver *dais; + struct snd_soc_pcm_stream *stream; + struct device *dev; + int i, num_dais; + + dev = amd_manager->dev; + num_dais = amd_manager->num_dout_ports + amd_manager->num_din_ports; + dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL); + if (!dais) + return -ENOMEM; + + dai_runtime_array = devm_kcalloc(dev, num_dais, + sizeof(struct sdw_amd_dai_runtime *), + GFP_KERNEL); + if (!dai_runtime_array) + return -ENOMEM; + amd_manager->dai_runtime_array = dai_runtime_array; + for (i = 0; i < num_dais; i++) { + dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", amd_manager->instance, + i); + if (!dais[i].name) + return -ENOMEM; + if (i < amd_manager->num_dout_ports) + stream = &dais[i].playback; + else + stream = &dais[i].capture; + + stream->channels_min = 2; + stream->channels_max = 2; + stream->rates = SNDRV_PCM_RATE_48000; + stream->formats = SNDRV_PCM_FMTBIT_S16_LE; + + dais[i].ops = &amd_sdw_dai_ops; + dais[i].id = i; + } + + return devm_snd_soc_register_component(dev, &amd_sdw_dai_component, + dais, num_dais); +} + static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -664,6 +840,12 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret); return ret; } + ret = amd_sdw_register_dais(amd_manager); + if (ret) { + dev_err(dev, "CPU DAI registration failed\n"); + sdw_bus_master_delete(&amd_manager->bus); + return ret; + } dev_set_drvdata(dev, amd_manager); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /* diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 0d4b8653877e..cad26034087b 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -204,6 +204,24 @@ struct sdw_manager_dp_reg { u32 lane_ctrl_ch_en_reg; };
+/* + * SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports) + * whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port) + * Below is the CPU DAI <->Manager port number mapping + * i.e SDW0 Pin0 -> port number 0 -> AUDIO0 TX + * SDW0 Pin1 -> Port number 1 -> AUDIO1 TX + * SDW0 Pin2 -> Port number 2 -> AUDIO2 TX + * SDW0 Pin3 -> port number 3 -> AUDIO0 RX + * SDW0 Pin4 -> Port number 4 -> AUDIO1 RX + * SDW0 Pin5 -> Port number 5 -> AUDIO2 RX + * Whereas for SDW1 instance + * SDW1 Pin0 -> port number 0 -> AUDIO1 TX + * SDW1 Pin1 -> Port number 1 -> AUDIO1 RX + * Same mapping should be used for programming DMA controller registers in SoundWire DMA driver. + * i.e if AUDIO0 TX channel is selected then we need to use AUDIO0 TX registers for DMA programming + * in SoundWire DMA driver. + */ + static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0, ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0}, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index c14a291a40e8..ac537419301d 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -23,6 +23,21 @@ struct sdw_manager_reg_mask { u32 acp_sdw_intr_mask; };
+/** + * struct sdw_amd_dai_runtime: AMD sdw dai runtime data + * + * @name: SoundWire stream name + * @stream: stream runtime + * @bus: Bus handle + * @stream_type: Stream type + */ +struct sdw_amd_dai_runtime { + char *name; + struct sdw_stream_runtime *stream; + struct sdw_bus *bus; + enum sdw_stream_type stream_type; +}; + /** * struct amd_sdw_manager - amd manager driver context * @bus: bus handle @@ -40,6 +55,7 @@ struct sdw_manager_reg_mask { * @quirks: SoundWire manager quirks * @wake_en_mask: wake enable mask per SoundWire manager * @power_mode_mask: flag interprets amd SoundWire manager power mode + * @dai_runtime_array: dai runtime array */ struct amd_sdw_manager { struct sdw_bus bus; @@ -63,5 +79,7 @@ struct amd_sdw_manager { u32 quirks; u32 wake_en_mask; u32 power_mode_mask; + + struct sdw_amd_dai_runtime **dai_runtime_array; }; #endif
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Register dai ops for SoundWire manager instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-4-Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 182 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 18 +++ include/linux/soundwire/sdw_amd.h | 18 +++ 3 files changed, 218 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index a5cf6acd936c..dd7fd4036d89 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -581,6 +581,182 @@ static const struct sdw_master_ops amd_sdw_ops = { .read_ping_status = amd_sdw_read_ping_status, };
+static int amd_sdw_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ch, dir;
- int ret;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return -EIO;
- ch = params_channels(params);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
- else
dir = SDW_DATA_DIR_TX;
- dev_dbg(amd_manager->dev, "dir:%d dai->id:0x%x\n", dir, dai->id);
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dai_runtime->stream_type;
- sconfig.bps = snd_pcm_format_width(params_format(params));
- /* Port configuration */
- pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
ret = -ENOMEM;
goto error;
- }
- pconfig->num = dai->id;
- pconfig->ch_mask = (1 << ch) - 1;
- ret = sdw_stream_add_master(&amd_manager->bus, &sconfig,
pconfig, 1, dai_runtime->stream);
- if (ret)
dev_err(amd_manager->dev, "add manager to stream failed:%d\n", ret);
- kfree(pconfig);
+error:
- return ret;
+}
+static int amd_sdw_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- int ret;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return -EIO;
- ret = sdw_stream_remove_master(&amd_manager->bus, dai_runtime->stream);
- if (ret < 0)
dev_err(dai->dev, "remove manager from stream %s failed: %d\n",
dai_runtime->stream->name, ret);
- return ret;
+}
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (stream) {
/* first paranoia check */
if (dai_runtime) {
dev_err(dai->dev,
"dai_runtime already allocated for dai %s\n",
dai->name);
single line would look better
return -EINVAL;
}
/* allocate and set dai_runtime info */
dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL);
if (!dai_runtime)
return -ENOMEM;
dai_runtime->stream_type = SDW_STREAM_PCM;
dai_runtime->bus = &amd_manager->bus;
dai_runtime->stream = stream;
amd_manager->dai_runtime_array[dai->id] = dai_runtime;
- } else {
/* second paranoia check */
if (!dai_runtime) {
dev_err(dai->dev,
"dai_runtime not allocated for dai %s\n",
dai->name);
return -EINVAL;
}
/* for NULL stream we release allocated dai_runtime */
kfree(dai_runtime);
amd_manager->dai_runtime_array[dai->id] = NULL;
- }
- return 0;
+}
+static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- return amd_set_sdw_stream(dai, stream, direction);
+}
+static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return ERR_PTR(-EINVAL);
- return dai_runtime->stream;
+}
+static const struct snd_soc_dai_ops amd_sdw_dai_ops = {
- .hw_params = amd_sdw_hw_params,
- .hw_free = amd_sdw_hw_free,
- .set_stream = amd_pcm_set_sdw_stream,
- .get_stream = amd_get_sdw_stream,
+};
+static const struct snd_soc_component_driver amd_sdw_dai_component = {
- .name = "soundwire",
+};
+static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) +{
- struct sdw_amd_dai_runtime **dai_runtime_array;
- struct snd_soc_dai_driver *dais;
- struct snd_soc_pcm_stream *stream;
- struct device *dev;
- int i, num_dais;
- dev = amd_manager->dev;
- num_dais = amd_manager->num_dout_ports + amd_manager->num_din_ports;
- dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
- if (!dais)
return -ENOMEM;
- dai_runtime_array = devm_kcalloc(dev, num_dais,
sizeof(struct sdw_amd_dai_runtime *),
GFP_KERNEL);
- if (!dai_runtime_array)
return -ENOMEM;
- amd_manager->dai_runtime_array = dai_runtime_array;
- for (i = 0; i < num_dais; i++) {
dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", amd_manager->instance,
i);
if (!dais[i].name)
return -ENOMEM;
if (i < amd_manager->num_dout_ports)
stream = &dais[i].playback;
else
stream = &dais[i].capture;
stream->channels_min = 2;
stream->channels_max = 2;
stream->rates = SNDRV_PCM_RATE_48000;
stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
dais[i].ops = &amd_sdw_dai_ops;
dais[i].id = i;
- }
- return devm_snd_soc_register_component(dev, &amd_sdw_dai_component,
dais, num_dais);
+}
static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -664,6 +840,12 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret); return ret; }
- ret = amd_sdw_register_dais(amd_manager);
- if (ret) {
dev_err(dev, "CPU DAI registration failed\n");
sdw_bus_master_delete(&amd_manager->bus);
return ret;
- } dev_set_drvdata(dev, amd_manager); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /*
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 0d4b8653877e..cad26034087b 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -204,6 +204,24 @@ struct sdw_manager_dp_reg { u32 lane_ctrl_ch_en_reg; };
+/*
- SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports)
- whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
- Below is the CPU DAI <->Manager port number mapping
- i.e SDW0 Pin0 -> port number 0 -> AUDIO0 TX
SDW0 Pin1 -> Port number 1 -> AUDIO1 TX
SDW0 Pin2 -> Port number 2 -> AUDIO2 TX
SDW0 Pin3 -> port number 3 -> AUDIO0 RX
SDW0 Pin4 -> Port number 4 -> AUDIO1 RX
SDW0 Pin5 -> Port number 5 -> AUDIO2 RX
- Whereas for SDW1 instance
- SDW1 Pin0 -> port number 0 -> AUDIO1 TX
- SDW1 Pin1 -> Port number 1 -> AUDIO1 RX
- Same mapping should be used for programming DMA controller registers in SoundWire DMA driver.
- i.e if AUDIO0 TX channel is selected then we need to use AUDIO0 TX registers for DMA programming
- in SoundWire DMA driver.
- */
static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0, ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0}, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index c14a291a40e8..ac537419301d 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -23,6 +23,21 @@ struct sdw_manager_reg_mask { u32 acp_sdw_intr_mask; };
+/**
- struct sdw_amd_dai_runtime: AMD sdw dai runtime data
- @name: SoundWire stream name
- @stream: stream runtime
- @bus: Bus handle
- @stream_type: Stream type
- */
+struct sdw_amd_dai_runtime {
- char *name;
- struct sdw_stream_runtime *stream;
- struct sdw_bus *bus;
- enum sdw_stream_type stream_type;
+};
/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
@@ -40,6 +55,7 @@ struct sdw_manager_reg_mask {
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
*/
- @dai_runtime_array: dai runtime array
struct amd_sdw_manager { struct sdw_bus bus; @@ -63,5 +79,7 @@ struct amd_sdw_manager { u32 quirks; u32 wake_en_mask; u32 power_mode_mask;
- struct sdw_amd_dai_runtime **dai_runtime_array;
};
#endif
2.34.1
On 15/03/23 15:28, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Register dai ops for SoundWire manager instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-4-Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 182 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 18 +++ include/linux/soundwire/sdw_amd.h | 18 +++ 3 files changed, 218 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index a5cf6acd936c..dd7fd4036d89 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -581,6 +581,182 @@ static const struct sdw_master_ops amd_sdw_ops = { .read_ping_status = amd_sdw_read_ping_status, };
+static int amd_sdw_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
+{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- struct sdw_stream_config sconfig;
- struct sdw_port_config *pconfig;
- int ch, dir;
- int ret;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return -EIO;
- ch = params_channels(params);
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
- else
dir = SDW_DATA_DIR_TX;
- dev_dbg(amd_manager->dev, "dir:%d dai->id:0x%x\n", dir, dai->id);
- sconfig.direction = dir;
- sconfig.ch_count = ch;
- sconfig.frame_rate = params_rate(params);
- sconfig.type = dai_runtime->stream_type;
- sconfig.bps = snd_pcm_format_width(params_format(params));
- /* Port configuration */
- pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL);
- if (!pconfig) {
ret = -ENOMEM;
goto error;
- }
- pconfig->num = dai->id;
- pconfig->ch_mask = (1 << ch) - 1;
- ret = sdw_stream_add_master(&amd_manager->bus, &sconfig,
pconfig, 1, dai_runtime->stream);
- if (ret)
dev_err(amd_manager->dev, "add manager to stream failed:%d\n", ret);
- kfree(pconfig);
+error:
- return ret;
+}
+static int amd_sdw_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- int ret;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return -EIO;
- ret = sdw_stream_remove_master(&amd_manager->bus, dai_runtime->stream);
- if (ret < 0)
dev_err(dai->dev, "remove manager from stream %s failed: %d\n",
dai_runtime->stream->name, ret);
- return ret;
+}
+static int amd_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (stream) {
/* first paranoia check */
if (dai_runtime) {
dev_err(dai->dev,
"dai_runtime already allocated for dai %s\n",
dai->name);
single line would look better
will fix it.
return -EINVAL;
}
/* allocate and set dai_runtime info */
dai_runtime = kzalloc(sizeof(*dai_runtime), GFP_KERNEL);
if (!dai_runtime)
return -ENOMEM;
dai_runtime->stream_type = SDW_STREAM_PCM;
dai_runtime->bus = &amd_manager->bus;
dai_runtime->stream = stream;
amd_manager->dai_runtime_array[dai->id] = dai_runtime;
- } else {
/* second paranoia check */
if (!dai_runtime) {
dev_err(dai->dev,
"dai_runtime not allocated for dai %s\n",
dai->name);
return -EINVAL;
}
/* for NULL stream we release allocated dai_runtime */
kfree(dai_runtime);
amd_manager->dai_runtime_array[dai->id] = NULL;
- }
- return 0;
+}
+static int amd_pcm_set_sdw_stream(struct snd_soc_dai *dai, void *stream, int direction) +{
- return amd_set_sdw_stream(dai, stream, direction);
+}
+static void *amd_get_sdw_stream(struct snd_soc_dai *dai, int direction) +{
- struct amd_sdw_manager *amd_manager = snd_soc_dai_get_drvdata(dai);
- struct sdw_amd_dai_runtime *dai_runtime;
- dai_runtime = amd_manager->dai_runtime_array[dai->id];
- if (!dai_runtime)
return ERR_PTR(-EINVAL);
- return dai_runtime->stream;
+}
+static const struct snd_soc_dai_ops amd_sdw_dai_ops = {
- .hw_params = amd_sdw_hw_params,
- .hw_free = amd_sdw_hw_free,
- .set_stream = amd_pcm_set_sdw_stream,
- .get_stream = amd_get_sdw_stream,
+};
+static const struct snd_soc_component_driver amd_sdw_dai_component = {
- .name = "soundwire",
+};
+static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) +{
- struct sdw_amd_dai_runtime **dai_runtime_array;
- struct snd_soc_dai_driver *dais;
- struct snd_soc_pcm_stream *stream;
- struct device *dev;
- int i, num_dais;
- dev = amd_manager->dev;
- num_dais = amd_manager->num_dout_ports + amd_manager->num_din_ports;
- dais = devm_kcalloc(dev, num_dais, sizeof(*dais), GFP_KERNEL);
- if (!dais)
return -ENOMEM;
- dai_runtime_array = devm_kcalloc(dev, num_dais,
sizeof(struct sdw_amd_dai_runtime *),
GFP_KERNEL);
- if (!dai_runtime_array)
return -ENOMEM;
- amd_manager->dai_runtime_array = dai_runtime_array;
- for (i = 0; i < num_dais; i++) {
dais[i].name = devm_kasprintf(dev, GFP_KERNEL, "SDW%d Pin%d", amd_manager->instance,
i);
if (!dais[i].name)
return -ENOMEM;
if (i < amd_manager->num_dout_ports)
stream = &dais[i].playback;
else
stream = &dais[i].capture;
stream->channels_min = 2;
stream->channels_max = 2;
stream->rates = SNDRV_PCM_RATE_48000;
stream->formats = SNDRV_PCM_FMTBIT_S16_LE;
dais[i].ops = &amd_sdw_dai_ops;
dais[i].id = i;
- }
- return devm_snd_soc_register_component(dev, &amd_sdw_dai_component,
dais, num_dais);
+}
static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -664,6 +840,12 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) dev_err(dev, "Failed to register SoundWire manager(%d)\n", ret); return ret; }
- ret = amd_sdw_register_dais(amd_manager);
- if (ret) {
dev_err(dev, "CPU DAI registration failed\n");
sdw_bus_master_delete(&amd_manager->bus);
return ret;
- } dev_set_drvdata(dev, amd_manager); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /*
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 0d4b8653877e..cad26034087b 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -204,6 +204,24 @@ struct sdw_manager_dp_reg { u32 lane_ctrl_ch_en_reg; };
+/*
- SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports)
- whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
- Below is the CPU DAI <->Manager port number mapping
- i.e SDW0 Pin0 -> port number 0 -> AUDIO0 TX
SDW0 Pin1 -> Port number 1 -> AUDIO1 TX
SDW0 Pin2 -> Port number 2 -> AUDIO2 TX
SDW0 Pin3 -> port number 3 -> AUDIO0 RX
SDW0 Pin4 -> Port number 4 -> AUDIO1 RX
SDW0 Pin5 -> Port number 5 -> AUDIO2 RX
- Whereas for SDW1 instance
- SDW1 Pin0 -> port number 0 -> AUDIO1 TX
- SDW1 Pin1 -> Port number 1 -> AUDIO1 RX
- Same mapping should be used for programming DMA controller registers in SoundWire DMA driver.
- i.e if AUDIO0 TX channel is selected then we need to use AUDIO0 TX registers for DMA programming
- in SoundWire DMA driver.
- */
static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { {ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0, ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0}, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index c14a291a40e8..ac537419301d 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -23,6 +23,21 @@ struct sdw_manager_reg_mask { u32 acp_sdw_intr_mask; };
+/**
- struct sdw_amd_dai_runtime: AMD sdw dai runtime data
- @name: SoundWire stream name
- @stream: stream runtime
- @bus: Bus handle
- @stream_type: Stream type
- */
+struct sdw_amd_dai_runtime {
- char *name;
- struct sdw_stream_runtime *stream;
- struct sdw_bus *bus;
- enum sdw_stream_type stream_type;
+};
/**
- struct amd_sdw_manager - amd manager driver context
- @bus: bus handle
@@ -40,6 +55,7 @@ struct sdw_manager_reg_mask {
- @quirks: SoundWire manager quirks
- @wake_en_mask: wake enable mask per SoundWire manager
- @power_mode_mask: flag interprets amd SoundWire manager power mode
*/
- @dai_runtime_array: dai runtime array
struct amd_sdw_manager { struct sdw_bus bus; @@ -63,5 +79,7 @@ struct amd_sdw_manager { u32 quirks; u32 wake_en_mask; u32 power_mode_mask;
- struct sdw_amd_dai_runtime **dai_runtime_array;
};
#endif
2.34.1
Enable build for SoundWire manager driver for AMD platforms.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230220100418.76754-5-Vijendar.Mukunda@amd.com --- drivers/soundwire/Kconfig | 10 ++++++++++ drivers/soundwire/Makefile | 4 ++++ 2 files changed, 14 insertions(+)
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 2b7795233282..983afe3570b2 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -46,4 +46,14 @@ config SOUNDWIRE_QCOM config SOUNDWIRE_GENERIC_ALLOCATION tristate
+config SOUNDWIRE_AMD + tristate "AMD SoundWire Manager driver" + select SOUNDWIRE_GENERIC_ALLOCATION + depends on ACPI && SND_SOC + help + SoundWire AMD Manager driver. + If you have an AMD platform which has a SoundWire Manager then + enable this config option to get the SoundWire support for that + device. + endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index ca97414ada70..5956229d3eb3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o #Qualcomm driver soundwire-qcom-y := qcom.o obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o + +#AMD driver +soundwire-amd-y := amd_manager.o +obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Enable build for SoundWire manager driver for AMD platforms.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230220100418.76754-5-Vijendar.Mukunda@amd.com
drivers/soundwire/Kconfig | 10 ++++++++++ drivers/soundwire/Makefile | 4 ++++ 2 files changed, 14 insertions(+)
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 2b7795233282..983afe3570b2 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -46,4 +46,14 @@ config SOUNDWIRE_QCOM config SOUNDWIRE_GENERIC_ALLOCATION tristate
+config SOUNDWIRE_AMD
- tristate "AMD SoundWire Manager driver"
Alphabetically sorted please
- select SOUNDWIRE_GENERIC_ALLOCATION
- depends on ACPI && SND_SOC
- help
SoundWire AMD Manager driver.
If you have an AMD platform which has a SoundWire Manager then
enable this config option to get the SoundWire support for that
device.
endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index ca97414ada70..5956229d3eb3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o #Qualcomm driver soundwire-qcom-y := qcom.o obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
+#AMD driver +soundwire-amd-y := amd_manager.o +obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
here as well
-- 2.34.1
On 15/03/23 15:29, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Enable build for SoundWire manager driver for AMD platforms.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230220100418.76754-5-Vijendar.Mukunda@amd.com
drivers/soundwire/Kconfig | 10 ++++++++++ drivers/soundwire/Makefile | 4 ++++ 2 files changed, 14 insertions(+)
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 2b7795233282..983afe3570b2 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -46,4 +46,14 @@ config SOUNDWIRE_QCOM config SOUNDWIRE_GENERIC_ALLOCATION tristate
+config SOUNDWIRE_AMD
- tristate "AMD SoundWire Manager driver"
Alphabetically sorted please
will fix it.
- select SOUNDWIRE_GENERIC_ALLOCATION
- depends on ACPI && SND_SOC
- help
SoundWire AMD Manager driver.
If you have an AMD platform which has a SoundWire Manager then
enable this config option to get the SoundWire support for that
device.
endif diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index ca97414ada70..5956229d3eb3 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -26,3 +26,7 @@ obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o #Qualcomm driver soundwire-qcom-y := qcom.o obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
+#AMD driver +soundwire-amd-y := amd_manager.o +obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
here as well
Will fix it.
-- 2.34.1
Add support for handling SoundWire manager interrupts.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-6-Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_manager.c | 130 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 1 + include/linux/soundwire/sdw_amd.h | 7 ++ 3 files changed, 138 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index dd7fd4036d89..165078beca2e 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -357,6 +357,51 @@ static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sd return SDW_CMD_OK; }
+static void amd_sdw_fill_slave_status(struct amd_sdw_manager *amd_manager, u16 index, u32 status) +{ + switch (status) { + case SDW_SLAVE_ATTACHED: + amd_manager->status[index] = SDW_SLAVE_ATTACHED; + break; + case SDW_SLAVE_UNATTACHED: + amd_manager->status[index] = SDW_SLAVE_UNATTACHED; + break; + case SDW_SLAVE_ALERT: + amd_manager->status[index] = SDW_SLAVE_ALERT; + break; + default: + amd_manager->status[index] = SDW_SLAVE_RESERVED; + break; + } +} + +static void amd_sdw_process_ping_status(u64 response, struct amd_sdw_manager *amd_manager) +{ + u64 slave_stat; + u32 val; + u16 dev_index; + + /* slave status response */ + slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response); + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8; + dev_dbg(amd_manager->dev, "slave_stat:0x%llx\n", slave_stat); + for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) { + val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK; + dev_dbg(amd_manager->dev, "val:0x%x\n", val); + amd_sdw_fill_slave_status(amd_manager, dev_index, val); + } +} + +static void amd_sdw_read_and_process_ping_status(struct amd_sdw_manager *amd_manager) +{ + u64 response; + + mutex_lock(&amd_manager->bus.msg_lock); + response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0); + mutex_unlock(&amd_manager->bus.msg_lock); + amd_sdw_process_ping_status(response, amd_manager); +} + static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) { struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); @@ -757,6 +802,89 @@ static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) dais, num_dais); }
+static void amd_sdw_update_slave_status_work(struct work_struct *work) +{ + struct amd_sdw_manager *amd_manager = + container_of(work, struct amd_sdw_manager, amd_sdw_work); + int retry_count = 0; + + if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) { + acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); + acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); + } + +update_status: + sdw_handle_slave_status(&amd_manager->bus, amd_manager->status); + /* + * During the peripheral enumeration sequence, the SoundWire manager interrupts + * are masked. Once the device number programming is done for all peripherals, + * interrupts will be unmasked. Read the peripheral device status from ping command + * and process the response. This sequence will ensure all peripheral devices enumerated + * and initialized properly. + */ + if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) { + if (retry_count++ < SDW_MAX_DEVICES) { + acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); + acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, + amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); + amd_sdw_read_and_process_ping_status(amd_manager); + goto update_status; + } else { + dev_err_ratelimited(amd_manager->dev, + "Device0 detected after %d iterations\n", + retry_count); + } + } +} + +static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_change_8to11, + struct amd_sdw_manager *amd_manager) +{ + u64 slave_stat; + u32 val; + int dev_index; + + if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED) + memset(amd_manager->status, 0, sizeof(amd_manager->status)); + slave_stat = status_change_0to7; + slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32; + dev_dbg(amd_manager->dev, "status_change_0to7:0x%x status_change_8to11:0x%x\n", + status_change_0to7, status_change_8to11); + if (slave_stat) { + for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) { + if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) { + val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) & + AMD_SDW_MCP_SLAVE_STATUS_MASK; + amd_sdw_fill_slave_status(amd_manager, dev_index, val); + } + } + } +} + +static void amd_sdw_irq_thread(struct work_struct *work) +{ + struct amd_sdw_manager *amd_manager = + container_of(work, struct amd_sdw_manager, amd_sdw_irq_thread); + u32 status_change_8to11; + u32 status_change_0to7; + + status_change_8to11 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11); + status_change_0to7 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7); + dev_dbg(amd_manager->dev, "[SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n", + amd_manager->instance, status_change_0to7, status_change_8to11); + if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) { + amd_sdw_read_and_process_ping_status(amd_manager); + } else { + /* Check for the updated status on peripheral device */ + amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager); + } + if (status_change_8to11 || status_change_0to7) + schedule_work(&amd_manager->amd_sdw_work); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7); +} + static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -847,6 +975,8 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) return ret; } dev_set_drvdata(dev, amd_manager); + INIT_WORK(&amd_manager->amd_sdw_irq_thread, amd_sdw_irq_thread); + INIT_WORK(&amd_manager->amd_sdw_work, amd_sdw_update_slave_status_work); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /* * Instead of having lengthy probe sequence, use deferred probe. diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index cad26034087b..807bc5a314d8 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -185,6 +185,7 @@ #define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF +#define AMD_SDW_PREQ_INTR_STAT BIT(19)
enum amd_sdw_cmd_type { AMD_SDW_CMD_PING = 0, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index ac537419301d..df60bc0de6fc 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -45,8 +45,11 @@ struct sdw_amd_dai_runtime { * @mmio: SoundWire registers mmio base * @acp_mmio: acp registers mmio base * @reg_mask: register mask structure per manager instance + * @amd_sdw_irq_thread: SoundWire manager irq workqueue + * @amd_sdw_work: peripheral status work queue * @probe_work: SoundWire manager probe workqueue * @acp_sdw_lock: mutex to protect acp share register access + * @status: peripheral devices status array * @num_din_ports: number of input ports * @num_dout_ports: number of output ports * @cols_index: Column index in frame shape @@ -65,10 +68,14 @@ struct amd_sdw_manager { void __iomem *acp_mmio;
struct sdw_manager_reg_mask *reg_mask; + struct work_struct amd_sdw_irq_thread; + struct work_struct amd_sdw_work; struct work_struct probe_work; /* mutex to protect acp common register access */ struct mutex *acp_sdw_lock;
+ enum sdw_slave_status status[SDW_MAX_DEVICES + 1]; + int num_din_ports; int num_dout_ports;
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Add support for handling SoundWire manager interrupts.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-6-Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 130 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 1 + include/linux/soundwire/sdw_amd.h | 7 ++ 3 files changed, 138 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index dd7fd4036d89..165078beca2e 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -357,6 +357,51 @@ static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sd return SDW_CMD_OK; }
+static void amd_sdw_fill_slave_status(struct amd_sdw_manager *amd_manager, u16 index, u32 status) +{
- switch (status) {
- case SDW_SLAVE_ATTACHED:
amd_manager->status[index] = SDW_SLAVE_ATTACHED;
break;
- case SDW_SLAVE_UNATTACHED:
amd_manager->status[index] = SDW_SLAVE_UNATTACHED;
break;
- case SDW_SLAVE_ALERT:
amd_manager->status[index] = SDW_SLAVE_ALERT;
break;
why not:
case SDW_SLAVE_ATTACHED: case SDW_SLAVE_UNATTACHED: case SDW_SLAVE_ALERT: amd_manager->status[index] = status; break;
- default:
amd_manager->status[index] = SDW_SLAVE_RESERVED;
break;
- }
+}
+static void amd_sdw_process_ping_status(u64 response, struct amd_sdw_manager *amd_manager) +{
- u64 slave_stat;
- u32 val;
- u16 dev_index;
- /* slave status response */
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(amd_manager->dev, "slave_stat:0x%llx\n", slave_stat);
- for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK;
dev_dbg(amd_manager->dev, "val:0x%x\n", val);
amd_sdw_fill_slave_status(amd_manager, dev_index, val);
- }
+}
+static void amd_sdw_read_and_process_ping_status(struct amd_sdw_manager *amd_manager) +{
- u64 response;
- mutex_lock(&amd_manager->bus.msg_lock);
- response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
- mutex_unlock(&amd_manager->bus.msg_lock);
- amd_sdw_process_ping_status(response, amd_manager);
+}
static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) { struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); @@ -757,6 +802,89 @@ static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) dais, num_dais); }
+static void amd_sdw_update_slave_status_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_work);
- int retry_count = 0;
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- }
+update_status:
- sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
- /*
* During the peripheral enumeration sequence, the SoundWire manager interrupts
* are masked. Once the device number programming is done for all peripherals,
* interrupts will be unmasked. Read the peripheral device status from ping command
* and process the response. This sequence will ensure all peripheral devices enumerated
* and initialized properly.
*/
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
if (retry_count++ < SDW_MAX_DEVICES) {
acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11,
amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
amd_sdw_read_and_process_ping_status(amd_manager);
goto update_status;
goto are mostly used for error handling, i dont thing case here deserves a goto, can you please change this...
} else {
dev_err_ratelimited(amd_manager->dev,
"Device0 detected after %d iterations\n",
retry_count);
}
- }
+}
+static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_change_8to11,
struct amd_sdw_manager *amd_manager)
+{
- u64 slave_stat;
- u32 val;
- int dev_index;
- if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED)
memset(amd_manager->status, 0, sizeof(amd_manager->status));
- slave_stat = status_change_0to7;
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32;
- dev_dbg(amd_manager->dev, "status_change_0to7:0x%x status_change_8to11:0x%x\n",
status_change_0to7, status_change_8to11);
- if (slave_stat) {
for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) {
val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) &
AMD_SDW_MCP_SLAVE_STATUS_MASK;
amd_sdw_fill_slave_status(amd_manager, dev_index, val);
}
}
- }
+}
+static void amd_sdw_irq_thread(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_irq_thread);
- u32 status_change_8to11;
- u32 status_change_0to7;
- status_change_8to11 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
- status_change_0to7 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
- dev_dbg(amd_manager->dev, "[SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n",
amd_manager->instance, status_change_0to7, status_change_8to11);
- if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) {
amd_sdw_read_and_process_ping_status(amd_manager);
- } else {
/* Check for the updated status on peripheral device */
amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager);
- }
- if (status_change_8to11 || status_change_0to7)
schedule_work(&amd_manager->amd_sdw_work);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
+}
static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -847,6 +975,8 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) return ret; } dev_set_drvdata(dev, amd_manager);
- INIT_WORK(&amd_manager->amd_sdw_irq_thread, amd_sdw_irq_thread);
- INIT_WORK(&amd_manager->amd_sdw_work, amd_sdw_update_slave_status_work); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /*
- Instead of having lengthy probe sequence, use deferred probe.
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index cad26034087b..807bc5a314d8 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -185,6 +185,7 @@ #define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF +#define AMD_SDW_PREQ_INTR_STAT BIT(19)
enum amd_sdw_cmd_type { AMD_SDW_CMD_PING = 0, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index ac537419301d..df60bc0de6fc 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -45,8 +45,11 @@ struct sdw_amd_dai_runtime {
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @amd_sdw_irq_thread: SoundWire manager irq workqueue
- @amd_sdw_work: peripheral status work queue
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @status: peripheral devices status array
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
@@ -65,10 +68,14 @@ struct amd_sdw_manager { void __iomem *acp_mmio;
struct sdw_manager_reg_mask *reg_mask;
struct work_struct amd_sdw_irq_thread;
struct work_struct amd_sdw_work; struct work_struct probe_work; /* mutex to protect acp common register access */ struct mutex *acp_sdw_lock;
enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
int num_din_ports; int num_dout_ports;
-- 2.34.1
On 15/03/23 15:36, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
Add support for handling SoundWire manager interrupts.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-6-Vijendar.Mukunda@amd.com
drivers/soundwire/amd_manager.c | 130 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 1 + include/linux/soundwire/sdw_amd.h | 7 ++ 3 files changed, 138 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index dd7fd4036d89..165078beca2e 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -357,6 +357,51 @@ static enum sdw_command_response amd_sdw_xfer_msg(struct sdw_bus *bus, struct sd return SDW_CMD_OK; }
+static void amd_sdw_fill_slave_status(struct amd_sdw_manager *amd_manager, u16 index, u32 status) +{
- switch (status) {
- case SDW_SLAVE_ATTACHED:
amd_manager->status[index] = SDW_SLAVE_ATTACHED;
break;
- case SDW_SLAVE_UNATTACHED:
amd_manager->status[index] = SDW_SLAVE_UNATTACHED;
break;
- case SDW_SLAVE_ALERT:
amd_manager->status[index] = SDW_SLAVE_ALERT;
break;
why not:
case SDW_SLAVE_ATTACHED: case SDW_SLAVE_UNATTACHED: case SDW_SLAVE_ALERT: amd_manager->status[index] = status; break;
Will fix it.
- default:
amd_manager->status[index] = SDW_SLAVE_RESERVED;
break;
- }
+}
+static void amd_sdw_process_ping_status(u64 response, struct amd_sdw_manager *amd_manager) +{
- u64 slave_stat;
- u32 val;
- u16 dev_index;
- /* slave status response */
- slave_stat = FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_0_3, response);
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STAT_4_11, response) << 8;
- dev_dbg(amd_manager->dev, "slave_stat:0x%llx\n", slave_stat);
- for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
val = (slave_stat >> (dev_index * 2)) & AMD_SDW_MCP_SLAVE_STATUS_MASK;
dev_dbg(amd_manager->dev, "val:0x%x\n", val);
amd_sdw_fill_slave_status(amd_manager, dev_index, val);
- }
+}
+static void amd_sdw_read_and_process_ping_status(struct amd_sdw_manager *amd_manager) +{
- u64 response;
- mutex_lock(&amd_manager->bus.msg_lock);
- response = amd_sdw_send_cmd_get_resp(amd_manager, 0, 0);
- mutex_unlock(&amd_manager->bus.msg_lock);
- amd_sdw_process_ping_status(response, amd_manager);
+}
static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) { struct amd_sdw_manager *amd_manager = to_amd_sdw(bus); @@ -757,6 +802,89 @@ static int amd_sdw_register_dais(struct amd_sdw_manager *amd_manager) dais, num_dais); }
+static void amd_sdw_update_slave_status_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_work);
- int retry_count = 0;
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- }
+update_status:
- sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
- /*
* During the peripheral enumeration sequence, the SoundWire manager interrupts
* are masked. Once the device number programming is done for all peripherals,
* interrupts will be unmasked. Read the peripheral device status from ping command
* and process the response. This sequence will ensure all peripheral devices enumerated
* and initialized properly.
*/
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
if (retry_count++ < SDW_MAX_DEVICES) {
acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11,
amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
amd_sdw_read_and_process_ping_status(amd_manager);
goto update_status;
goto are mostly used for error handling, i dont thing case here deserves a goto, can you please change this...
I agree. goto statements will be used mostly for error handling. But this is a different scenario. We have used goto statement to call sdw_handle_slave_status() from if statement to make sure all peripheral devices are enumerated and initialized properly. Please let us know if you are expecting code to be modified as mentioned below.
sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) { acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); amd_sdw_read_and_process_ping_status(amd_manager); sdw_handle_slave_status(&amd_manager->bus, amd_manager->status); }
We have to check any race conditions occurs or not if we implement code as mentioned above. IMHO, it is still good to go with goto statement implementation.
} else {
dev_err_ratelimited(amd_manager->dev,
"Device0 detected after %d iterations\n",
retry_count);
}
- }
+}
+static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_change_8to11,
struct amd_sdw_manager *amd_manager)
+{
- u64 slave_stat;
- u32 val;
- int dev_index;
- if (status_change_0to7 == AMD_SDW_SLAVE_0_ATTACHED)
memset(amd_manager->status, 0, sizeof(amd_manager->status));
- slave_stat = status_change_0to7;
- slave_stat |= FIELD_GET(AMD_SDW_MCP_SLAVE_STATUS_8TO_11, status_change_8to11) << 32;
- dev_dbg(amd_manager->dev, "status_change_0to7:0x%x status_change_8to11:0x%x\n",
status_change_0to7, status_change_8to11);
- if (slave_stat) {
for (dev_index = 0; dev_index <= SDW_MAX_DEVICES; ++dev_index) {
if (slave_stat & AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(dev_index)) {
val = (slave_stat >> AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(dev_index)) &
AMD_SDW_MCP_SLAVE_STATUS_MASK;
amd_sdw_fill_slave_status(amd_manager, dev_index, val);
}
}
- }
+}
+static void amd_sdw_irq_thread(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_irq_thread);
- u32 status_change_8to11;
- u32 status_change_0to7;
- status_change_8to11 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
- status_change_0to7 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
- dev_dbg(amd_manager->dev, "[SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n",
amd_manager->instance, status_change_0to7, status_change_8to11);
- if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) {
amd_sdw_read_and_process_ping_status(amd_manager);
- } else {
/* Check for the updated status on peripheral device */
amd_sdw_update_slave_status(status_change_0to7, status_change_8to11, amd_manager);
- }
- if (status_change_8to11 || status_change_0to7)
schedule_work(&amd_manager->amd_sdw_work);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
- acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7);
+}
static void amd_sdw_probe_work(struct work_struct *work) { struct amd_sdw_manager *amd_manager = container_of(work, struct amd_sdw_manager, @@ -847,6 +975,8 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) return ret; } dev_set_drvdata(dev, amd_manager);
- INIT_WORK(&amd_manager->amd_sdw_irq_thread, amd_sdw_irq_thread);
- INIT_WORK(&amd_manager->amd_sdw_work, amd_sdw_update_slave_status_work); INIT_WORK(&amd_manager->probe_work, amd_sdw_probe_work); /*
- Instead of having lengthy probe sequence, use deferred probe.
diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index cad26034087b..807bc5a314d8 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -185,6 +185,7 @@ #define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10 #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF +#define AMD_SDW_PREQ_INTR_STAT BIT(19)
enum amd_sdw_cmd_type { AMD_SDW_CMD_PING = 0, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index ac537419301d..df60bc0de6fc 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -45,8 +45,11 @@ struct sdw_amd_dai_runtime {
- @mmio: SoundWire registers mmio base
- @acp_mmio: acp registers mmio base
- @reg_mask: register mask structure per manager instance
- @amd_sdw_irq_thread: SoundWire manager irq workqueue
- @amd_sdw_work: peripheral status work queue
- @probe_work: SoundWire manager probe workqueue
- @acp_sdw_lock: mutex to protect acp share register access
- @status: peripheral devices status array
- @num_din_ports: number of input ports
- @num_dout_ports: number of output ports
- @cols_index: Column index in frame shape
@@ -65,10 +68,14 @@ struct amd_sdw_manager { void __iomem *acp_mmio;
struct sdw_manager_reg_mask *reg_mask;
struct work_struct amd_sdw_irq_thread;
struct work_struct amd_sdw_work; struct work_struct probe_work; /* mutex to protect acp common register access */ struct mutex *acp_sdw_lock;
enum sdw_slave_status status[SDW_MAX_DEVICES + 1];
int num_din_ports; int num_dout_ports;
-- 2.34.1
On 16-03-23, 22:34, Mukunda,Vijendar wrote:
On 15/03/23 15:36, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
+static void amd_sdw_update_slave_status_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_work);
- int retry_count = 0;
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- }
+update_status:
- sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
- /*
* During the peripheral enumeration sequence, the SoundWire manager interrupts
* are masked. Once the device number programming is done for all peripherals,
* interrupts will be unmasked. Read the peripheral device status from ping command
* and process the response. This sequence will ensure all peripheral devices enumerated
* and initialized properly.
*/
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
if (retry_count++ < SDW_MAX_DEVICES) {
acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11,
amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
amd_sdw_read_and_process_ping_status(amd_manager);
goto update_status;
goto are mostly used for error handling, i dont thing case here deserves a goto, can you please change this...
I agree. goto statements will be used mostly for error handling. But this is a different scenario. We have used goto statement to call sdw_handle_slave_status() from if statement to make sure all peripheral devices are enumerated and initialized properly. Please let us know if you are expecting code to be modified as mentioned below.
sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) { acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); amd_sdw_read_and_process_ping_status(amd_manager); sdw_handle_slave_status(&amd_manager->bus, amd_manager->status); }
We have to check any race conditions occurs or not if we implement code as mentioned above.
what race are you talking about
IMHO, it is still good to go with goto statement implementation.
Since you keep checking, essentially this seems to be a loop?
On 17/03/23 19:06, Vinod Koul wrote:
On 16-03-23, 22:34, Mukunda,Vijendar wrote:
On 15/03/23 15:36, Vinod Koul wrote:
On 07-03-23, 19:01, Vijendar Mukunda wrote:
+static void amd_sdw_update_slave_status_work(struct work_struct *work) +{
- struct amd_sdw_manager *amd_manager =
container_of(work, struct amd_sdw_manager, amd_sdw_work);
- int retry_count = 0;
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(0, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
- }
+update_status:
- sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
- /*
* During the peripheral enumeration sequence, the SoundWire manager interrupts
* are masked. Once the device number programming is done for all peripherals,
* interrupts will be unmasked. Read the peripheral device status from ping command
* and process the response. This sequence will ensure all peripheral devices enumerated
* and initialized properly.
*/
- if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) {
if (retry_count++ < SDW_MAX_DEVICES) {
acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio +
ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7);
acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11,
amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
amd_sdw_read_and_process_ping_status(amd_manager);
goto update_status;
goto are mostly used for error handling, i dont thing case here deserves a goto, can you please change this...
I agree. goto statements will be used mostly for error handling. But this is a different scenario. We have used goto statement to call sdw_handle_slave_status() from if statement to make sure all peripheral devices are enumerated and initialized properly. Please let us know if you are expecting code to be modified as mentioned below.
sdw_handle_slave_status(&amd_manager->bus, amd_manager->status);
if (amd_manager->status[0] == SDW_SLAVE_ATTACHED) { acp_reg_writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); acp_reg_writel(AMD_SDW_IRQ_MASK_8TO11, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); amd_sdw_read_and_process_ping_status(amd_manager); sdw_handle_slave_status(&amd_manager->bus, amd_manager->status); }
We have to check any race conditions occurs or not if we implement code as mentioned above.
what race are you talking about
Our intention is to convey that we need to verify the above logic and check for faulty case handling where status[0] is keep on updated as "ATTACHED" when multiple peripheral devices are connected over the link.
IMHO, it is still good to go with goto statement implementation.
Since you keep checking, essentially this seems to be a loop?
In normal scenario , if condition gets executed once and exits the function. In faulty case, if status[0] is still reported as ATTACHED, it runs in loop till retry_count reaches SDW_MAX_DEVICES. We want to keep checking the status until ping command reports correct status on all other devices other than "ATTACHED" on device0 in a loop using goto statement with max retry count.
Add support for runtime pm ops for AMD SoundWire manager driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-7-Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_manager.c | 140 ++++++++++++++++++++++++++++++ drivers/soundwire/amd_manager.h | 3 + include/linux/soundwire/sdw_amd.h | 17 ++++ 3 files changed, 160 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index 165078beca2e..c3c0bee57bd1 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -14,6 +14,7 @@ #include <linux/slab.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> +#include <linux/pm_runtime.h> #include <linux/wait.h> #include <sound/pcm_params.h> #include <sound/soc.h> @@ -133,6 +134,12 @@ static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK); }
+static int amd_deinit_sdw_manager(struct amd_sdw_manager *amd_manager) +{ + amd_disable_sdw_interrupts(amd_manager); + return amd_disable_sdw_manager(amd_manager); +} + static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) { u32 frame_size; @@ -904,6 +911,12 @@ static void amd_sdw_probe_work(struct work_struct *work) return; amd_sdw_set_frameshape(amd_manager); } + /* Enable runtime PM */ + pm_runtime_set_autosuspend_delay(amd_manager->dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(amd_manager->dev); + pm_runtime_mark_last_busy(amd_manager->dev); + pm_runtime_set_active(amd_manager->dev); + pm_runtime_enable(amd_manager->dev); }
static int amd_sdw_manager_probe(struct platform_device *pdev) @@ -989,17 +1002,144 @@ static int amd_sdw_manager_remove(struct platform_device *pdev) { struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev);
+ pm_runtime_disable(&pdev->dev); cancel_work_sync(&amd_manager->probe_work); amd_disable_sdw_interrupts(amd_manager); sdw_bus_master_delete(&amd_manager->bus); return amd_disable_sdw_manager(amd_manager); }
+static int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager) +{ + u32 val; + int ret; + + ret = sdw_bus_prep_clk_stop(&amd_manager->bus); + if (ret < 0 && ret != -ENODATA) { + dev_err(amd_manager->dev, "prepare clock stop failed %d", ret); + return 0; + } + ret = sdw_bus_clk_stop(&amd_manager->bus); + if (ret < 0 && ret != -ENODATA) { + dev_err(amd_manager->dev, "bus clock stop failed %d", ret); + return 0; + } + + ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_CLK_STOP_DONE), ACP_DELAY_US, + AMD_SDW_TIMEOUT, false, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + if (ret) { + dev_err(amd_manager->dev, "SDW%x clock stop failed\n", amd_manager->instance); + return 0; + } + + amd_manager->clk_stopped = true; + if (amd_manager->wake_en_mask) + acp_reg_writel(0x01, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance)); + + dev_dbg(amd_manager->dev, "SDW%x clock stop successful\n", amd_manager->instance); + return 0; +} + +static int amd_sdw_clock_stop_exit(struct amd_sdw_manager *amd_manager) +{ + int ret; + u32 val; + + if (amd_manager->clk_stopped) { + val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + val |= AMD_SDW_CLK_RESUME_REQ; + acp_reg_writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_CLK_RESUME_DONE), + ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + if (val & AMD_SDW_CLK_RESUME_DONE) { + acp_reg_writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + ret = sdw_bus_exit_clk_stop(&amd_manager->bus); + if (ret < 0) + dev_err(amd_manager->dev, "bus failed to exit clock stop %d\n", + ret); + amd_manager->clk_stopped = false; + } + } + if (amd_manager->clk_stopped) { + dev_err(amd_manager->dev, "SDW%x clock stop exit failed\n", amd_manager->instance); + return 0; + } + dev_dbg(amd_manager->dev, "SDW%x clock stop exit successful\n", amd_manager->instance); + return 0; +} + +static int __maybe_unused amd_suspend_runtime(struct device *dev) +{ + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); + struct sdw_bus *bus = &amd_manager->bus; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(bus->dev, "SoundWire manager %d is disabled,\n", + bus->link_id); + return 0; + } + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + return amd_sdw_clock_stop(amd_manager); + } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + ret = amd_sdw_clock_stop(amd_manager); + if (ret) + return ret; + return amd_deinit_sdw_manager(amd_manager); + } + return 0; +} + +static int __maybe_unused amd_resume_runtime(struct device *dev) +{ + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); + struct sdw_bus *bus = &amd_manager->bus; + int ret; + u32 val; + + if (bus->prop.hw_disabled) { + dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + return amd_sdw_clock_stop_exit(amd_manager); + } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + val = acp_reg_readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + if (val) { + val |= AMD_SDW_CLK_RESUME_REQ; + acp_reg_writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + ret = read_poll_timeout(acp_reg_readl, val, (val & AMD_SDW_CLK_RESUME_DONE), + ACP_DELAY_US, AMD_SDW_TIMEOUT, false, + amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + if (val & AMD_SDW_CLK_RESUME_DONE) { + acp_reg_writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); + amd_manager->clk_stopped = false; + } + } + sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET); + amd_init_sdw_manager(amd_manager); + amd_enable_sdw_interrupts(amd_manager); + ret = amd_enable_sdw_manager(amd_manager); + if (ret) + return ret; + amd_sdw_set_frameshape(amd_manager); + } + return 0; +} + +static const struct dev_pm_ops amd_pm = { + SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL) +}; + static struct platform_driver amd_sdw_driver = { .probe = &amd_sdw_manager_probe, .remove = &amd_sdw_manager_remove, .driver = { .name = "amd_sdw_manager", + .pm = &amd_pm, } }; module_platform_driver(amd_sdw_driver); diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 807bc5a314d8..65bc79d8f7c9 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -186,6 +186,9 @@ #define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1E #define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xF #define AMD_SDW_PREQ_INTR_STAT BIT(19) +#define AMD_SDW_CLK_STOP_DONE 1 +#define AMD_SDW_CLK_RESUME_REQ 2 +#define AMD_SDW_CLK_RESUME_DONE 3
enum amd_sdw_cmd_type { AMD_SDW_CMD_PING = 0, diff --git a/include/linux/soundwire/sdw_amd.h b/include/linux/soundwire/sdw_amd.h index df60bc0de6fc..ceecad74aef9 100644 --- a/include/linux/soundwire/sdw_amd.h +++ b/include/linux/soundwire/sdw_amd.h @@ -8,6 +8,21 @@
#include <linux/soundwire/sdw.h>
+/* AMD pm_runtime quirk definitions */ + +/* + * Force the clock to stop(ClockStopMode0) when suspend callback + * is invoked. + */ +#define AMD_SDW_CLK_STOP_MODE 1 + +/* + * Stop the bus when runtime suspend/system level suspend callback + * is invoked. If set, a complete bus reset and re-enumeration will + * be performed when the bus restarts. In-band wake interrupts are + * not supported in this mode. + */ +#define AMD_SDW_POWER_OFF_MODE 2 #define ACP_SDW0 0 #define ACP_SDW1 1
@@ -57,6 +72,7 @@ struct sdw_amd_dai_runtime { * @instance: SoundWire manager instance * @quirks: SoundWire manager quirks * @wake_en_mask: wake enable mask per SoundWire manager + * @clk_stopped: flag set to true when clock is stopped * @power_mode_mask: flag interprets amd SoundWire manager power mode * @dai_runtime_array: dai runtime array */ @@ -86,6 +102,7 @@ struct amd_sdw_manager { u32 quirks; u32 wake_en_mask; u32 power_mode_mask; + bool clk_stopped;
struct sdw_amd_dai_runtime **dai_runtime_array; };
Add wake enable interrupt support for both the SoundWire manager instances.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Link: https://lore.kernel.org/lkml/20230227154801.50319-8-Vijendar.Mukunda@amd.com --- drivers/soundwire/amd_manager.c | 10 ++++++++++ drivers/soundwire/amd_manager.h | 1 + 2 files changed, 11 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index c3c0bee57bd1..70e4fcd7e1e0 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -869,6 +869,13 @@ static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_chang } }
+static void amd_sdw_process_wake_event(struct amd_sdw_manager *amd_manager) +{ + pm_request_resume(amd_manager->dev); + acp_reg_writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance)); + acp_reg_writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11); +} + static void amd_sdw_irq_thread(struct work_struct *work) { struct amd_sdw_manager *amd_manager = @@ -880,6 +887,9 @@ static void amd_sdw_irq_thread(struct work_struct *work) status_change_0to7 = acp_reg_readl(amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_0TO7); dev_dbg(amd_manager->dev, "[SDW%d] SDW INT: 0to7=0x%x, 8to11=0x%x\n", amd_manager->instance, status_change_0to7, status_change_8to11); + if (status_change_8to11 & AMD_SDW_WAKE_STAT_MASK) + return amd_sdw_process_wake_event(amd_manager); + if (status_change_8to11 & AMD_SDW_PREQ_INTR_STAT) { amd_sdw_read_and_process_ping_status(amd_manager); } else { diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 65bc79d8f7c9..cc1a14731fd4 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -189,6 +189,7 @@ #define AMD_SDW_CLK_STOP_DONE 1 #define AMD_SDW_CLK_RESUME_REQ 2 #define AMD_SDW_CLK_RESUME_DONE 3 +#define AMD_SDW_WAKE_STAT_MASK BIT(16)
enum amd_sdw_cmd_type { AMD_SDW_CMD_PING = 0,
Add pm_prepare callback and System level pm ops support for AMD SoundWire manager driver.
Signed-off-by: Vijendar Mukunda Vijendar.Mukunda@amd.com Signed-off-by: Mastan Katragadda Mastan.Katragadda@amd.com --- drivers/soundwire/amd_manager.c | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+)
diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index 70e4fcd7e1e0..8d4117f0dc3c 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -1079,6 +1079,89 @@ static int amd_sdw_clock_stop_exit(struct amd_sdw_manager *amd_manager) return 0; }
+static int amd_resume_child_device(struct device *dev, void *data) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + int ret; + + if (!slave->probed) { + dev_dbg(dev, "skipping device, no probed driver\n"); + return 0; + } + if (!slave->dev_num_sticky) { + dev_dbg(dev, "skipping device, never detected on bus\n"); + return 0; + } + if (!pm_runtime_suspended(dev)) + return 0; + ret = pm_request_resume(dev); + if (ret < 0) + dev_err(dev, "pm_request_resume failed: %d\n", ret); + + return ret; +} + +static int __maybe_unused amd_pm_prepare(struct device *dev) +{ + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); + struct sdw_bus *bus = &amd_manager->bus; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + /* + * When multiple peripheral devices connected over the same link, if SoundWire manager + * device is not in runtime suspend state, observed that device alerts are missing + * without pm_prepare on AMD platforms in clockstop mode0. + */ + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + ret = pm_request_resume(dev); + if (ret < 0) { + dev_err(bus->dev, "pm_request_resume failed: %d\n", ret); + return 0; + } + } + /* To force peripheral devices to system level suspend state, resume the devices + * from runtime suspend state first. Without that unable to dispatch the alert + * status to peripheral driver during system level resume as they are in runtime + * suspend state. + */ + ret = device_for_each_child(bus->dev, NULL, amd_resume_child_device); + if (ret < 0) + dev_err(dev, "amd_resume_child_device failed: %d\n", ret); + return 0; +} + +static int __maybe_unused amd_suspend(struct device *dev) +{ + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); + struct sdw_bus *bus = &amd_manager->bus; + int ret; + + if (bus->prop.hw_disabled) { + dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", + bus->link_id); + return 0; + } + + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { + return amd_sdw_clock_stop(amd_manager); + } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + /* + * As per hardware programming sequence on AMD platforms, + * clock stop should be invoked first before powering-off + */ + ret = amd_sdw_clock_stop(amd_manager); + if (ret) + return ret; + return amd_deinit_sdw_manager(amd_manager); + } + return 0; +} + static int __maybe_unused amd_suspend_runtime(struct device *dev) { struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); @@ -1141,6 +1224,8 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) }
static const struct dev_pm_ops amd_pm = { + .prepare = amd_pm_prepare, + SET_SYSTEM_SLEEP_PM_OPS(amd_suspend, amd_resume_runtime) SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL) };
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
- if (ret < 0)
dev_err(dev, "pm_request_resume failed: %d\n", ret);
- return ret;
+}
On 07/03/23 20:58, Pierre-Louis Bossart wrote:
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
As explained below thread,
https://lore.kernel.org/lkml/acd3a560-1218-9f1d-06ec-19e4d3d4e2c9@amd.com
Our scenario is multiple peripheral devices are connected over the same link.
In our implementation, device_for_each_child() function invokes amd_resume_child_device callback for each child. When any one of the child device is active, It will break the iteration, which results in failure resuming all child devices.
If we skip , pm_suspended check , it will not resume all peripheral devices when any one of the peripheral device is active.
- if (ret < 0)
dev_err(dev, "pm_request_resume failed: %d\n", ret);
- return ret;
+}
On 3/7/23 14:25, Mukunda,Vijendar wrote:
On 07/03/23 20:58, Pierre-Louis Bossart wrote:
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
As explained below thread,
https://lore.kernel.org/lkml/acd3a560-1218-9f1d-06ec-19e4d3d4e2c9@amd.com
Our scenario is multiple peripheral devices are connected over the same link.
In our implementation, device_for_each_child() function invokes amd_resume_child_device callback for each child. When any one of the child device is active, It will break the iteration, which results in failure resuming all child devices.
Can you clarify the 'it will break the iteration' statement?
Are you saying pm_request_resume() will return a negative error code if the device is already active?
We've used an unconditional pm_request_resume() in the Intel code for quite some time, including with multiple amplifiers per link, and have never observed the issue you report, so I'd like to get to the root cause pretty please. You took the Intel code and added a test for AMD platforms, and I'd really like to understand if the Intel code was wrong in the first place, or if the test is not needed. Something does not add up here.
If we skip , pm_suspended check , it will not resume all peripheral devices when any one of the peripheral device is active.
- if (ret < 0)
dev_err(dev, "pm_request_resume failed: %d\n", ret);
- return ret;
+}
On 08/03/23 02:38, Pierre-Louis Bossart wrote:
On 3/7/23 14:25, Mukunda,Vijendar wrote:
On 07/03/23 20:58, Pierre-Louis Bossart wrote:
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
As explained below thread,
https://lore.kernel.org/lkml/acd3a560-1218-9f1d-06ec-19e4d3d4e2c9@amd.com
Our scenario is multiple peripheral devices are connected over the same link.
In our implementation, device_for_each_child() function invokes amd_resume_child_device callback for each child. When any one of the child device is active, It will break the iteration, which results in failure resuming all child devices.
Can you clarify the 'it will break the iteration' statement?
Are you saying pm_request_resume() will return a negative error code if the device is already active?
We've used an unconditional pm_request_resume() in the Intel code for quite some time, including with multiple amplifiers per link, and have never observed the issue you report, so I'd like to get to the root cause pretty please. You took the Intel code and added a test for AMD platforms, and I'd really like to understand if the Intel code was wrong in the first place, or if the test is not needed. Something does not add up here.
AMP Codec (In aggregate mode) + Jack Codec connected over the same link on our platform. Consider below, scenario. Active stream is running on AMP codec and Jack codec is already in runtime suspend state. If system level suspend is invoked, in prepare callback, we need to resume both the codec devices.
device_for_each_child() will invoke amd_resume_child_device() function callback for each device which will try to resume the child device in this case. By definition, device_for_each_child() Iterate over @parent's child devices, and invokes the callback for each. We check the return of amd_resume_child_device() each time. If it returns anything other than 0, we break out and return that value.
In current scenario, As AMP codec is not in runtime suspend state, pm_request_resume() will return a value as 1. This will break the sequence for resuming rest of the child devices(JACK codec in our case).
As mentioned in an earlier thread, there are two possible solutions. 1. check pm runtime suspend state and return 0 if it is not suspended 2. simply always return 0 for amd_resume_child_device() function callback.
We opted first one as solution.
If we skip , pm_suspended check , it will not resume all peripheral devices when any one of the peripheral device is active.
- if (ret < 0)
dev_err(dev, "pm_request_resume failed: %d\n", ret);
- return ret;
+}
On 3/7/23 22:32, Mukunda,Vijendar wrote:
On 08/03/23 02:38, Pierre-Louis Bossart wrote:
On 3/7/23 14:25, Mukunda,Vijendar wrote:
On 07/03/23 20:58, Pierre-Louis Bossart wrote:
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
As explained below thread,
https://lore.kernel.org/lkml/acd3a560-1218-9f1d-06ec-19e4d3d4e2c9@amd.com
Our scenario is multiple peripheral devices are connected over the same link.
In our implementation, device_for_each_child() function invokes amd_resume_child_device callback for each child. When any one of the child device is active, It will break the iteration, which results in failure resuming all child devices.
Can you clarify the 'it will break the iteration' statement?
Are you saying pm_request_resume() will return a negative error code if the device is already active?
We've used an unconditional pm_request_resume() in the Intel code for quite some time, including with multiple amplifiers per link, and have never observed the issue you report, so I'd like to get to the root cause pretty please. You took the Intel code and added a test for AMD platforms, and I'd really like to understand if the Intel code was wrong in the first place, or if the test is not needed. Something does not add up here.
AMP Codec (In aggregate mode) + Jack Codec connected over the same link on our platform. Consider below, scenario. Active stream is running on AMP codec and Jack codec is already in runtime suspend state. If system level suspend is invoked, in prepare callback, we need to resume both the codec devices.
device_for_each_child() will invoke amd_resume_child_device() function callback for each device which will try to resume the child device in this case. By definition, device_for_each_child() Iterate over @parent's child devices, and invokes the callback for each. We check the return of amd_resume_child_device() each time. If it returns anything other than 0, we break out and return that value.
In current scenario, As AMP codec is not in runtime suspend state, pm_request_resume() will return a value as 1. This will break the sequence for resuming rest of the child devices(JACK codec in our case).
Well, yes, now that makes sense, thanks for the details.
I think the reason why we didn't see the problem with the Intel code is that both amplifiers are on the same dailink, so they are by construction either both suspended or both active. We never had different types of devices on the same link.
I would however suggest this simpler alternative, where only negative return values are returned:
ret = pm_request_resume(dev); if (ret < 0) { dev_err(dev, "pm_request_resume failed: %d\n", ret); return ret; } return 0;
this would work just fine, no?
As mentioned in an earlier thread, there are two possible solutions.
- check pm runtime suspend state and return 0 if it is not suspended
- simply always return 0 for amd_resume_child_device() function callback.
We opted first one as solution.
My suggestion looks like your option 2. It's cleaner IMHO.
On 08/03/23 19:28, Pierre-Louis Bossart wrote:
On 3/7/23 22:32, Mukunda,Vijendar wrote:
On 08/03/23 02:38, Pierre-Louis Bossart wrote:
On 3/7/23 14:25, Mukunda,Vijendar wrote:
On 07/03/23 20:58, Pierre-Louis Bossart wrote:
+static int amd_resume_child_device(struct device *dev, void *data) +{
- struct sdw_slave *slave = dev_to_sdw_dev(dev);
- int ret;
- if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
- }
- if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
- }
- if (!pm_runtime_suspended(dev))
return 0;
- ret = pm_request_resume(dev);
I still don't get why the test above was needed. It's racy and brings limited benefits.
As explained below thread,
https://lore.kernel.org/lkml/acd3a560-1218-9f1d-06ec-19e4d3d4e2c9@amd.com
Our scenario is multiple peripheral devices are connected over the same link.
In our implementation, device_for_each_child() function invokes amd_resume_child_device callback for each child. When any one of the child device is active, It will break the iteration, which results in failure resuming all child devices.
Can you clarify the 'it will break the iteration' statement?
Are you saying pm_request_resume() will return a negative error code if the device is already active?
We've used an unconditional pm_request_resume() in the Intel code for quite some time, including with multiple amplifiers per link, and have never observed the issue you report, so I'd like to get to the root cause pretty please. You took the Intel code and added a test for AMD platforms, and I'd really like to understand if the Intel code was wrong in the first place, or if the test is not needed. Something does not add up here.
AMP Codec (In aggregate mode) + Jack Codec connected over the same link on our platform. Consider below, scenario. Active stream is running on AMP codec and Jack codec is already in runtime suspend state. If system level suspend is invoked, in prepare callback, we need to resume both the codec devices.
device_for_each_child() will invoke amd_resume_child_device() function callback for each device which will try to resume the child device in this case. By definition, device_for_each_child() Iterate over @parent's child devices, and invokes the callback for each. We check the return of amd_resume_child_device() each time. If it returns anything other than 0, we break out and return that value.
In current scenario, As AMP codec is not in runtime suspend state, pm_request_resume() will return a value as 1. This will break the sequence for resuming rest of the child devices(JACK codec in our case).
Well, yes, now that makes sense, thanks for the details.
I think the reason why we didn't see the problem with the Intel code is that both amplifiers are on the same dailink, so they are by construction either both suspended or both active. We never had different types of devices on the same link.
I would however suggest this simpler alternative, where only negative return values are returned:
ret = pm_request_resume(dev); if (ret < 0) { dev_err(dev, "pm_request_resume failed: %d\n", ret); return ret; } return 0;
this would work just fine, no? No, As explained, pm_request_resume() return value is 1 for active device.
As mentioned in an earlier thread, there are two possible solutions.
- check pm runtime suspend state and return 0 if it is not suspended
- simply always return 0 for amd_resume_child_device() function callback.
We opted first one as solution.
My suggestion looks like your option 2. It's cleaner IMHO.
To use option 2, we need to respin the patch series. Is it okay if we fix it as supplement patch?
device_for_each_child() will invoke amd_resume_child_device() function callback for each device which will try to resume the child device in this case. By definition, device_for_each_child() Iterate over @parent's child devices, and invokes the callback for each. We check the return of amd_resume_child_device() each time. If it returns anything other than 0, we break out and return that value.
In current scenario, As AMP codec is not in runtime suspend state, pm_request_resume() will return a value as 1. This will break the sequence for resuming rest of the child devices(JACK codec in our case).
Well, yes, now that makes sense, thanks for the details.
I think the reason why we didn't see the problem with the Intel code is that both amplifiers are on the same dailink, so they are by construction either both suspended or both active. We never had different types of devices on the same link.
I would however suggest this simpler alternative, where only negative return values are returned:
ret = pm_request_resume(dev); if (ret < 0) { dev_err(dev, "pm_request_resume failed: %d\n", ret); return ret; } return 0;
this would work just fine, no? No, As explained, pm_request_resume() return value is 1 for active device.
As mentioned in an earlier thread, there are two possible solutions.
- check pm runtime suspend state and return 0 if it is not suspended
- simply always return 0 for amd_resume_child_device() function callback.
We opted first one as solution.
My suggestion looks like your option 2. It's cleaner IMHO.
To use option 2, we need to respin the patch series. Is it okay if we fix it as supplement patch?
I would vote for re-spinning a new version and ask others to review.
On 08/03/23 19:53, Pierre-Louis Bossart wrote:
device_for_each_child() will invoke amd_resume_child_device() function callback for each device which will try to resume the child device in this case. By definition, device_for_each_child() Iterate over @parent's child devices, and invokes the callback for each. We check the return of amd_resume_child_device() each time. If it returns anything other than 0, we break out and return that value.
In current scenario, As AMP codec is not in runtime suspend state, pm_request_resume() will return a value as 1. This will break the sequence for resuming rest of the child devices(JACK codec in our case).
Well, yes, now that makes sense, thanks for the details.
I think the reason why we didn't see the problem with the Intel code is that both amplifiers are on the same dailink, so they are by construction either both suspended or both active. We never had different types of devices on the same link.
I would however suggest this simpler alternative, where only negative return values are returned:
ret = pm_request_resume(dev); if (ret < 0) { dev_err(dev, "pm_request_resume failed: %d\n", ret); return ret; } return 0;
this would work just fine, no?
Sorry its my bad. This would work fine. We will fix it and respin the patch series.
No, As explained, pm_request_resume() return value is 1 for active device.
As mentioned in an earlier thread, there are two possible solutions.
- check pm runtime suspend state and return 0 if it is not suspended
- simply always return 0 for amd_resume_child_device() function callback.
We opted first one as solution.
My suggestion looks like your option 2. It's cleaner IMHO.
To use option 2, we need to respin the patch series. Is it okay if we fix it as supplement patch?
I would vote for re-spinning a new version and ask others to review.
participants (4)
-
Mukunda,Vijendar
-
Pierre-Louis Bossart
-
Vijendar Mukunda
-
Vinod Koul