diff options
Diffstat (limited to 'drivers/soundwire')
29 files changed, 2956 insertions, 533 deletions
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index 4d8f3b7024ae..ad56393e4c93 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -6,6 +6,7 @@ menuconfig SOUNDWIRE tristate "SoundWire support" depends on ACPI || OF + depends on SND_SOC_SDCA_OPTIONAL help SoundWire is a 2-Pin interface with data and clock line ratified by the MIPI Alliance. SoundWire is used for transporting data @@ -30,6 +31,7 @@ config SOUNDWIRE_AMD config SOUNDWIRE_CADENCE tristate + select CRC8 config SOUNDWIRE_INTEL tristate "Intel SoundWire Master driver" diff --git a/drivers/soundwire/amd_init.c b/drivers/soundwire/amd_init.c index e45dc8261ab1..643e94524fe6 100644 --- a/drivers/soundwire/amd_init.c +++ b/drivers/soundwire/amd_init.c @@ -8,6 +8,7 @@ */ #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/export.h> #include <linux/io.h> #include <linux/module.h> @@ -17,42 +18,38 @@ #define ACP_PAD_PULLDOWN_CTRL 0x0001448 #define ACP_SW_PAD_KEEPER_EN 0x0001454 -#define AMD_SDW_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9a -#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9f -#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7ffa -#define AMD_SDW0_PAD_EN_MASK 1 -#define AMD_SDW1_PAD_EN_MASK 0x10 -#define AMD_SDW_PAD_EN_MASK (AMD_SDW0_PAD_EN_MASK | AMD_SDW1_PAD_EN_MASK) +#define AMD_SDW0_PAD_CTRL_MASK 0x60 +#define AMD_SDW1_PAD_CTRL_MASK 5 +#define AMD_SDW_PAD_CTRL_MASK (AMD_SDW0_PAD_CTRL_MASK | AMD_SDW1_PAD_CTRL_MASK) +#define AMD_SDW0_PAD_EN 1 +#define AMD_SDW1_PAD_EN 0x10 +#define AMD_SDW_PAD_EN (AMD_SDW0_PAD_EN | AMD_SDW1_PAD_EN) static int amd_enable_sdw_pads(void __iomem *mmio, u32 link_mask, struct device *dev) { - u32 val; - u32 pad_keeper_en_mask, pad_pulldown_ctrl_mask; + u32 pad_keeper_en, pad_pulldown_ctrl_mask; switch (link_mask) { case 1: - pad_keeper_en_mask = AMD_SDW0_PAD_EN_MASK; - pad_pulldown_ctrl_mask = AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK; + pad_keeper_en = AMD_SDW0_PAD_EN; + pad_pulldown_ctrl_mask = AMD_SDW0_PAD_CTRL_MASK; break; case 2: - pad_keeper_en_mask = AMD_SDW1_PAD_EN_MASK; - pad_pulldown_ctrl_mask = AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK; + pad_keeper_en = AMD_SDW1_PAD_EN; + pad_pulldown_ctrl_mask = AMD_SDW1_PAD_CTRL_MASK; break; case 3: - pad_keeper_en_mask = AMD_SDW_PAD_EN_MASK; - pad_pulldown_ctrl_mask = AMD_SDW_PAD_PULLDOWN_CTRL_ENABLE_MASK; + pad_keeper_en = AMD_SDW_PAD_EN; + pad_pulldown_ctrl_mask = AMD_SDW_PAD_CTRL_MASK; break; default: dev_err(dev, "No SDW Links are enabled\n"); return -ENODEV; } - val = readl(mmio + ACP_SW_PAD_KEEPER_EN); - val |= pad_keeper_en_mask; - writel(val, mmio + ACP_SW_PAD_KEEPER_EN); - val = readl(mmio + ACP_PAD_PULLDOWN_CTRL); - val &= pad_pulldown_ctrl_mask; - writel(val, mmio + ACP_PAD_PULLDOWN_CTRL); + amd_updatel(mmio, ACP_SW_PAD_KEEPER_EN, pad_keeper_en, pad_keeper_en); + amd_updatel(mmio, ACP_PAD_PULLDOWN_CTRL, pad_pulldown_ctrl_mask, 0); + return 0; } @@ -73,7 +70,6 @@ static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res) { struct sdw_amd_ctx *ctx; struct acpi_device *adev; - struct resource *sdw_res; struct acp_sdw_pdata sdw_pdata[2]; struct platform_device_info pdevinfo[2]; u32 link_mask; @@ -108,7 +104,8 @@ static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res) ctx->count = count; ctx->link_mask = res->link_mask; - sdw_res = kzalloc(sizeof(*sdw_res), GFP_KERNEL); + struct resource *sdw_res __free(kfree) = kzalloc(sizeof(*sdw_res), + GFP_KERNEL); if (!sdw_res) { kfree(ctx); return NULL; @@ -124,6 +121,7 @@ static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res) sdw_pdata[index].instance = index; sdw_pdata[index].acp_sdw_lock = res->acp_lock; + sdw_pdata[index].acp_rev = res->acp_rev; pdevinfo[index].name = "amd_sdw_manager"; pdevinfo[index].id = index; pdevinfo[index].parent = res->parent; @@ -136,7 +134,6 @@ static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res) if (IS_ERR(ctx->pdev[index])) goto err; } - kfree(sdw_res); return ctx; err: while (index--) { @@ -146,7 +143,6 @@ err: platform_device_unregister(ctx->pdev[index]); } - kfree(sdw_res); kfree(ctx); return NULL; } @@ -177,15 +173,15 @@ int sdw_amd_probe(struct sdw_amd_res *res, struct sdw_amd_ctx **sdw_ctx) return sdw_amd_startup(*sdw_ctx); } -EXPORT_SYMBOL_NS(sdw_amd_probe, SOUNDWIRE_AMD_INIT); +EXPORT_SYMBOL_NS(sdw_amd_probe, "SOUNDWIRE_AMD_INIT"); void sdw_amd_exit(struct sdw_amd_ctx *ctx) { sdw_amd_cleanup(ctx); - kfree(ctx->ids); + kfree(ctx->peripherals); kfree(ctx); } -EXPORT_SYMBOL_NS(sdw_amd_exit, SOUNDWIRE_AMD_INIT); +EXPORT_SYMBOL_NS(sdw_amd_exit, "SOUNDWIRE_AMD_INIT"); int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx) { @@ -209,10 +205,11 @@ int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx) num_slaves++; } - ctx->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL); - if (!ctx->ids) + ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves), + GFP_KERNEL); + if (!ctx->peripherals) return -ENOMEM; - ctx->num_slaves = num_slaves; + ctx->peripherals->num_peripherals = num_slaves; for (index = 0; index < ctx->count; index++) { if (!(ctx->link_mask & BIT(index))) continue; @@ -220,15 +217,14 @@ int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx) if (amd_manager) { bus = &amd_manager->bus; list_for_each_entry(slave, &bus->slaves, node) { - ctx->ids[i].id = slave->id; - ctx->ids[i].link_id = bus->link_id; + ctx->peripherals->array[i] = slave; i++; } } } return 0; } -EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, SOUNDWIRE_AMD_INIT); +EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, "SOUNDWIRE_AMD_INIT"); MODULE_AUTHOR("Vijendar.Mukunda@amd.com"); MODULE_DESCRIPTION("AMD SoundWire Init Library"); diff --git a/drivers/soundwire/amd_init.h b/drivers/soundwire/amd_init.h index 928b0c707162..5e7b43836a37 100644 --- a/drivers/soundwire/amd_init.h +++ b/drivers/soundwire/amd_init.h @@ -10,4 +10,12 @@ int amd_sdw_manager_start(struct amd_sdw_manager *amd_manager); +static inline void amd_updatel(void __iomem *mmio, int offset, u32 mask, u32 val) +{ + u32 tmp; + + tmp = readl(mmio + offset); + tmp = (tmp & ~mask) | val; + writel(tmp, mmio + offset); +} #endif diff --git a/drivers/soundwire/amd_manager.c b/drivers/soundwire/amd_manager.c index 6bcf8e75273c..a12c68b93b1c 100644 --- a/drivers/soundwire/amd_manager.c +++ b/drivers/soundwire/amd_manager.c @@ -6,6 +6,7 @@ */ #include <linux/completion.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/io.h> #include <linux/jiffies.h> @@ -89,9 +90,8 @@ static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) u32 val; mutex_lock(amd_manager->acp_sdw_lock); - val = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); - val |= sdw_manager_reg_mask_array[amd_manager->instance]; - writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + val = sdw_manager_reg_mask_array[amd_manager->instance]; + amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance), val, val); mutex_unlock(amd_manager->acp_sdw_lock); writel(AMD_SDW_IRQ_MASK_0TO7, amd_manager->mmio + @@ -103,12 +103,12 @@ static void amd_enable_sdw_interrupts(struct amd_sdw_manager *amd_manager) static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) { - u32 val; + u32 irq_mask; mutex_lock(amd_manager->acp_sdw_lock); - val = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); - val &= ~sdw_manager_reg_mask_array[amd_manager->instance]; - writel(val, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(amd_manager->instance)); + irq_mask = sdw_manager_reg_mask_array[amd_manager->instance]; + amd_updatel(amd_manager->acp_mmio, ACP_EXTERNAL_INTR_CNTL(amd_manager->instance), + irq_mask, 0); mutex_unlock(amd_manager->acp_sdw_lock); writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7); @@ -143,6 +143,57 @@ static void amd_sdw_wake_enable(struct amd_sdw_manager *amd_manager, bool enable writel(wake_ctrl, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11); } +static int amd_sdw_set_device_state(struct amd_sdw_manager *amd_manager, u32 target_device_state) +{ + u32 sdw_dev_state; + + sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE); + switch (amd_manager->instance) { + case ACP_SDW0: + u32p_replace_bits(&sdw_dev_state, target_device_state, + AMD_SDW0_DEVICE_STATE_MASK); + break; + case ACP_SDW1: + u32p_replace_bits(&sdw_dev_state, target_device_state, + AMD_SDW1_DEVICE_STATE_MASK); + break; + default: + return -EINVAL; + } + writel(sdw_dev_state, amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE); + sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE); + dev_dbg(amd_manager->dev, "AMD_SDW_DEVICE_STATE:0x%x\n", sdw_dev_state); + return 0; +} + +static int amd_sdw_host_wake_enable(struct amd_sdw_manager *amd_manager, bool enable) +{ + u32 intr_cntl1; + u32 sdw_host_wake_irq_mask; + + if (!amd_manager->wake_en_mask) + return 0; + + switch (amd_manager->instance) { + case ACP_SDW0: + sdw_host_wake_irq_mask = AMD_SDW0_HOST_WAKE_INTR_MASK; + break; + case ACP_SDW1: + sdw_host_wake_irq_mask = AMD_SDW1_HOST_WAKE_INTR_MASK; + break; + default: + return -EINVAL; + } + + intr_cntl1 = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1)); + if (enable) + intr_cntl1 |= sdw_host_wake_irq_mask; + else + intr_cntl1 &= ~sdw_host_wake_irq_mask; + writel(intr_cntl1, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1)); + return 0; +} + static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, struct sdw_msg *msg, int cmd_offset) { @@ -295,7 +346,7 @@ static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *a msg->dev_num); return SDW_CMD_FAIL; } - dev_err_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n", + dev_dbg_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n", msg->dev_num); return SDW_CMD_IGNORED; } @@ -384,7 +435,7 @@ static u32 amd_sdw_read_ping_status(struct sdw_bus *bus) return slave_stat; } -static int amd_sdw_compute_params(struct sdw_bus *bus) +static int amd_sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) { struct sdw_transport_data t_data = {0}; struct sdw_master_runtime *m_rt; @@ -410,7 +461,7 @@ static int amd_sdw_compute_params(struct sdw_bus *bus) 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_BLK_PKG_PER_PORT, p_rt->lane); sdw_fill_port_params(&p_rt->port_params, p_rt->num, bps, @@ -433,12 +484,22 @@ static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_pa 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; + switch (amd_manager->acp_rev) { + case ACP63_PCI_REV_ID: + switch (amd_manager->instance) { + case ACP_SDW0: + frame_fmt_reg = acp63_sdw0_dp_reg[p_params->num].frame_fmt_reg; + break; + case ACP_SDW1: + frame_fmt_reg = acp63_sdw1_dp_reg[p_params->num].frame_fmt_reg; + break; + default: + return -EINVAL; + } break; - case ACP_SDW1: - frame_fmt_reg = sdw1_manager_dp_reg[p_params->num].frame_fmt_reg; + case ACP70_PCI_REV_ID: + case ACP71_PCI_REV_ID: + frame_fmt_reg = acp70_sdw_dp_reg[p_params->num].frame_fmt_reg; break; default: return -EINVAL; @@ -465,20 +526,36 @@ static int amd_sdw_transport_params(struct sdw_bus *bus, 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; + switch (amd_manager->acp_rev) { + case ACP63_PCI_REV_ID: + switch (amd_manager->instance) { + case ACP_SDW0: + frame_fmt_reg = acp63_sdw0_dp_reg[params->port_num].frame_fmt_reg; + sample_int_reg = acp63_sdw0_dp_reg[params->port_num].sample_int_reg; + hctrl_dp0_reg = acp63_sdw0_dp_reg[params->port_num].hctrl_dp0_reg; + offset_reg = acp63_sdw0_dp_reg[params->port_num].offset_reg; + lane_ctrl_ch_en_reg = + acp63_sdw0_dp_reg[params->port_num].lane_ctrl_ch_en_reg; + break; + case ACP_SDW1: + frame_fmt_reg = acp63_sdw1_dp_reg[params->port_num].frame_fmt_reg; + sample_int_reg = acp63_sdw1_dp_reg[params->port_num].sample_int_reg; + hctrl_dp0_reg = acp63_sdw1_dp_reg[params->port_num].hctrl_dp0_reg; + offset_reg = acp63_sdw1_dp_reg[params->port_num].offset_reg; + lane_ctrl_ch_en_reg = + acp63_sdw1_dp_reg[params->port_num].lane_ctrl_ch_en_reg; + break; + default: + return -EINVAL; + } 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; + case ACP70_PCI_REV_ID: + case ACP71_PCI_REV_ID: + frame_fmt_reg = acp70_sdw_dp_reg[params->port_num].frame_fmt_reg; + sample_int_reg = acp70_sdw_dp_reg[params->port_num].sample_int_reg; + hctrl_dp0_reg = acp70_sdw_dp_reg[params->port_num].hctrl_dp0_reg; + offset_reg = acp70_sdw_dp_reg[params->port_num].offset_reg; + lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[params->port_num].lane_ctrl_ch_en_reg; break; default: return -EINVAL; @@ -520,12 +597,24 @@ static int amd_sdw_port_enable(struct sdw_bus *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; + switch (amd_manager->acp_rev) { + case ACP63_PCI_REV_ID: + switch (amd_manager->instance) { + case ACP_SDW0: + lane_ctrl_ch_en_reg = + acp63_sdw0_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; + break; + case ACP_SDW1: + lane_ctrl_ch_en_reg = + acp63_sdw1_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; + break; + default: + return -EINVAL; + } break; - case ACP_SDW1: - lane_ctrl_ch_en_reg = sdw1_manager_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; + case ACP70_PCI_REV_ID: + case ACP71_PCI_REV_ID: + lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg; break; default: return -EINVAL; @@ -572,6 +661,9 @@ static int sdw_master_read_amd_prop(struct sdw_bus *bus) 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; + + fwnode_handle_put(link); + return 0; } @@ -601,7 +693,6 @@ static int amd_sdw_hw_params(struct snd_pcm_substream *substream, 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; @@ -624,11 +715,10 @@ static int amd_sdw_hw_params(struct snd_pcm_substream *substream, sconfig.bps = snd_pcm_format_width(params_format(params)); /* Port configuration */ - pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); - if (!pconfig) { - ret = -ENOMEM; - goto error; - } + struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig), + GFP_KERNEL); + if (!pconfig) + return -ENOMEM; pconfig->num = dai->id; pconfig->ch_mask = (1 << ch) - 1; @@ -637,8 +727,6 @@ static int amd_sdw_hw_params(struct snd_pcm_substream *substream, if (ret) dev_err(amd_manager->dev, "add manager to stream failed:%d\n", ret); - kfree(pconfig); -error: return ret; } @@ -828,6 +916,7 @@ 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) { + dev_dbg(amd_manager->dev, "SoundWire Wake event reported\n"); pm_request_resume(amd_manager->dev); writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance)); writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11); @@ -911,6 +1000,7 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) 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->acp_rev = pdata->acp_rev; 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; @@ -927,15 +1017,26 @@ static int amd_sdw_manager_probe(struct platform_device *pdev) * information. */ amd_manager->bus.controller_id = 0; - - 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; + dev_dbg(dev, "acp_rev:0x%x\n", amd_manager->acp_rev); + switch (amd_manager->acp_rev) { + case ACP63_PCI_REV_ID: + switch (amd_manager->instance) { + case ACP_SDW0: + amd_manager->num_dout_ports = AMD_ACP63_SDW0_MAX_TX_PORTS; + amd_manager->num_din_ports = AMD_ACP63_SDW0_MAX_RX_PORTS; + break; + case ACP_SDW1: + amd_manager->num_dout_ports = AMD_ACP63_SDW1_MAX_TX_PORTS; + amd_manager->num_din_ports = AMD_ACP63_SDW1_MAX_RX_PORTS; + break; + default: + return -EINVAL; + } break; - case ACP_SDW1: - amd_manager->num_dout_ports = AMD_SDW1_MAX_TX_PORTS; - amd_manager->num_din_ports = AMD_SDW1_MAX_RX_PORTS; + case ACP70_PCI_REV_ID: + case ACP71_PCI_REV_ID: + amd_manager->num_dout_ports = AMD_ACP70_SDW_MAX_TX_PORTS; + amd_manager->num_din_ports = AMD_ACP70_SDW_MAX_RX_PORTS; break; default: return -EINVAL; @@ -1109,8 +1210,21 @@ static int __maybe_unused amd_suspend(struct device *dev) if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { amd_sdw_wake_enable(amd_manager, false); - return amd_sdw_clock_stop(amd_manager); + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, false); + if (ret) + return ret; + } + ret = amd_sdw_clock_stop(amd_manager); + if (ret) + return ret; } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + amd_sdw_wake_enable(amd_manager, false); + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, false); + if (ret) + return ret; + } /* * As per hardware programming sequence on AMD platforms, * clock stop should be invoked first before powering-off @@ -1118,7 +1232,14 @@ static int __maybe_unused amd_suspend(struct device *dev) ret = amd_sdw_clock_stop(amd_manager); if (ret) return ret; - return amd_deinit_sdw_manager(amd_manager); + ret = amd_deinit_sdw_manager(amd_manager); + if (ret) + return ret; + } + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3); + if (ret) + return ret; } return 0; } @@ -1128,6 +1249,7 @@ 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; + u32 val; if (bus->prop.hw_disabled) { dev_dbg(bus->dev, "SoundWire manager %d is disabled,\n", @@ -1136,12 +1258,40 @@ static int __maybe_unused amd_suspend_runtime(struct device *dev) } if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { amd_sdw_wake_enable(amd_manager, true); - return amd_sdw_clock_stop(amd_manager); + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, true); + if (ret) + return ret; + } + ret = amd_sdw_clock_stop(amd_manager); + if (ret) + return ret; } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + amd_sdw_wake_enable(amd_manager, true); + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, true); + if (ret) + return ret; + } ret = amd_sdw_clock_stop(amd_manager); if (ret) return ret; - return amd_deinit_sdw_manager(amd_manager); + ret = amd_deinit_sdw_manager(amd_manager); + if (ret) + return ret; + } + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3); + if (ret) + return ret; + if (amd_manager->wake_en_mask) { + val = readl(amd_manager->acp_mmio + ACP_PME_EN); + if (!val) { + writel(1, amd_manager->acp_mmio + ACP_PME_EN); + val = readl(amd_manager->acp_mmio + ACP_PME_EN); + dev_dbg(amd_manager->dev, "ACP_PME_EN:0x%x\n", val); + } + } } return 0; } @@ -1160,8 +1310,21 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) } if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { - return amd_sdw_clock_stop_exit(amd_manager); + ret = amd_sdw_clock_stop_exit(amd_manager); + if (ret) + return ret; + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, false); + if (ret) + return ret; + } } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { + writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance)); + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_host_wake_enable(amd_manager, false); + if (ret) + return ret; + } val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); if (val) { val |= AMD_SDW_CLK_RESUME_REQ; @@ -1182,6 +1345,11 @@ static int __maybe_unused amd_resume_runtime(struct device *dev) return ret; amd_sdw_set_frameshape(amd_manager); } + if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) { + ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D0); + if (ret) + return ret; + } return 0; } @@ -1193,7 +1361,7 @@ static const struct dev_pm_ops amd_pm = { static struct platform_driver amd_sdw_driver = { .probe = &amd_sdw_manager_probe, - .remove_new = &amd_sdw_manager_remove, + .remove = &amd_sdw_manager_remove, .driver = { .name = "amd_sdw_manager", .pm = &amd_pm, diff --git a/drivers/soundwire/amd_manager.h b/drivers/soundwire/amd_manager.h index 707065468e05..6cc916b0c820 100644 --- a/drivers/soundwire/amd_manager.h +++ b/drivers/soundwire/amd_manager.h @@ -155,12 +155,15 @@ #define AMD_SDW_IRQ_MASK_8TO11 0x000c7777 #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_ACP63_SDW0_MAX_TX_PORTS 3 +#define AMD_ACP63_SDW0_MAX_RX_PORTS 3 +#define AMD_ACP63_SDW1_MAX_TX_PORTS 1 +#define AMD_ACP63_SDW1_MAX_RX_PORTS 1 +#define AMD_ACP70_SDW_MAX_TX_PORTS 3 +#define AMD_ACP70_SDW_MAX_RX_PORTS 3 +#define AMD_ACP63_SDW0_MAX_DAI 6 +#define AMD_ACP63_SDW1_MAX_DAI 2 +#define AMD_ACP70_SDW_MAX_DAI 6 #define AMD_SDW_SLAVE_0_ATTACHED 5 #define AMD_SDW_SSP_COUNTER_VAL 3 @@ -191,6 +194,14 @@ #define AMD_SDW_CLK_RESUME_DONE 3 #define AMD_SDW_WAKE_STAT_MASK BIT(16) #define AMD_SDW_WAKE_INTR_MASK BIT(16) +#define AMD_SDW0_HOST_WAKE_INTR_MASK BIT(22) +#define AMD_SDW1_HOST_WAKE_INTR_MASK BIT(23) +#define AMD_SDW_DEVICE_STATE 0x1430 +#define AMD_SDW0_DEVICE_STATE_MASK GENMASK(1, 0) +#define AMD_SDW1_DEVICE_STATE_MASK GENMASK(3, 2) +#define AMD_SDW_DEVICE_STATE_D0 0 +#define AMD_SDW_DEVICE_STATE_D3 3 +#define ACP_PME_EN 0x0001400 static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = { AMD_SDW_DEFAULT_CLK_FREQ, @@ -222,7 +233,7 @@ struct sdw_manager_dp_reg { * in SoundWire DMA driver. */ -static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { +static struct sdw_manager_dp_reg acp63_sdw0_dp_reg[AMD_ACP63_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, @@ -237,13 +248,28 @@ static struct sdw_manager_dp_reg sdw0_manager_dp_reg[AMD_SDW0_MAX_DAI] = { 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] = { +static struct sdw_manager_dp_reg acp63_sdw1_dp_reg[AMD_ACP63_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_dp_reg acp70_sdw_dp_reg[AMD_ACP70_SDW_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 u32 sdw_manager_reg_mask_array[AMD_SDW_MAX_MANAGER_COUNT] = { AMD_SDW0_EXT_INTR_MASK, AMD_SDW1_EXT_INTR_MASK diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index f3fec15c3112..39aecd34c641 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -8,6 +8,7 @@ #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_type.h> +#include <linux/string_choices.h> #include "bus.h" #include "irq.h" #include "sysfs_local.h" @@ -112,7 +113,7 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, /* Set higher order bits */ *bus->assigned = ~GENMASK(SDW_BROADCAST_DEV_NUM, SDW_ENUM_DEV_NUM); - /* Set enumuration device number and broadcast device number */ + /* Set enumeration device number and broadcast device number */ set_bit(SDW_ENUM_DEV_NUM, bus->assigned); set_bit(SDW_BROADCAST_DEV_NUM, bus->assigned); @@ -121,6 +122,10 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, set_bit(SDW_GROUP13_DEV_NUM, bus->assigned); set_bit(SDW_MASTER_DEV_NUM, bus->assigned); + ret = sdw_irq_create(bus, fwnode); + if (ret) + return ret; + /* * SDW is an enumerable bus, but devices can be powered off. So, * they won't be able to report as present. @@ -137,6 +142,7 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, if (ret < 0) { dev_err(bus->dev, "Finding slaves failed:%d\n", ret); + sdw_irq_delete(bus); return ret; } @@ -155,10 +161,6 @@ int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent, bus->params.curr_bank = SDW_BANK0; bus->params.next_bank = SDW_BANK1; - ret = sdw_irq_create(bus, fwnode); - if (ret) - return ret; - return 0; } EXPORT_SYMBOL(sdw_bus_master_add); @@ -277,7 +279,7 @@ static int sdw_transfer_unlocked(struct sdw_bus *bus, struct sdw_msg *msg) if (ret != 0 && ret != -ENODATA) dev_err(bus->dev, "trf on Slave %d failed:%d %s addr %x count %d\n", msg->dev_num, ret, - (msg->flags & SDW_MSG_FLAG_WRITE) ? "write" : "read", + str_write_read(msg->flags & SDW_MSG_FLAG_WRITE), msg->addr, msg->len); return ret; @@ -813,6 +815,16 @@ void sdw_extract_slave_id(struct sdw_bus *bus, } EXPORT_SYMBOL(sdw_extract_slave_id); +bool is_clock_scaling_supported_by_slave(struct sdw_slave *slave) +{ + /* + * Dynamic scaling is a defined by SDCA. However, some devices expose the class ID but + * can't support dynamic scaling. We might need a quirk to handle such devices. + */ + return slave->id.class_id; +} +EXPORT_SYMBOL(is_clock_scaling_supported_by_slave); + static int sdw_program_device_num(struct sdw_bus *bus, bool *programmed) { u8 buf[SDW_NUM_DEV_ID_REGISTERS] = {0}; @@ -1253,7 +1265,7 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave, if (slave->bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) { dev_dbg(&slave->dev, "TEST FAIL interrupt %s\n", - enable ? "on" : "off"); + str_on_off(enable)); mask |= SDW_DPN_INT_TEST_FAIL; } @@ -1276,23 +1288,12 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave, return ret; } -static int sdw_slave_set_frequency(struct sdw_slave *slave) +int sdw_slave_get_scale_index(struct sdw_slave *slave, u8 *base) { u32 mclk_freq = slave->bus->prop.mclk_freq; u32 curr_freq = slave->bus->params.curr_dr_freq >> 1; unsigned int scale; u8 scale_index; - u8 base; - int ret; - - /* - * frequency base and scale registers are required for SDCA - * devices. They may also be used for 1.2+/non-SDCA devices. - * Driver can set the property, we will need a DisCo property - * to discover this case from platform firmware. - */ - if (!slave->id.class_id && !slave->prop.clock_reg_supported) - return 0; if (!mclk_freq) { dev_err(&slave->dev, @@ -1311,19 +1312,19 @@ static int sdw_slave_set_frequency(struct sdw_slave *slave) */ if (!(19200000 % mclk_freq)) { mclk_freq = 19200000; - base = SDW_SCP_BASE_CLOCK_19200000_HZ; - } else if (!(24000000 % mclk_freq)) { - mclk_freq = 24000000; - base = SDW_SCP_BASE_CLOCK_24000000_HZ; - } else if (!(24576000 % mclk_freq)) { - mclk_freq = 24576000; - base = SDW_SCP_BASE_CLOCK_24576000_HZ; + *base = SDW_SCP_BASE_CLOCK_19200000_HZ; } else if (!(22579200 % mclk_freq)) { mclk_freq = 22579200; - base = SDW_SCP_BASE_CLOCK_22579200_HZ; + *base = SDW_SCP_BASE_CLOCK_22579200_HZ; + } else if (!(24576000 % mclk_freq)) { + mclk_freq = 24576000; + *base = SDW_SCP_BASE_CLOCK_24576000_HZ; } else if (!(32000000 % mclk_freq)) { mclk_freq = 32000000; - base = SDW_SCP_BASE_CLOCK_32000000_HZ; + *base = SDW_SCP_BASE_CLOCK_32000000_HZ; + } else if (!(96000000 % mclk_freq)) { + mclk_freq = 24000000; + *base = SDW_SCP_BASE_CLOCK_24000000_HZ; } else { dev_err(&slave->dev, "Unsupported clock base, mclk %d\n", @@ -1354,6 +1355,34 @@ static int sdw_slave_set_frequency(struct sdw_slave *slave) } scale_index++; + dev_dbg(&slave->dev, + "Configured bus base %d, scale %d, mclk %d, curr_freq %d\n", + *base, scale_index, mclk_freq, curr_freq); + + return scale_index; +} +EXPORT_SYMBOL(sdw_slave_get_scale_index); + +static int sdw_slave_set_frequency(struct sdw_slave *slave) +{ + int scale_index; + u8 base; + int ret; + + /* + * frequency base and scale registers are required for SDCA + * devices. They may also be used for 1.2+/non-SDCA devices. + * Driver can set the property directly, for now there's no + * DisCo property to discover support for the scaling registers + * from platform firmware. + */ + if (!slave->id.class_id && !slave->prop.clock_reg_supported) + return 0; + + scale_index = sdw_slave_get_scale_index(slave, &base); + if (scale_index < 0) + return scale_index; + ret = sdw_write_no_pm(slave, SDW_SCP_BUS_CLOCK_BASE, base); if (ret < 0) { dev_err(&slave->dev, @@ -1373,10 +1402,6 @@ static int sdw_slave_set_frequency(struct sdw_slave *slave) dev_err(&slave->dev, "SDW_SCP_BUSCLOCK_SCALE_B1 write failed:%d\n", ret); - dev_dbg(&slave->dev, - "Configured bus base %d, scale %d, mclk %d, curr_freq %d\n", - base, scale_index, mclk_freq, curr_freq); - return ret; } @@ -1410,7 +1435,7 @@ static int sdw_initialize_slave(struct sdw_slave *slave) } } if ((slave->bus->prop.quirks & SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY) && - !(slave->prop.quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY)) { + !(prop->quirks & SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY)) { /* Clear parity interrupt before enabling interrupt mask */ status = sdw_read_no_pm(slave, SDW_SCP_INT1); if (status < 0) { @@ -1436,7 +1461,7 @@ static int sdw_initialize_slave(struct sdw_slave *slave) * device-dependent, it might e.g. only be enabled in * steady-state after a couple of frames. */ - val = slave->prop.scp_int1_mask; + val = prop->scp_int1_mask; /* Enable SCP interrupts */ ret = sdw_update_no_pm(slave, SDW_SCP_INTMASK1, val, val); @@ -1447,7 +1472,7 @@ static int sdw_initialize_slave(struct sdw_slave *slave) } /* No need to continue if DP0 is not present */ - if (!slave->prop.dp0_prop) + if (!prop->dp0_prop) return 0; /* Enable DP0 interrupts */ @@ -1474,7 +1499,7 @@ static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status) } do { - clear = status & ~SDW_DP0_INTERRUPTS; + clear = status & ~(SDW_DP0_INTERRUPTS | SDW_DP0_SDCA_CASCADE); if (status & SDW_DP0_INT_TEST_FAIL) { dev_err(&slave->dev, "Test fail for port 0\n"); @@ -2015,3 +2040,46 @@ void sdw_clear_slave_status(struct sdw_bus *bus, u32 request) } } EXPORT_SYMBOL(sdw_clear_slave_status); + +int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) +{ + if (msg->len > SDW_BPT_MSG_MAX_BYTES) { + dev_err(bus->dev, "Invalid BPT message length %d\n", msg->len); + return -EINVAL; + } + + /* check device is enumerated */ + if (slave->dev_num == SDW_ENUM_DEV_NUM || + slave->dev_num > SDW_MAX_DEVICES) { + dev_err(&slave->dev, "Invalid device number %d\n", slave->dev_num); + return -ENODEV; + } + + /* make sure all callbacks are defined */ + if (!bus->ops->bpt_send_async || + !bus->ops->bpt_wait) { + dev_err(bus->dev, "BPT callbacks not defined\n"); + return -EOPNOTSUPP; + } + + return bus->ops->bpt_send_async(bus, slave, msg); +} +EXPORT_SYMBOL(sdw_bpt_send_async); + +int sdw_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) +{ + return bus->ops->bpt_wait(bus, slave, msg); +} +EXPORT_SYMBOL(sdw_bpt_wait); + +int sdw_bpt_send_sync(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) +{ + int ret; + + ret = sdw_bpt_send_async(bus, slave, msg); + if (ret < 0) + return ret; + + return sdw_bpt_wait(bus, slave, msg); +} +EXPORT_SYMBOL(sdw_bpt_send_sync); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index fda6b24ac2da..02651fbb683a 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -72,6 +72,24 @@ struct sdw_msg { bool page; }; +/** + * struct sdw_btp_msg - Message structure + * @addr: Start Register address accessed in the Slave + * @len: number of bytes to transfer. More than 64Kb can be transferred + * but a practical limit of SDW_BPT_MSG_MAX_BYTES is enforced. + * @dev_num: Slave device number + * @flags: transfer flags, indicate if xfer is read or write + * @buf: message data buffer (filled by host for write, filled + * by Peripheral hardware for reads) + */ +struct sdw_bpt_msg { + u32 addr; + u32 len; + u8 dev_num; + u8 flags; + u8 *buf; +}; + #define SDW_DOUBLE_RATE_FACTOR 2 #define SDW_STRM_RATE_GROUPING 1 @@ -90,6 +108,7 @@ int sdw_find_col_index(int col); * @transport_params: Transport parameters * @port_params: Port parameters * @port_node: List node for Master or Slave port_list + * @lane: Which lane is used * * SoundWire spec has no mention of ports for Master interface but the * concept is logically extended. @@ -100,6 +119,7 @@ struct sdw_port_runtime { struct sdw_transport_params transport_params; struct sdw_port_params port_params; struct list_head port_node; + unsigned int lane; }; /** @@ -149,6 +169,7 @@ struct sdw_transport_data { int hstop; int block_offset; int sub_block_offset; + unsigned int lane; }; struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, diff --git a/drivers/soundwire/bus_type.c b/drivers/soundwire/bus_type.c index fd65b2360fc1..e98d5db81b1c 100644 --- a/drivers/soundwire/bus_type.c +++ b/drivers/soundwire/bus_type.c @@ -19,7 +19,7 @@ * struct sdw_device_id. */ static const struct sdw_device_id * -sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) +sdw_get_device_id(struct sdw_slave *slave, const struct sdw_driver *drv) { const struct sdw_device_id *id; @@ -35,10 +35,10 @@ sdw_get_device_id(struct sdw_slave *slave, struct sdw_driver *drv) return NULL; } -static int sdw_bus_match(struct device *dev, struct device_driver *ddrv) +static int sdw_bus_match(struct device *dev, const struct device_driver *ddrv) { struct sdw_slave *slave; - struct sdw_driver *drv; + const struct sdw_driver *drv; int ret = 0; if (is_sdw_slave(dev)) { @@ -83,7 +83,6 @@ static int sdw_drv_probe(struct device *dev) struct sdw_slave *slave = dev_to_sdw_dev(dev); struct sdw_driver *drv = drv_to_sdw_driver(dev->driver); const struct sdw_device_id *id; - const char *name; int ret; /* @@ -108,11 +107,6 @@ static int sdw_drv_probe(struct device *dev) ret = drv->probe(slave, id); if (ret) { - name = drv->name; - if (!name) - name = drv->driver.name; - - dev_err(dev, "Probe of %s failed: %d\n", name, ret); dev_pm_domain_detach(dev, false); return ret; } @@ -126,10 +120,10 @@ static int sdw_drv_probe(struct device *dev) if (slave->prop.use_domain_irq) sdw_irq_create_mapping(slave); - /* init the sysfs as we have properties now */ - ret = sdw_slave_sysfs_init(slave); + /* init the dynamic sysfs attributes we need */ + ret = sdw_slave_sysfs_dpn_init(slave); if (ret < 0) - dev_warn(dev, "Slave sysfs init failed:%d\n", ret); + dev_warn(dev, "failed to initialise sysfs: %d\n", ret); /* * Check for valid clk_stop_timeout, use DisCo worst case value of @@ -153,7 +147,7 @@ static int sdw_drv_probe(struct device *dev) if (drv->ops && drv->ops->update_status) { ret = drv->ops->update_status(slave, slave->status); if (ret < 0) - dev_warn(dev, "%s: update_status failed with status %d\n", __func__, ret); + dev_warn(dev, "failed to update status at probe: %d\n", ret); } mutex_unlock(&slave->sdw_dev_lock); @@ -173,9 +167,6 @@ static int sdw_drv_remove(struct device *dev) slave->probed = false; - if (slave->prop.use_domain_irq) - sdw_irq_dispose_mapping(slave); - mutex_unlock(&slave->sdw_dev_lock); if (drv->remove) @@ -204,16 +195,11 @@ static void sdw_drv_shutdown(struct device *dev) */ int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) { - const char *name; - drv->driver.bus = &sdw_bus_type; if (!drv->probe) { - name = drv->name; - if (!name) - name = drv->driver.name; - - pr_err("driver %s didn't provide SDW probe routine\n", name); + pr_err("driver %s didn't provide SDW probe routine\n", + drv->driver.name); return -EINVAL; } @@ -221,6 +207,7 @@ int __sdw_register_driver(struct sdw_driver *drv, struct module *owner) drv->driver.probe = sdw_drv_probe; drv->driver.remove = sdw_drv_remove; drv->driver.shutdown = sdw_drv_shutdown; + drv->driver.dev_groups = sdw_attr_groups; return driver_register(&drv->driver); } diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 0efc1c3bee5f..21bb491d026b 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -6,6 +6,8 @@ * Used by Master driver */ +#include <linux/cleanup.h> +#include <linux/crc8.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/debugfs.h> @@ -183,6 +185,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask"); #define CDNS_PORTCTRL_TEST_FAILED BIT(1) #define CDNS_PORTCTRL_DIRN BIT(7) #define CDNS_PORTCTRL_BANK_INVERT BIT(8) +#define CDNS_PORTCTRL_BULK_ENABLE BIT(16) #define CDNS_PORT_OFFSET 0x80 @@ -323,12 +326,11 @@ static ssize_t cdns_sprintf(struct sdw_cdns *cdns, static int cdns_reg_show(struct seq_file *s, void *data) { struct sdw_cdns *cdns = s->private; - char *buf; ssize_t ret; int num_ports; int i, j; - buf = kzalloc(RD_BUF, GFP_KERNEL); + char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -389,7 +391,6 @@ static int cdns_reg_show(struct seq_file *s, void *data) ret += cdns_sprintf(cdns, buf, ret, CDNS_PDI_CONFIG(i)); seq_printf(s, "%s", buf); - kfree(buf); return 0; } @@ -891,8 +892,14 @@ static int cdns_update_slave_status(struct sdw_cdns *cdns, } } - if (is_slave) - return sdw_handle_slave_status(&cdns->bus, status); + if (is_slave) { + int ret; + + mutex_lock(&cdns->status_update_lock); + ret = sdw_handle_slave_status(&cdns->bus, status); + mutex_unlock(&cdns->status_update_lock); + return ret; + } return 0; } @@ -989,6 +996,31 @@ irqreturn_t sdw_cdns_irq(int irq, void *dev_id) } EXPORT_SYMBOL(sdw_cdns_irq); +static void cdns_check_attached_status_dwork(struct work_struct *work) +{ + struct sdw_cdns *cdns = + container_of(work, struct sdw_cdns, attach_dwork.work); + enum sdw_slave_status status[SDW_MAX_DEVICES + 1]; + u32 val; + int ret; + int i; + + val = cdns_readl(cdns, CDNS_MCP_SLAVE_STAT); + + for (i = 0; i <= SDW_MAX_DEVICES; i++) { + status[i] = val & 0x3; + if (status[i]) + dev_dbg(cdns->dev, "Peripheral %d status: %d\n", i, status[i]); + val >>= 2; + } + + mutex_lock(&cdns->status_update_lock); + ret = sdw_handle_slave_status(&cdns->bus, status); + mutex_unlock(&cdns->status_update_lock); + if (ret < 0) + dev_err(cdns->dev, "%s: sdw_handle_slave_status failed: %d\n", __func__, ret); +} + /** * cdns_update_slave_status_work - update slave status in a work since we will need to handle * other interrupts eg. CDNS_MCP_INT_RX_WL during the update slave @@ -1236,7 +1268,7 @@ EXPORT_SYMBOL(sdw_cdns_enable_interrupt); static int cdns_allocate_pdi(struct sdw_cdns *cdns, struct sdw_cdns_pdi **stream, - u32 num, u32 pdi_offset) + u32 num) { struct sdw_cdns_pdi *pdi; int i; @@ -1249,7 +1281,7 @@ static int cdns_allocate_pdi(struct sdw_cdns *cdns, return -ENOMEM; for (i = 0; i < num; i++) { - pdi[i].num = i + pdi_offset; + pdi[i].num = i; } *stream = pdi; @@ -1266,7 +1298,6 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, struct sdw_cdns_stream_config config) { struct sdw_cdns_streams *stream; - int offset; int ret; cdns->pcm.num_bd = config.pcm_bd; @@ -1277,24 +1308,15 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns, stream = &cdns->pcm; /* we allocate PDI0 and PDI1 which are used for Bulk */ - offset = 0; - - ret = cdns_allocate_pdi(cdns, &stream->bd, - stream->num_bd, offset); + ret = cdns_allocate_pdi(cdns, &stream->bd, stream->num_bd); if (ret) return ret; - offset += stream->num_bd; - - ret = cdns_allocate_pdi(cdns, &stream->in, - stream->num_in, offset); + ret = cdns_allocate_pdi(cdns, &stream->in, stream->num_in); if (ret) return ret; - offset += stream->num_in; - - ret = cdns_allocate_pdi(cdns, &stream->out, - stream->num_out, offset); + ret = cdns_allocate_pdi(cdns, &stream->out, stream->num_out); if (ret) return ret; @@ -1321,7 +1343,7 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols) return val; } -static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) +static int cdns_init_clock_ctrl(struct sdw_cdns *cdns) { struct sdw_bus *bus = &cdns->bus; struct sdw_master_prop *prop = &bus->prop; @@ -1329,14 +1351,31 @@ static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) u32 ssp_interval; int divider; + dev_dbg(cdns->dev, "mclk %d max %d row %d col %d\n", + prop->mclk_freq, + prop->max_clk_freq, + prop->default_row, + prop->default_col); + + if (!prop->default_frame_rate || !prop->default_row) { + dev_err(cdns->dev, "Default frame_rate %d or row %d is invalid\n", + prop->default_frame_rate, prop->default_row); + return -EINVAL; + } + /* Set clock divider */ - divider = (prop->mclk_freq / prop->max_clk_freq) - 1; + divider = (prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR / + bus->params.curr_dr_freq) - 1; cdns_updatel(cdns, CDNS_MCP_CLK_CTRL0, CDNS_MCP_CLK_MCLKD_MASK, divider); cdns_updatel(cdns, CDNS_MCP_CLK_CTRL1, CDNS_MCP_CLK_MCLKD_MASK, divider); + /* Set frame shape base on the actual bus frequency. */ + prop->default_col = bus->params.curr_dr_freq / + prop->default_frame_rate / prop->default_row; + /* * Frame shape changes after initialization have to be done * with the bank switch mechanism @@ -1349,7 +1388,34 @@ static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ; cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval); cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval); + + return 0; +} + +/** + * sdw_cdns_soft_reset() - Cadence soft-reset + * @cdns: Cadence instance + */ +int sdw_cdns_soft_reset(struct sdw_cdns *cdns) +{ + int ret; + + cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_SOFT_RST, + CDNS_MCP_CONTROL_SOFT_RST); + + ret = cdns_config_update(cdns); + if (ret < 0) { + dev_err(cdns->dev, "%s: config update failed\n", __func__); + return ret; + } + + ret = cdns_set_wait(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_SOFT_RST, 0); + if (ret < 0) + dev_err(cdns->dev, "%s: Soft Reset timed out\n", __func__); + + return ret; } +EXPORT_SYMBOL(sdw_cdns_soft_reset); /** * sdw_cdns_init() - Cadence initialization @@ -1357,9 +1423,12 @@ static void cdns_init_clock_ctrl(struct sdw_cdns *cdns) */ int sdw_cdns_init(struct sdw_cdns *cdns) { + int ret; u32 val; - cdns_init_clock_ctrl(cdns); + ret = cdns_init_clock_ctrl(cdns); + if (ret) + return ret; sdw_cdns_check_self_clearing_bits(cdns, __func__, false, 0); @@ -1374,6 +1443,11 @@ int sdw_cdns_init(struct sdw_cdns *cdns) cdns_ip_updatel(cdns, CDNS_IP_MCP_CONTROL, CDNS_IP_MCP_CONTROL_CMD_ACCEPT, CDNS_IP_MCP_CONTROL_CMD_ACCEPT); + /* disable wakeup */ + cdns_ip_updatel(cdns, CDNS_IP_MCP_CONTROL, + CDNS_IP_MCP_CONTROL_BLOCK_WAKEUP, + 0); + /* Configure mcp config */ val = cdns_readl(cdns, CDNS_MCP_CONFIG); @@ -1745,7 +1819,11 @@ int sdw_cdns_probe(struct sdw_cdns *cdns) init_completion(&cdns->tx_complete); cdns->bus.port_ops = &cdns_port_ops; + mutex_init(&cdns->status_update_lock); + INIT_WORK(&cdns->work, cdns_update_slave_status_work); + INIT_DELAYED_WORK(&cdns->attach_dwork, cdns_check_attached_status_dwork); + return 0; } EXPORT_SYMBOL(sdw_cdns_probe); @@ -1802,7 +1880,6 @@ EXPORT_SYMBOL(cdns_set_sdw_stream); * cdns_find_pdi() - Find a free PDI * * @cdns: Cadence instance - * @offset: Starting offset * @num: Number of PDIs * @pdi: PDI instances * @dai_id: DAI id @@ -1811,14 +1888,13 @@ EXPORT_SYMBOL(cdns_set_sdw_stream); * expected to match, return NULL otherwise. */ static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns, - unsigned int offset, unsigned int num, struct sdw_cdns_pdi *pdi, int dai_id) { int i; - for (i = offset; i < offset + num; i++) + for (i = 0; i < num; i++) if (pdi[i].num == dai_id) return &pdi[i]; @@ -1843,13 +1919,20 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns, if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL) val |= CDNS_PORTCTRL_TEST_FAILED; + } else if (pdi->num == 0 || pdi->num == 1) { + val |= CDNS_PORTCTRL_BULK_ENABLE; } offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET; cdns_updatel(cdns, offset, - CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED, + CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED | + CDNS_PORTCTRL_BULK_ENABLE, val); - val = pdi->num; + /* The DataPort0 needs to be mapped to both PDI0 and PDI1 ! */ + if (pdi->num == 1) + val = 0; + else + val = pdi->num; val |= CDNS_PDI_CONFIG_SOFT_RESET; val |= FIELD_PREP(CDNS_PDI_CONFIG_CHANNEL, (1 << ch) - 1); cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val); @@ -1872,15 +1955,15 @@ struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, struct sdw_cdns_pdi *pdi = NULL; if (dir == SDW_DATA_DIR_RX) - pdi = cdns_find_pdi(cdns, 0, stream->num_in, stream->in, + pdi = cdns_find_pdi(cdns, stream->num_in, stream->in, dai_id); else - pdi = cdns_find_pdi(cdns, 0, stream->num_out, stream->out, + pdi = cdns_find_pdi(cdns, stream->num_out, stream->out, dai_id); /* check if we found a PDI, else find in bi-directional */ if (!pdi) - pdi = cdns_find_pdi(cdns, 2, stream->num_bd, stream->bd, + pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd, dai_id); if (pdi) { @@ -1894,5 +1977,638 @@ struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns, } EXPORT_SYMBOL(sdw_cdns_alloc_pdi); +/* + * the MIPI SoundWire CRC8 polynomial is X^8 + X^6 + X^3 + X^2 + 1, MSB first + * The value is (1)01001101 = 0x4D + * + * the table below was generated with + * + * u8 crc8_lookup_table[CRC8_TABLE_SIZE]; + * crc8_populate_msb(crc8_lookup_table, SDW_CRC8_POLY); + * + */ +#define SDW_CRC8_SEED 0xFF +#define SDW_CRC8_POLY 0x4D + +static const u8 sdw_crc8_lookup_msb[CRC8_TABLE_SIZE] = { + 0x00, 0x4d, 0x9a, 0xd7, 0x79, 0x34, 0xe3, 0xae, /* 0 - 7 */ + 0xf2, 0xbf, 0x68, 0x25, 0x8b, 0xc6, 0x11, 0x5c, /* 8 -15 */ + 0xa9, 0xe4, 0x33, 0x7e, 0xd0, 0x9d, 0x4a, 0x07, /* 16 - 23 */ + 0x5b, 0x16, 0xc1, 0x8c, 0x22, 0x6f, 0xb8, 0xf5, /* 24 - 31 */ + 0x1f, 0x52, 0x85, 0xc8, 0x66, 0x2b, 0xfc, 0xb1, /* 32 - 39 */ + 0xed, 0xa0, 0x77, 0x3a, 0x94, 0xd9, 0x0e, 0x43, /* 40 - 47 */ + 0xb6, 0xfb, 0x2c, 0x61, 0xcf, 0x82, 0x55, 0x18, /* 48 - 55 */ + 0x44, 0x09, 0xde, 0x93, 0x3d, 0x70, 0xa7, 0xea, /* 56 - 63 */ + 0x3e, 0x73, 0xa4, 0xe9, 0x47, 0x0a, 0xdd, 0x90, /* 64 - 71 */ + 0xcc, 0x81, 0x56, 0x1b, 0xb5, 0xf8, 0x2f, 0x62, /* 72 - 79 */ + 0x97, 0xda, 0x0d, 0x40, 0xee, 0xa3, 0x74, 0x39, /* 80 - 87 */ + 0x65, 0x28, 0xff, 0xb2, 0x1c, 0x51, 0x86, 0xcb, /* 88 - 95 */ + 0x21, 0x6c, 0xbb, 0xf6, 0x58, 0x15, 0xc2, 0x8f, /* 96 - 103 */ + 0xd3, 0x9e, 0x49, 0x04, 0xaa, 0xe7, 0x30, 0x7d, /* 104 - 111 */ + 0x88, 0xc5, 0x12, 0x5f, 0xf1, 0xbc, 0x6b, 0x26, /* 112 - 119 */ + 0x7a, 0x37, 0xe0, 0xad, 0x03, 0x4e, 0x99, 0xd4, /* 120 - 127 */ + 0x7c, 0x31, 0xe6, 0xab, 0x05, 0x48, 0x9f, 0xd2, /* 128 - 135 */ + 0x8e, 0xc3, 0x14, 0x59, 0xf7, 0xba, 0x6d, 0x20, /* 136 - 143 */ + 0xd5, 0x98, 0x4f, 0x02, 0xac, 0xe1, 0x36, 0x7b, /* 144 - 151 */ + 0x27, 0x6a, 0xbd, 0xf0, 0x5e, 0x13, 0xc4, 0x89, /* 152 - 159 */ + 0x63, 0x2e, 0xf9, 0xb4, 0x1a, 0x57, 0x80, 0xcd, /* 160 - 167 */ + 0x91, 0xdc, 0x0b, 0x46, 0xe8, 0xa5, 0x72, 0x3f, /* 168 - 175 */ + 0xca, 0x87, 0x50, 0x1d, 0xb3, 0xfe, 0x29, 0x64, /* 176 - 183 */ + 0x38, 0x75, 0xa2, 0xef, 0x41, 0x0c, 0xdb, 0x96, /* 184 - 191 */ + 0x42, 0x0f, 0xd8, 0x95, 0x3b, 0x76, 0xa1, 0xec, /* 192 - 199 */ + 0xb0, 0xfd, 0x2a, 0x67, 0xc9, 0x84, 0x53, 0x1e, /* 200 - 207 */ + 0xeb, 0xa6, 0x71, 0x3c, 0x92, 0xdf, 0x08, 0x45, /* 208 - 215 */ + 0x19, 0x54, 0x83, 0xce, 0x60, 0x2d, 0xfa, 0xb7, /* 216 - 223 */ + 0x5d, 0x10, 0xc7, 0x8a, 0x24, 0x69, 0xbe, 0xf3, /* 224 - 231 */ + 0xaf, 0xe2, 0x35, 0x78, 0xd6, 0x9b, 0x4c, 0x01, /* 232 - 239 */ + 0xf4, 0xb9, 0x6e, 0x23, 0x8d, 0xc0, 0x17, 0x5a, /* 240 - 247 */ + 0x06, 0x4b, 0x9c, 0xd1, 0x7f, 0x32, 0xe5, 0xa8 /* 248 - 255 */ +}; + +/* BPT/BRA helpers */ + +#define SDW_CDNS_BRA_HDR 6 /* defined by MIPI */ +#define SDW_CDNS_BRA_HDR_CRC 1 /* defined by MIPI */ +#define SDW_CDNS_BRA_HDR_CRC_PAD 1 /* Cadence only */ +#define SDW_CDNS_BRA_HDR_RESP 1 /* defined by MIPI */ +#define SDW_CDNS_BRA_HDR_RESP_PAD 1 /* Cadence only */ + +#define SDW_CDNS_BRA_DATA_PAD 1 /* Cadence only */ +#define SDW_CDNS_BRA_DATA_CRC 1 /* defined by MIPI */ +#define SDW_CDNS_BRA_DATA_CRC_PAD 1 /* Cadence only */ + +#define SDW_CDNS_BRA_FOOTER_RESP 1 /* defined by MIPI */ +#define SDW_CDNS_BRA_FOOTER_RESP_PAD 1 /* Cadence only */ + +#define SDW_CDNS_WRITE_PDI1_BUFFER_SIZE \ + ((SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD + \ + SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD) * 2) + +#define SDW_CDNS_READ_PDI0_BUFFER_SIZE \ + ((SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD) * 2) + +static unsigned int sdw_cdns_bra_actual_data_size(unsigned int allocated_bytes_per_frame) +{ + unsigned int total; + + if (allocated_bytes_per_frame < (SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + + SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_DATA_CRC + + SDW_CDNS_BRA_FOOTER_RESP)) + return 0; + + total = allocated_bytes_per_frame - SDW_CDNS_BRA_HDR - SDW_CDNS_BRA_HDR_CRC - + SDW_CDNS_BRA_HDR_RESP - SDW_CDNS_BRA_DATA_CRC - SDW_CDNS_BRA_FOOTER_RESP; + + return total; +} + +static unsigned int sdw_cdns_write_pdi0_buffer_size(unsigned int actual_data_size) +{ + unsigned int total; + + total = SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD; + + total += actual_data_size; + if (actual_data_size & 1) + total += SDW_CDNS_BRA_DATA_PAD; + + total += SDW_CDNS_BRA_DATA_CRC + SDW_CDNS_BRA_DATA_CRC_PAD; + + return total * 2; +} + +static unsigned int sdw_cdns_read_pdi1_buffer_size(unsigned int actual_data_size) +{ + unsigned int total; + + total = SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD; + + total += actual_data_size; + if (actual_data_size & 1) + total += SDW_CDNS_BRA_DATA_PAD; + + total += SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD; + + total += SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD; + + return total * 2; +} + +int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */ + int row, int col, unsigned int data_bytes, + unsigned int requested_bytes_per_frame, + unsigned int *data_per_frame, unsigned int *pdi0_buffer_size, + unsigned int *pdi1_buffer_size, unsigned int *num_frames) +{ + unsigned int bpt_bits = row * (col - 1); + unsigned int bpt_bytes = bpt_bits >> 3; + unsigned int actual_bpt_bytes; + unsigned int pdi0_tx_size; + unsigned int pdi1_rx_size; + unsigned int remainder; + + if (!data_bytes) + return -EINVAL; + + actual_bpt_bytes = sdw_cdns_bra_actual_data_size(bpt_bytes); + if (!actual_bpt_bytes) + return -EINVAL; + + if (data_bytes < actual_bpt_bytes) + actual_bpt_bytes = data_bytes; + + /* + * the caller may want to set the number of bytes per frame, + * allow when possible + */ + if (requested_bytes_per_frame < actual_bpt_bytes) + actual_bpt_bytes = requested_bytes_per_frame; + + *data_per_frame = actual_bpt_bytes; + + if (command == 0) { + /* + * for writes we need to send all the data_bytes per frame, + * even for the last frame which may only transport fewer bytes + */ + + *num_frames = DIV_ROUND_UP(data_bytes, actual_bpt_bytes); + + pdi0_tx_size = sdw_cdns_write_pdi0_buffer_size(actual_bpt_bytes); + pdi1_rx_size = SDW_CDNS_WRITE_PDI1_BUFFER_SIZE; + + *pdi0_buffer_size = pdi0_tx_size * *num_frames; + *pdi1_buffer_size = pdi1_rx_size * *num_frames; + } else { + /* + * for reads we need to retrieve only what is requested in the BPT + * header, so the last frame needs to be special-cased + */ + *num_frames = data_bytes / actual_bpt_bytes; + + pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE; + pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(actual_bpt_bytes); + + *pdi0_buffer_size = pdi0_tx_size * *num_frames; + *pdi1_buffer_size = pdi1_rx_size * *num_frames; + + remainder = data_bytes % actual_bpt_bytes; + if (remainder) { + pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE; + pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(remainder); + + *num_frames = *num_frames + 1; + *pdi0_buffer_size += pdi0_tx_size; + *pdi1_buffer_size += pdi1_rx_size; + } + } + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_bpt_find_buffer_sizes); + +static int sdw_cdns_copy_write_data(u8 *data, int data_size, u8 *dma_buffer, int dma_buffer_size) +{ + /* + * the implementation copies the data one byte at a time. Experiments with + * two bytes at a time did not seem to improve the performance + */ + int i, j; + + /* size check to prevent out of bounds access */ + i = data_size - 1; + j = (2 * i) - (i & 1); + if (data_size & 1) + j++; + j += 2; + if (j >= dma_buffer_size) + return -EINVAL; + + /* copy data */ + for (i = 0; i < data_size; i++) { + j = (2 * i) - (i & 1); + dma_buffer[j] = data[i]; + } + /* add required pad */ + if (data_size & 1) + dma_buffer[++j] = 0; + /* skip last two bytes */ + j += 2; + + /* offset and data are off-by-one */ + return j + 1; +} + +static int sdw_cdns_prepare_write_pd0_buffer(u8 *header, unsigned int header_size, + u8 *data, unsigned int data_size, + u8 *dma_buffer, unsigned int dma_buffer_size, + unsigned int *dma_data_written, + unsigned int frame_counter) +{ + int data_written; + u8 *last_byte; + u8 crc; + + *dma_data_written = 0; + + data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer[3] = BIT(7); + dma_buffer[3] |= frame_counter & GENMASK(3, 0); + + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + crc = SDW_CRC8_SEED; + crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc); + + data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + data_written = sdw_cdns_copy_write_data(data, data_size, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + crc = SDW_CRC8_SEED; + crc = crc8(sdw_crc8_lookup_msb, data, data_size, crc); + data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + /* tag last byte */ + last_byte = dma_buffer - 1; + last_byte[0] = BIT(6); + + return 0; +} + +static int sdw_cdns_prepare_read_pd0_buffer(u8 *header, unsigned int header_size, + u8 *dma_buffer, unsigned int dma_buffer_size, + unsigned int *dma_data_written, + unsigned int frame_counter) +{ + int data_written; + u8 *last_byte; + u8 crc; + + *dma_data_written = 0; + + data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer[3] = BIT(7); + dma_buffer[3] |= frame_counter & GENMASK(3, 0); + + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + crc = SDW_CRC8_SEED; + crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc); + + data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size); + if (data_written < 0) + return data_written; + dma_buffer += data_written; + dma_buffer_size -= data_written; + *dma_data_written += data_written; + + /* tag last byte */ + last_byte = dma_buffer - 1; + last_byte[0] = BIT(6); + + return 0; +} + +#define CDNS_BPT_ROLLING_COUNTER_START 1 + +int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size, + int data_per_frame, u8 *dma_buffer, int dma_buffer_size, + int *dma_buffer_total_bytes) +{ + int total_dma_data_written = 0; + u8 *p_dma_buffer = dma_buffer; + u8 header[SDW_CDNS_BRA_HDR]; + int dma_data_written; + u8 *p_data = data; + u8 counter; + int ret; + + counter = CDNS_BPT_ROLLING_COUNTER_START; + + header[0] = BIT(1); /* write command: BIT(1) set */ + header[0] |= GENMASK(7, 6); /* header is active */ + header[0] |= (dev_num << 2); + + while (data_size >= data_per_frame) { + header[1] = data_per_frame; + header[2] = start_register >> 24 & 0xFF; + header[3] = start_register >> 16 & 0xFF; + header[4] = start_register >> 8 & 0xFF; + header[5] = start_register >> 0 & 0xFF; + + ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR, + p_data, data_per_frame, + p_dma_buffer, dma_buffer_size, + &dma_data_written, counter); + if (ret < 0) + return ret; + + counter++; + + p_data += data_per_frame; + data_size -= data_per_frame; + + p_dma_buffer += dma_data_written; + dma_buffer_size -= dma_data_written; + total_dma_data_written += dma_data_written; + + start_register += data_per_frame; + } + + if (data_size) { + header[1] = data_size; + header[2] = start_register >> 24 & 0xFF; + header[3] = start_register >> 16 & 0xFF; + header[4] = start_register >> 8 & 0xFF; + header[5] = start_register >> 0 & 0xFF; + + ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR, + p_data, data_size, + p_dma_buffer, dma_buffer_size, + &dma_data_written, counter); + if (ret < 0) + return ret; + + total_dma_data_written += dma_data_written; + } + + *dma_buffer_total_bytes = total_dma_data_written; + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_prepare_write_dma_buffer); + +int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size, + int data_per_frame, u8 *dma_buffer, int dma_buffer_size, + int *dma_buffer_total_bytes) +{ + int total_dma_data_written = 0; + u8 *p_dma_buffer = dma_buffer; + u8 header[SDW_CDNS_BRA_HDR]; + int dma_data_written; + u8 counter; + int ret; + + counter = CDNS_BPT_ROLLING_COUNTER_START; + + header[0] = 0; /* read command: BIT(1) cleared */ + header[0] |= GENMASK(7, 6); /* header is active */ + header[0] |= (dev_num << 2); + + while (data_size >= data_per_frame) { + header[1] = data_per_frame; + header[2] = start_register >> 24 & 0xFF; + header[3] = start_register >> 16 & 0xFF; + header[4] = start_register >> 8 & 0xFF; + header[5] = start_register >> 0 & 0xFF; + + ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer, + dma_buffer_size, &dma_data_written, + counter); + if (ret < 0) + return ret; + + counter++; + + data_size -= data_per_frame; + + p_dma_buffer += dma_data_written; + dma_buffer_size -= dma_data_written; + total_dma_data_written += dma_data_written; + + start_register += data_per_frame; + } + + if (data_size) { + header[1] = data_size; + header[2] = start_register >> 24 & 0xFF; + header[3] = start_register >> 16 & 0xFF; + header[4] = start_register >> 8 & 0xFF; + header[5] = start_register >> 0 & 0xFF; + + ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer, + dma_buffer_size, &dma_data_written, + counter); + if (ret < 0) + return ret; + + total_dma_data_written += dma_data_written; + } + + *dma_buffer_total_bytes = total_dma_data_written; + + return 0; +} +EXPORT_SYMBOL(sdw_cdns_prepare_read_dma_buffer); + +static int check_counter(u32 val, u8 counter) +{ + u8 frame; + + frame = (val >> 24) & GENMASK(3, 0); + if (counter != frame) + return -EIO; + return 0; +} + +static int check_response(u32 val) +{ + u8 response; + + response = (val >> 3) & GENMASK(1, 0); + if (response == 0) /* Ignored */ + return -ENODATA; + if (response != 1) /* ACK */ + return -EIO; + + return 0; +} + +static int check_frame_start(u32 header, u8 counter) +{ + int ret; + + /* check frame_start marker */ + if (!(header & BIT(31))) + return -EIO; + + ret = check_counter(header, counter); + if (ret < 0) + return ret; + + return check_response(header); +} + +static int check_frame_end(u32 footer) +{ + /* check frame_end marker */ + if (!(footer & BIT(30))) + return -EIO; + + return check_response(footer); +} + +int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer, + int dma_buffer_size, int num_frames) +{ + u32 *p_data; + int counter; + u32 header; + u32 footer; + int ret; + int i; + + /* paranoia check on buffer size */ + if (dma_buffer_size != num_frames * 8) + return -EINVAL; + + counter = CDNS_BPT_ROLLING_COUNTER_START; + p_data = (u32 *)dma_buffer; + + for (i = 0; i < num_frames; i++) { + header = *p_data++; + footer = *p_data++; + + ret = check_frame_start(header, counter); + if (ret < 0) { + dev_err(dev, "%s: bad frame %d/%d start header %x\n", + __func__, i, num_frames, header); + return ret; + } + + ret = check_frame_end(footer); + if (ret < 0) { + dev_err(dev, "%s: bad frame %d/%d end footer %x\n", + __func__, i, num_frames, footer); + return ret; + } + + counter++; + counter &= GENMASK(3, 0); + } + return 0; +} +EXPORT_SYMBOL(sdw_cdns_check_write_response); + +static u8 extract_read_data(u32 *data, int num_bytes, u8 *buffer) +{ + u32 val; + int i; + u8 crc; + u8 b0; + u8 b1; + + crc = SDW_CRC8_SEED; + + /* process two bytes at a time */ + for (i = 0; i < num_bytes / 2; i++) { + val = *data++; + + b0 = val & 0xff; + b1 = (val >> 8) & 0xff; + + *buffer++ = b0; + crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc); + + *buffer++ = b1; + crc = crc8(sdw_crc8_lookup_msb, &b1, 1, crc); + } + /* handle remaining byte if it exists */ + if (num_bytes & 1) { + val = *data; + + b0 = val & 0xff; + + *buffer++ = b0; + crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc); + } + return crc; +} + +int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size, + u8 *buffer, int buffer_size, int num_frames, int data_per_frame) +{ + int total_num_bytes = 0; + u32 *p_data; + u8 *p_buf; + int counter; + u32 header; + u32 footer; + u8 expected_crc; + u8 crc; + int len; + int ret; + int i; + + counter = CDNS_BPT_ROLLING_COUNTER_START; + p_data = (u32 *)dma_buffer; + p_buf = buffer; + + for (i = 0; i < num_frames; i++) { + header = *p_data++; + + ret = check_frame_start(header, counter); + if (ret < 0) { + dev_err(dev, "%s: bad frame %d/%d start header %x\n", + __func__, i, num_frames, header); + return ret; + } + + len = data_per_frame; + if (total_num_bytes + data_per_frame > buffer_size) + len = buffer_size - total_num_bytes; + + crc = extract_read_data(p_data, len, p_buf); + + p_data += (len + 1) / 2; + expected_crc = *p_data++ & 0xff; + + if (crc != expected_crc) { + dev_err(dev, "%s: bad frame %d/%d crc %#x expected %#x\n", + __func__, i, num_frames, crc, expected_crc); + return -EIO; + } + + p_buf += len; + total_num_bytes += len; + + footer = *p_data++; + ret = check_frame_end(footer); + if (ret < 0) { + dev_err(dev, "%s: bad frame %d/%d end footer %x\n", + __func__, i, num_frames, footer); + return ret; + } + + counter++; + counter &= GENMASK(3, 0); + } + return 0; +} +EXPORT_SYMBOL(sdw_cdns_check_read_response); + MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Cadence Soundwire Library"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index bc84435e420f..9373426c7f63 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -117,6 +117,8 @@ struct sdw_cdns_dai_runtime { * @link_up: Link status * @msg_count: Messages sent on bus * @dai_runtime_array: runtime context for each allocated DAI. + * @status_update_lock: protect concurrency between interrupt-based and delayed work + * status update */ struct sdw_cdns { struct device *dev; @@ -148,10 +150,13 @@ struct sdw_cdns { bool interrupt_enabled; struct work_struct work; + struct delayed_work attach_dwork; struct list_head list; struct sdw_cdns_dai_runtime **dai_runtime_array; + + struct mutex status_update_lock; /* add mutual exclusion to sdw_handle_slave_status() */ }; #define bus_to_cdns(_bus) container_of(_bus, struct sdw_cdns, bus) @@ -163,6 +168,7 @@ int sdw_cdns_probe(struct sdw_cdns *cdns); irqreturn_t sdw_cdns_irq(int irq, void *dev_id); irqreturn_t sdw_cdns_thread(int irq, void *dev_id); +int sdw_cdns_soft_reset(struct sdw_cdns *cdns); int sdw_cdns_init(struct sdw_cdns *cdns); int sdw_cdns_pdi_init(struct sdw_cdns *cdns, struct sdw_cdns_stream_config config); @@ -202,4 +208,24 @@ void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string void sdw_cdns_config_update(struct sdw_cdns *cdns); int sdw_cdns_config_update_set_wait(struct sdw_cdns *cdns); +/* SoundWire BPT/BRA helpers to format data */ +int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */ + int row, int col, unsigned int data_bytes, + unsigned int requested_bytes_per_frame, + unsigned int *data_per_frame, unsigned int *pdi0_buffer_size, + unsigned int *pdi1_buffer_size, unsigned int *num_frames); + +int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size, + int data_per_frame, u8 *dma_buffer, int dma_buffer_size, + int *dma_buffer_total_bytes); + +int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size, + int data_per_frame, u8 *dma_buffer, int dma_buffer_size, + int *dma_buffer_total_bytes); + +int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer, + int dma_buffer_size, int num_frames); + +int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size, + u8 *buffer, int buffer_size, int num_frames, int data_per_frame); #endif /* __SDW_CADENCE_H */ diff --git a/drivers/soundwire/debugfs.c b/drivers/soundwire/debugfs.c index 67abd7e52f09..3099ea074f10 100644 --- a/drivers/soundwire/debugfs.c +++ b/drivers/soundwire/debugfs.c @@ -1,13 +1,16 @@ // SPDX-License-Identifier: GPL-2.0-only // Copyright(c) 2017-2019 Intel Corporation. +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/debugfs.h> +#include <linux/firmware.h> #include <linux/mod_devicetable.h> #include <linux/pm_runtime.h> #include <linux/slab.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_registers.h> +#include <linux/string_choices.h> #include "bus.h" static struct dentry *sdw_debugfs_root; @@ -48,18 +51,16 @@ static ssize_t sdw_sprintf(struct sdw_slave *slave, static int sdw_slave_reg_show(struct seq_file *s_file, void *data) { struct sdw_slave *slave = s_file->private; - char *buf; ssize_t ret; int i, j; - buf = kzalloc(RD_BUF, GFP_KERNEL); + char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; ret = pm_runtime_get_sync(&slave->dev); if (ret < 0 && ret != -EACCES) { pm_runtime_put_noidle(&slave->dev); - kfree(buf); return ret; } @@ -131,12 +132,200 @@ static int sdw_slave_reg_show(struct seq_file *s_file, void *data) pm_runtime_mark_last_busy(&slave->dev); pm_runtime_put(&slave->dev); - kfree(buf); - return 0; } DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg); +#define MAX_CMD_BYTES (1024 * 1024) + +static int cmd; +static int cmd_type; +static u32 start_addr; +static size_t num_bytes; +static u8 read_buffer[MAX_CMD_BYTES]; +static char *firmware_file; + +static int set_command(void *data, u64 value) +{ + struct sdw_slave *slave = data; + + if (value > 1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + dev_dbg(&slave->dev, "command: %s\n", str_read_write(value)); + cmd = value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(set_command_fops, NULL, + set_command, "%llu\n"); + +static int set_command_type(void *data, u64 value) +{ + struct sdw_slave *slave = data; + + if (value > 1) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + dev_dbg(&slave->dev, "command type: %s\n", value ? "BRA" : "Column0"); + + cmd_type = (int)value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(set_command_type_fops, NULL, + set_command_type, "%llu\n"); + +static int set_start_address(void *data, u64 value) +{ + struct sdw_slave *slave = data; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + dev_dbg(&slave->dev, "start address %#llx\n", value); + + start_addr = value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(set_start_address_fops, NULL, + set_start_address, "%llu\n"); + +static int set_num_bytes(void *data, u64 value) +{ + struct sdw_slave *slave = data; + + if (value == 0 || value > MAX_CMD_BYTES) + return -EINVAL; + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + dev_dbg(&slave->dev, "number of bytes %lld\n", value); + + num_bytes = value; + + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(set_num_bytes_fops, NULL, + set_num_bytes, "%llu\n"); + +static int do_bpt_sequence(struct sdw_slave *slave, bool write, u8 *buffer) +{ + struct sdw_bpt_msg msg = {0}; + + msg.addr = start_addr; + msg.len = num_bytes; + msg.dev_num = slave->dev_num; + if (write) + msg.flags = SDW_MSG_FLAG_WRITE; + else + msg.flags = SDW_MSG_FLAG_READ; + msg.buf = buffer; + + return sdw_bpt_send_sync(slave->bus, slave, &msg); +} + +static int cmd_go(void *data, u64 value) +{ + const struct firmware *fw = NULL; + struct sdw_slave *slave = data; + ktime_t start_t; + ktime_t finish_t; + int ret; + + if (value != 1) + return -EINVAL; + + /* one last check */ + if (start_addr > SDW_REG_MAX || + num_bytes == 0 || num_bytes > MAX_CMD_BYTES) + return -EINVAL; + + ret = pm_runtime_get_sync(&slave->dev); + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(&slave->dev); + return ret; + } + + if (cmd == 0) { + ret = request_firmware(&fw, firmware_file, &slave->dev); + if (ret < 0) { + dev_err(&slave->dev, "firmware %s not found\n", firmware_file); + goto out; + } + if (fw->size < num_bytes) { + dev_err(&slave->dev, + "firmware %s: firmware size %zd, desired %zd\n", + firmware_file, fw->size, num_bytes); + goto out; + } + } + + /* Userspace changed the hardware state behind the kernel's back */ + add_taint(TAINT_USER, LOCKDEP_STILL_OK); + + dev_dbg(&slave->dev, "starting command\n"); + start_t = ktime_get(); + + if (cmd == 0) { + if (cmd_type) + ret = do_bpt_sequence(slave, true, (u8 *)fw->data); + else + ret = sdw_nwrite_no_pm(slave, start_addr, num_bytes, fw->data); + } else { + memset(read_buffer, 0, sizeof(read_buffer)); + + if (cmd_type) + ret = do_bpt_sequence(slave, false, read_buffer); + else + ret = sdw_nread_no_pm(slave, start_addr, num_bytes, read_buffer); + } + + finish_t = ktime_get(); + +out: + if (fw) + release_firmware(fw); + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put(&slave->dev); + + dev_dbg(&slave->dev, "command completed, num_byte %zu status %d, time %lld ms\n", + num_bytes, ret, div_u64(finish_t - start_t, NSEC_PER_MSEC)); + + return ret; +} +DEFINE_DEBUGFS_ATTRIBUTE(cmd_go_fops, NULL, + cmd_go, "%llu\n"); + +#define MAX_LINE_LEN 128 + +static int read_buffer_show(struct seq_file *s_file, void *data) +{ + char buf[MAX_LINE_LEN]; + int i; + + if (num_bytes == 0 || num_bytes > MAX_CMD_BYTES) + return -EINVAL; + + for (i = 0; i < num_bytes; i++) { + scnprintf(buf, MAX_LINE_LEN, "address %#x val 0x%02x\n", + start_addr + i, read_buffer[i]); + seq_printf(s_file, "%s", buf); + } + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(read_buffer); + void sdw_slave_debugfs_init(struct sdw_slave *slave) { struct dentry *master; @@ -151,6 +340,17 @@ void sdw_slave_debugfs_init(struct sdw_slave *slave) debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops); + /* interface to send arbitrary commands */ + debugfs_create_file("command", 0200, d, slave, &set_command_fops); + debugfs_create_file("command_type", 0200, d, slave, &set_command_type_fops); + debugfs_create_file("start_address", 0200, d, slave, &set_start_address_fops); + debugfs_create_file("num_bytes", 0200, d, slave, &set_num_bytes_fops); + debugfs_create_file("go", 0200, d, slave, &cmd_go_fops); + + debugfs_create_file("read_buffer", 0400, d, slave, &read_buffer_fops); + firmware_file = NULL; + debugfs_create_str("firmware_file", 0200, d, &firmware_file); + slave->debugfs = d; } diff --git a/drivers/soundwire/generic_bandwidth_allocation.c b/drivers/soundwire/generic_bandwidth_allocation.c index c70a63d009ae..1cfaccf43eac 100644 --- a/drivers/soundwire/generic_bandwidth_allocation.c +++ b/drivers/soundwire/generic_bandwidth_allocation.c @@ -18,6 +18,7 @@ struct sdw_group_params { unsigned int rate; + unsigned int lane; int full_bw; int payload_bw; int hwidth; @@ -27,6 +28,7 @@ struct sdw_group { unsigned int count; unsigned int max_size; unsigned int *rates; + unsigned int *lanes; }; void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, @@ -48,6 +50,9 @@ void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, slave_total_ch = 0; list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + if (p_rt->lane != t_data->lane) + continue; + ch = hweight32(p_rt->ch_mask); sdw_fill_xport_params(&p_rt->transport_params, @@ -56,7 +61,7 @@ void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, sample_int, port_bo, port_bo >> 8, t_data->hstart, t_data->hstop, - SDW_BLK_PKG_PER_PORT, 0x0); + SDW_BLK_PKG_PER_PORT, p_rt->lane); sdw_fill_port_params(&p_rt->port_params, p_rt->num, bps, @@ -81,9 +86,52 @@ void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt, } EXPORT_SYMBOL(sdw_compute_slave_ports); +static void sdw_compute_dp0_slave_ports(struct sdw_master_runtime *m_rt) +{ + struct sdw_bus *bus = m_rt->bus; + struct sdw_slave_runtime *s_rt; + struct sdw_port_runtime *p_rt; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + list_for_each_entry(p_rt, &s_rt->port_list, port_node) { + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, false, + SDW_BLK_GRP_CNT_1, bus->params.col, 0, 0, 1, + bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0); + + sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1, + SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL); + } + } +} + +static void sdw_compute_dp0_master_ports(struct sdw_master_runtime *m_rt) +{ + struct sdw_port_runtime *p_rt; + struct sdw_bus *bus = m_rt->bus; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, false, + SDW_BLK_GRP_CNT_1, bus->params.col, 0, 0, 1, + bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0); + + sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1, + SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL); + } +} + +static void sdw_compute_dp0_port_params(struct sdw_bus *bus) +{ + struct sdw_master_runtime *m_rt; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + sdw_compute_dp0_master_ports(m_rt); + sdw_compute_dp0_slave_ports(m_rt); + } +} + static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, struct sdw_group_params *params, - int port_bo, int hstop) + int *port_bo, int hstop) { struct sdw_transport_data t_data = {0}; struct sdw_port_runtime *p_rt; @@ -105,11 +153,13 @@ static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, t_data.hstart = hstart; list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + if (p_rt->lane != params->lane) + continue; 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); + *port_bo, (*port_bo) >> 8, hstart, hstop, + SDW_BLK_PKG_PER_PORT, p_rt->lane); sdw_fill_port_params(&p_rt->port_params, p_rt->num, bps, @@ -120,17 +170,18 @@ static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt, if (!(p_rt == list_first_entry(&m_rt->port_list, struct sdw_port_runtime, port_node))) { - port_bo += bps * ch; + (*port_bo) += bps * ch; continue; } t_data.hstart = hstart; t_data.hstop = hstop; - t_data.block_offset = port_bo; + t_data.block_offset = *port_bo; t_data.sub_block_offset = 0; - port_bo += bps * ch; + (*port_bo) += bps * ch; } + t_data.lane = params->lane; sdw_compute_slave_ports(m_rt, &t_data); } @@ -138,71 +189,108 @@ static void _sdw_compute_port_params(struct sdw_bus *bus, struct sdw_group_params *params, int count) { struct sdw_master_runtime *m_rt; - int hstop = bus->params.col - 1; - int port_bo, i; + int port_bo, i, l; + int hstop; /* Run loop for all groups to compute transport parameters */ - for (i = 0; i < count; i++) { - port_bo = 1; + for (l = 0; l < SDW_MAX_LANES; l++) { + if (l > 0 && !bus->lane_used_bandwidth[l]) + continue; + /* reset hstop for each lane */ + hstop = bus->params.col - 1; + for (i = 0; i < count; i++) { + if (params[i].lane != l) + continue; + port_bo = 1; - list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { - sdw_compute_master_ports(m_rt, ¶ms[i], port_bo, hstop); + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + sdw_compute_master_ports(m_rt, ¶ms[i], &port_bo, hstop); + } - port_bo += m_rt->ch_count * m_rt->stream->params.bps; + hstop = hstop - params[i].hwidth; } - - hstop = hstop - params[i].hwidth; } } static int sdw_compute_group_params(struct sdw_bus *bus, + struct sdw_stream_runtime *stream, struct sdw_group_params *params, - int *rates, int count) + struct sdw_group *group) { struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; int sel_col = bus->params.col; unsigned int rate, bps, ch; - int i, column_needed = 0; + int i, l, column_needed; /* Calculate bandwidth per group */ - for (i = 0; i < count; i++) { - params[i].rate = rates[i]; + for (i = 0; i < group->count; i++) { + params[i].rate = group->rates[i]; + params[i].lane = group->lanes[i]; params[i].full_bw = bus->params.curr_dr_freq / params[i].rate; } list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { - rate = m_rt->stream->params.rate; - bps = m_rt->stream->params.bps; - ch = m_rt->ch_count; + if (m_rt->stream == stream) { + /* Only runtime during prepare should be added */ + if (stream->state != SDW_STREAM_CONFIGURED) + continue; + } else { + /* + * Include runtimes with running (ENABLED/PREPARED state) and + * paused (DISABLED state) streams + */ + if (m_rt->stream->state != SDW_STREAM_ENABLED && + m_rt->stream->state != SDW_STREAM_PREPARED && + m_rt->stream->state != SDW_STREAM_DISABLED) + continue; + } + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + rate = m_rt->stream->params.rate; + bps = m_rt->stream->params.bps; + ch = hweight32(p_rt->ch_mask); - for (i = 0; i < count; i++) { - if (rate == params[i].rate) - params[i].payload_bw += bps * ch; + for (i = 0; i < group->count; i++) { + if (rate == params[i].rate && p_rt->lane == params[i].lane) + params[i].payload_bw += bps * ch; + } } } - for (i = 0; i < count; i++) { - params[i].hwidth = (sel_col * - params[i].payload_bw + params[i].full_bw - 1) / - params[i].full_bw; + for (l = 0; l < SDW_MAX_LANES; l++) { + if (l > 0 && !bus->lane_used_bandwidth[l]) + continue; + /* reset column_needed for each lane */ + column_needed = 0; + for (i = 0; i < group->count; i++) { + if (params[i].lane != l) + continue; + + params[i].hwidth = (sel_col * params[i].payload_bw + + params[i].full_bw - 1) / params[i].full_bw; - column_needed += params[i].hwidth; + column_needed += params[i].hwidth; + /* There is no control column for lane 1 and above */ + if (column_needed > sel_col) + return -EINVAL; + /* Column 0 is control column on lane 0 */ + if (params[i].lane == 0 && column_needed > sel_col - 1) + return -EINVAL; + } } - if (column_needed > sel_col - 1) - return -EINVAL; return 0; } static int sdw_add_element_group_count(struct sdw_group *group, - unsigned int rate) + unsigned int rate, unsigned int lane) { int num = group->count; int i; for (i = 0; i <= num; i++) { - if (rate == group->rates[i]) + if (rate == group->rates[i] && lane == group->lanes[i]) break; if (i != num) @@ -210,6 +298,7 @@ static int sdw_add_element_group_count(struct sdw_group *group, if (group->count >= group->max_size) { unsigned int *rates; + unsigned int *lanes; group->max_size += 1; rates = krealloc(group->rates, @@ -217,10 +306,20 @@ static int sdw_add_element_group_count(struct sdw_group *group, GFP_KERNEL); if (!rates) return -ENOMEM; + group->rates = rates; + + lanes = krealloc(group->lanes, + (sizeof(int) * group->max_size), + GFP_KERNEL); + if (!lanes) + return -ENOMEM; + + group->lanes = lanes; } - group->rates[group->count++] = rate; + group->rates[group->count] = rate; + group->lanes[group->count++] = lane; } return 0; @@ -230,6 +329,7 @@ static int sdw_get_group_count(struct sdw_bus *bus, struct sdw_group *group) { struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; unsigned int rate; int ret = 0; @@ -239,17 +339,32 @@ static int sdw_get_group_count(struct sdw_bus *bus, if (!group->rates) return -ENOMEM; + group->lanes = kcalloc(group->max_size, sizeof(int), GFP_KERNEL); + if (!group->lanes) { + kfree(group->rates); + group->rates = NULL; + return -ENOMEM; + } + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + if (m_rt->stream->state == SDW_STREAM_DEPREPARED) + continue; + rate = m_rt->stream->params.rate; if (m_rt == list_first_entry(&bus->m_rt_list, struct sdw_master_runtime, bus_node)) { group->rates[group->count++] = rate; - - } else { - ret = sdw_add_element_group_count(group, rate); + } + /* + * Different ports could use different lane, add group element + * even if m_rt is the first entry + */ + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + ret = sdw_add_element_group_count(group, rate, p_rt->lane); if (ret < 0) { kfree(group->rates); + kfree(group->lanes); return ret; } } @@ -262,8 +377,9 @@ static int sdw_get_group_count(struct sdw_bus *bus, * sdw_compute_port_params: Compute transport and port parameters * * @bus: SDW Bus instance + * @stream: Soundwire stream */ -static int sdw_compute_port_params(struct sdw_bus *bus) +static int sdw_compute_port_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) { struct sdw_group_params *params = NULL; struct sdw_group group; @@ -283,8 +399,7 @@ static int sdw_compute_port_params(struct sdw_bus *bus) } /* Compute transport parameters for grouped streams */ - ret = sdw_compute_group_params(bus, params, - &group.rates[0], group.count); + ret = sdw_compute_group_params(bus, stream, params, &group); if (ret < 0) goto free_params; @@ -294,6 +409,7 @@ free_params: kfree(params); out: kfree(group.rates); + kfree(group.lanes); return ret; } @@ -301,7 +417,6 @@ out: static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) { struct sdw_master_prop *prop = &bus->prop; - int frame_int, frame_freq; int r, c; for (c = 0; c < SDW_FRAME_COLS; c++) { @@ -310,11 +425,8 @@ static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) sdw_cols[c] != prop->default_col) continue; - frame_int = sdw_rows[r] * sdw_cols[c]; - frame_freq = clk_freq / frame_int; - - if ((clk_freq - (frame_freq * SDW_FRAME_CTRL_BITS)) < - bus->params.bandwidth) + if (clk_freq * (sdw_cols[c] - 1) < + bus->params.bandwidth * sdw_cols[c]) continue; bus->params.row = sdw_rows[r]; @@ -326,6 +438,95 @@ static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) return -EINVAL; } +static bool is_clock_scaling_supported(struct sdw_bus *bus) +{ + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) + if (!is_clock_scaling_supported_by_slave(s_rt->slave)) + return false; + + return true; +} + +/** + * is_lane_connected_to_all_peripherals: Check if the given manager lane connects to all peripherals + * So that all peripherals can use the manager lane. + * + * @m_rt: Manager runtime + * @lane: Lane number + */ +static bool is_lane_connected_to_all_peripherals(struct sdw_master_runtime *m_rt, unsigned int lane) +{ + struct sdw_slave_prop *slave_prop; + struct sdw_slave_runtime *s_rt; + int i; + + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + slave_prop = &s_rt->slave->prop; + for (i = 1; i < SDW_MAX_LANES; i++) { + if (slave_prop->lane_maps[i] == lane) { + dev_dbg(&s_rt->slave->dev, + "M lane %d is connected to P lane %d\n", + lane, i); + break; + } + } + if (i == SDW_MAX_LANES) { + dev_dbg(&s_rt->slave->dev, "M lane %d is not connected\n", lane); + return false; + } + } + return true; +} + +static int get_manager_lane(struct sdw_bus *bus, struct sdw_master_runtime *m_rt, + struct sdw_slave_runtime *s_rt, unsigned int curr_dr_freq) +{ + struct sdw_slave_prop *slave_prop = &s_rt->slave->prop; + struct sdw_port_runtime *m_p_rt; + unsigned int required_bandwidth; + int m_lane; + int l; + + for (l = 1; l < SDW_MAX_LANES; l++) { + if (!slave_prop->lane_maps[l]) + continue; + + required_bandwidth = 0; + list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) { + required_bandwidth += m_rt->stream->params.rate * + hweight32(m_p_rt->ch_mask) * + m_rt->stream->params.bps; + } + if (required_bandwidth <= + curr_dr_freq - bus->lane_used_bandwidth[l]) { + /* Check if m_lane is connected to all Peripherals */ + if (!is_lane_connected_to_all_peripherals(m_rt, + slave_prop->lane_maps[l])) { + dev_dbg(bus->dev, + "Not all Peripherals are connected to M lane %d\n", + slave_prop->lane_maps[l]); + continue; + } + m_lane = slave_prop->lane_maps[l]; + dev_dbg(&s_rt->slave->dev, "M lane %d is used\n", m_lane); + bus->lane_used_bandwidth[l] += required_bandwidth; + /* + * Use non-zero manager lane, subtract the lane 0 + * bandwidth that is already calculated + */ + bus->params.bandwidth -= required_bandwidth; + return m_lane; + } + } + + /* No available multi lane found, only lane 0 can be used */ + return 0; +} + /** * sdw_compute_bus_params: Compute bus parameters * @@ -333,10 +534,16 @@ static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq) */ static int sdw_compute_bus_params(struct sdw_bus *bus) { - unsigned int curr_dr_freq = 0; struct sdw_master_prop *mstr_prop = &bus->prop; - int i, clk_values, ret; + struct sdw_slave_prop *slave_prop; + struct sdw_port_runtime *m_p_rt; + struct sdw_port_runtime *s_p_rt; + struct sdw_master_runtime *m_rt; + struct sdw_slave_runtime *s_rt; + unsigned int curr_dr_freq = 0; + int i, l, clk_values, ret; bool is_gear = false; + int m_lane = 0; u32 *clk_buf; if (mstr_prop->num_clk_gears) { @@ -351,6 +558,10 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) clk_buf = NULL; } + /* If dynamic scaling is not supported, don't try higher freq */ + if (!is_clock_scaling_supported(bus)) + clk_values = 1; + for (i = 0; i < clk_values; i++) { if (!clk_buf) curr_dr_freq = bus->params.max_dr_freq; @@ -359,10 +570,26 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) (bus->params.max_dr_freq >> clk_buf[i]) : clk_buf[i] * SDW_DOUBLE_RATE_FACTOR; - if (curr_dr_freq <= bus->params.bandwidth) - continue; + if (curr_dr_freq * (mstr_prop->default_col - 1) >= + bus->params.bandwidth * mstr_prop->default_col) + break; + + list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { + /* + * Get the first s_rt that will be used to find the available lane that + * can be used. No need to check all Peripherals because we can't use + * multi-lane if we can't find any available lane for the first Peripheral. + */ + s_rt = list_first_entry(&m_rt->slave_rt_list, + struct sdw_slave_runtime, m_rt_node); - break; + /* + * Find the available Manager lane that connected to the first Peripheral. + */ + m_lane = get_manager_lane(bus, m_rt, s_rt, curr_dr_freq); + if (m_lane > 0) + goto out; + } /* * TODO: Check all the Slave(s) port(s) audio modes and find @@ -376,6 +603,38 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) __func__, bus->params.bandwidth); return -EINVAL; } +out: + /* multilane can be used */ + if (m_lane > 0) { + /* Set Peripheral lanes */ + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { + slave_prop = &s_rt->slave->prop; + for (l = 1; l < SDW_MAX_LANES; l++) { + if (slave_prop->lane_maps[l] == m_lane) { + list_for_each_entry(s_p_rt, &s_rt->port_list, port_node) { + s_p_rt->lane = l; + dev_dbg(&s_rt->slave->dev, + "Set P lane %d for port %d\n", + l, s_p_rt->num); + } + break; + } + } + } + /* + * Set Manager lanes. Configure the last m_rt in bus->m_rt_list only since + * we don't want to touch other m_rts that are already working. + */ + list_for_each_entry(m_p_rt, &m_rt->port_list, port_node) { + m_p_rt->lane = m_lane; + } + } + + if (!mstr_prop->default_frame_rate || !mstr_prop->default_row) + return -EINVAL; + + mstr_prop->default_col = curr_dr_freq / mstr_prop->default_frame_rate / + mstr_prop->default_row; ret = sdw_select_row_col(bus, curr_dr_freq); if (ret < 0) { @@ -392,8 +651,9 @@ static int sdw_compute_bus_params(struct sdw_bus *bus) * sdw_compute_params: Compute bus, transport and port parameters * * @bus: SDW Bus instance + * @stream: Soundwire stream */ -int sdw_compute_params(struct sdw_bus *bus) +int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) { int ret; @@ -402,8 +662,13 @@ int sdw_compute_params(struct sdw_bus *bus) if (ret < 0) return ret; + if (stream->type == SDW_STREAM_BPT) { + sdw_compute_dp0_port_params(bus); + return 0; + } + /* Compute transport and port params */ - ret = sdw_compute_port_params(bus); + ret = sdw_compute_port_params(bus, stream); if (ret < 0) { dev_err(bus->dev, "Compute transport params failed: %d\n", ret); return ret; diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 1287a325c435..9db78f3d7615 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -6,6 +6,7 @@ */ #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/debugfs.h> #include <linux/delay.h> #include <linux/io.h> @@ -73,12 +74,11 @@ static int intel_reg_show(struct seq_file *s_file, void *data) struct sdw_intel *sdw = s_file->private; void __iomem *s = sdw->link_res->shim; void __iomem *a = sdw->link_res->alh; - char *buf; ssize_t ret; int i, j; unsigned int links, reg; - buf = kzalloc(RD_BUF, GFP_KERNEL); + char *buf __free(kfree) = kzalloc(RD_BUF, GFP_KERNEL); if (!buf) return -ENOMEM; @@ -129,7 +129,6 @@ static int intel_reg_show(struct seq_file *s_file, void *data) ret += intel_sprintf(a, true, buf, ret, SDW_ALH_STRMZCFG(i)); seq_printf(s_file, "%s", buf); - kfree(buf); return 0; } @@ -345,8 +344,10 @@ static int intel_link_power_up(struct sdw_intel *sdw) u32 spa_mask, cpa_mask; u32 link_control; int ret = 0; + u32 clock_source; u32 syncprd; u32 sync_reg; + bool lcap_mlcs; mutex_lock(sdw->link_res->shim_lock); @@ -358,12 +359,35 @@ static int intel_link_power_up(struct sdw_intel *sdw) * is only dependent on the oscillator clock provided to * the IP, so adjust based on _DSD properties reported in DSDT * tables. The values reported are based on either 24MHz - * (CNL/CML) or 38.4 MHz (ICL/TGL+). + * (CNL/CML) or 38.4 MHz (ICL/TGL+). On MeteorLake additional + * frequencies are available with the MLCS clock source selection. */ - if (prop->mclk_freq % 6000000) - syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; - else - syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; + lcap_mlcs = intel_readl(shim, SDW_SHIM_LCAP) & SDW_SHIM_LCAP_MLCS_MASK; + + if (prop->mclk_freq % 6000000) { + if (prop->mclk_freq % 2400000) { + if (lcap_mlcs) { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576; + clock_source = SDW_SHIM_MLCS_CARDINAL_CLK; + } else { + dev_err(sdw->cdns.dev, "%s: invalid clock configuration, mclk %d lcap_mlcs %d\n", + __func__, prop->mclk_freq, lcap_mlcs); + ret = -EINVAL; + goto out; + } + } else { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; + clock_source = SDW_SHIM_MLCS_XTAL_CLK; + } + } else { + if (lcap_mlcs) { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96; + clock_source = SDW_SHIM_MLCS_AUDIO_PLL_CLK; + } else { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; + clock_source = SDW_SHIM_MLCS_XTAL_CLK; + } + } if (!*shim_mask) { dev_dbg(sdw->cdns.dev, "powering up all links\n"); @@ -403,6 +427,13 @@ static int intel_link_power_up(struct sdw_intel *sdw) "Failed to set SHIM_SYNC: %d\n", ret); goto out; } + + /* update link clock if needed */ + if (lcap_mlcs) { + link_control = intel_readl(shim, SDW_SHIM_LCTL); + u32p_replace_bits(&link_control, clock_source, SDW_SHIM_LCTL_MLCS_MASK); + intel_writel(shim, SDW_SHIM_LCTL, link_control); + } } *shim_mask |= BIT(link_id); @@ -668,6 +699,24 @@ static int intel_params_stream(struct sdw_intel *sdw, * DAI routines */ +static int intel_free_stream(struct sdw_intel *sdw, + struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + int link_id) +{ + struct sdw_intel_link_res *res = sdw->link_res; + struct sdw_intel_stream_free_data free_data; + + free_data.substream = substream; + free_data.dai = dai; + free_data.link_id = link_id; + + if (res->ops && res->ops->free_stream && res->dev) + return res->ops->free_stream(res->dev, &free_data); + + return 0; +} + static int intel_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -677,7 +726,6 @@ static int intel_hw_params(struct snd_pcm_substream *substream, struct sdw_cdns_dai_runtime *dai_runtime; struct sdw_cdns_pdi *pdi; struct sdw_stream_config sconfig; - struct sdw_port_config *pconfig; int ch, dir; int ret; @@ -693,10 +741,8 @@ static int intel_hw_params(struct snd_pcm_substream *substream, pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); - if (!pdi) { - ret = -EINVAL; - goto error; - } + if (!pdi) + return -EINVAL; /* do run-time configurations for SHIM, ALH and PDI/PORT */ intel_pdi_shim_configure(sdw, pdi); @@ -713,7 +759,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream, sdw->instance, pdi->intel_alh_id); if (ret) - goto error; + return ret; sconfig.direction = dir; sconfig.ch_count = ch; @@ -723,11 +769,10 @@ static int intel_hw_params(struct snd_pcm_substream *substream, sconfig.bps = snd_pcm_format_width(params_format(params)); /* Port configuration */ - pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); - if (!pconfig) { - ret = -ENOMEM; - goto error; - } + struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig), + GFP_KERNEL); + if (!pconfig) + return -ENOMEM; pconfig->num = pdi->num; pconfig->ch_mask = (1 << ch) - 1; @@ -737,8 +782,6 @@ static int intel_hw_params(struct snd_pcm_substream *substream, if (ret) dev_err(cdns->dev, "add master to stream failed:%d\n", ret); - kfree(pconfig); -error: return ret; } @@ -799,6 +842,7 @@ static int intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); + struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dai_runtime *dai_runtime; int ret; @@ -819,6 +863,12 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) return ret; } + ret = intel_free_stream(sdw, substream, dai, sdw->instance); + if (ret < 0) { + dev_err(dai->dev, "intel_free_stream: failed %d\n", ret); + return ret; + } + dai_runtime->pdi = NULL; return 0; @@ -1061,5 +1111,4 @@ const struct sdw_intel_hw_ops sdw_intel_cnl_hw_ops = { .sync_go = intel_shim_sync_go, .sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked, }; -EXPORT_SYMBOL_NS(sdw_intel_cnl_hw_ops, SOUNDWIRE_INTEL); - +EXPORT_SYMBOL_NS(sdw_intel_cnl_hw_ops, "SOUNDWIRE_INTEL"); diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index 511932c55216..d44e70d3c4e3 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -48,16 +48,51 @@ struct sdw_intel_link_res { struct hdac_bus *hbus; }; +/** + * struct sdw_intel_bpt - SoundWire Intel BPT context + * @bpt_tx_stream: BPT TX stream + * @dmab_tx_bdl: BPT TX buffer descriptor list + * @bpt_rx_stream: BPT RX stream + * @dmab_rx_bdl: BPT RX buffer descriptor list + * @pdi0_buffer_size: PDI0 buffer size + * @pdi1_buffer_size: PDI1 buffer size + * @num_frames: number of frames + * @data_per_frame: data per frame + */ +struct sdw_intel_bpt { + struct hdac_ext_stream *bpt_tx_stream; + struct snd_dma_buffer dmab_tx_bdl; + struct hdac_ext_stream *bpt_rx_stream; + struct snd_dma_buffer dmab_rx_bdl; + unsigned int pdi0_buffer_size; + unsigned int pdi1_buffer_size; + unsigned int num_frames; + unsigned int data_per_frame; +}; + struct sdw_intel { struct sdw_cdns cdns; int instance; struct sdw_intel_link_res *link_res; bool startup_done; + struct sdw_intel_bpt bpt_ctx; #ifdef CONFIG_DEBUG_FS struct dentry *debugfs; #endif }; +struct sdw_intel_prop { + u16 clde; + u16 doaise2; + u16 dodse2; + u16 clds; + u16 clss; + u16 doaise; + u16 doais; + u16 dodse; + u16 dods; +}; + enum intel_pdi_type { INTEL_PDI_IN = 0, INTEL_PDI_OUT = 1, @@ -91,6 +126,8 @@ static inline void intel_writew(void __iomem *base, int offset, u16 value) #define INTEL_MASTER_RESET_ITERATIONS 10 +#define SDW_INTEL_DELAYED_ENUMERATION_MS 100 + #define SDW_INTEL_CHECK_OPS(sdw, cb) ((sdw) && (sdw)->link_res && (sdw)->link_res->hw_ops && \ (sdw)->link_res->hw_ops->cb) #define SDW_INTEL_OPS(sdw, cb) ((sdw)->link_res->hw_ops->cb) @@ -210,6 +247,13 @@ static inline bool sdw_intel_sync_check_cmdsync_unlocked(struct sdw_intel *sdw) return false; } +static inline int sdw_intel_get_link_count(struct sdw_intel *sdw) +{ + if (SDW_INTEL_CHECK_OPS(sdw, get_link_count)) + return SDW_INTEL_OPS(sdw, get_link_count)(sdw); + return 4; /* default on older generations */ +} + /* common bus management */ int intel_start_bus(struct sdw_intel *sdw); int intel_start_bus_after_reset(struct sdw_intel *sdw); diff --git a/drivers/soundwire/intel_ace2x.c b/drivers/soundwire/intel_ace2x.c index 8280baa3254b..5b31e1f69591 100644 --- a/drivers/soundwire/intel_ace2x.c +++ b/drivers/soundwire/intel_ace2x.c @@ -1,21 +1,332 @@ // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) -// Copyright(c) 2023 Intel Corporation. All rights reserved. +// Copyright(c) 2023 Intel Corporation /* * Soundwire Intel ops for LunarLake */ #include <linux/acpi.h> +#include <linux/cleanup.h> #include <linux/device.h> #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_intel.h> -#include <sound/pcm_params.h> +#include <sound/hdaudio.h> #include <sound/hda-mlink.h> +#include <sound/hda-sdw-bpt.h> +#include <sound/hda_register.h> +#include <sound/pcm_params.h> #include "cadence_master.h" #include "bus.h" #include "intel.h" +static int sdw_slave_bpt_stream_add(struct sdw_slave *slave, struct sdw_stream_runtime *stream) +{ + struct sdw_stream_config sconfig = {0}; + struct sdw_port_config pconfig = {0}; + int ret; + + /* arbitrary configuration */ + sconfig.frame_rate = 16000; + sconfig.ch_count = 1; + sconfig.bps = 32; /* this is required for BPT/BRA */ + sconfig.direction = SDW_DATA_DIR_RX; + sconfig.type = SDW_STREAM_BPT; + + pconfig.num = 0; + pconfig.ch_mask = BIT(0); + + ret = sdw_stream_add_slave(slave, &sconfig, &pconfig, 1, stream); + if (ret) + dev_err(&slave->dev, "%s: failed: %d\n", __func__, ret); + + return ret; +} + +static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave *slave, + struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = &sdw->cdns; + struct sdw_bus *bus = &cdns->bus; + struct sdw_master_prop *prop = &bus->prop; + struct sdw_stream_runtime *stream; + struct sdw_stream_config sconfig; + struct sdw_port_config *pconfig; + unsigned int pdi0_buffer_size; + unsigned int tx_dma_bandwidth; + unsigned int pdi1_buffer_size; + unsigned int rx_dma_bandwidth; + unsigned int data_per_frame; + unsigned int tx_total_bytes; + struct sdw_cdns_pdi *pdi0; + struct sdw_cdns_pdi *pdi1; + unsigned int num_frames; + int command; + int ret1; + int ret; + int dir; + int i; + + stream = sdw_alloc_stream("BPT", SDW_STREAM_BPT); + if (!stream) + return -ENOMEM; + + cdns->bus.bpt_stream = stream; + + ret = sdw_slave_bpt_stream_add(slave, stream); + if (ret < 0) + goto release_stream; + + /* handle PDI0 first */ + dir = SDW_DATA_DIR_TX; + + pdi0 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 0); + if (!pdi0) { + dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi0 failed\n", __func__); + ret = -EINVAL; + goto remove_slave; + } + + sdw_cdns_config_stream(cdns, 1, dir, pdi0); + + /* handle PDI1 */ + dir = SDW_DATA_DIR_RX; + + pdi1 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 1); + if (!pdi1) { + dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi1 failed\n", __func__); + ret = -EINVAL; + goto remove_slave; + } + + sdw_cdns_config_stream(cdns, 1, dir, pdi1); + + /* + * the port config direction, number of channels and frame + * rate is totally arbitrary + */ + sconfig.direction = dir; + sconfig.ch_count = 1; + sconfig.frame_rate = 16000; + sconfig.type = SDW_STREAM_BPT; + sconfig.bps = 32; /* this is required for BPT/BRA */ + + /* Port configuration */ + pconfig = kcalloc(2, sizeof(*pconfig), GFP_KERNEL); + if (!pconfig) { + ret = -ENOMEM; + goto remove_slave; + } + + for (i = 0; i < 2 /* num_pdi */; i++) { + pconfig[i].num = i; + pconfig[i].ch_mask = 1; + } + + ret = sdw_stream_add_master(&cdns->bus, &sconfig, pconfig, 2, stream); + kfree(pconfig); + + if (ret < 0) { + dev_err(cdns->dev, "add master to stream failed:%d\n", ret); + goto remove_slave; + } + + ret = sdw_prepare_stream(cdns->bus.bpt_stream); + if (ret < 0) + goto remove_master; + + command = (msg->flags & SDW_MSG_FLAG_WRITE) ? 0 : 1; + + ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row, cdns->bus.params.col, + msg->len, SDW_BPT_MSG_MAX_BYTES, &data_per_frame, + &pdi0_buffer_size, &pdi1_buffer_size, &num_frames); + if (ret < 0) + goto deprepare_stream; + + sdw->bpt_ctx.pdi0_buffer_size = pdi0_buffer_size; + sdw->bpt_ctx.pdi1_buffer_size = pdi1_buffer_size; + sdw->bpt_ctx.num_frames = num_frames; + sdw->bpt_ctx.data_per_frame = data_per_frame; + tx_dma_bandwidth = div_u64((u64)pdi0_buffer_size * 8 * (u64)prop->default_frame_rate, + num_frames); + rx_dma_bandwidth = div_u64((u64)pdi1_buffer_size * 8 * (u64)prop->default_frame_rate, + num_frames); + + dev_dbg(cdns->dev, "Message len %d transferred in %d frames (%d per frame)\n", + msg->len, num_frames, data_per_frame); + dev_dbg(cdns->dev, "sizes pdi0 %d pdi1 %d tx_bandwidth %d rx_bandwidth %d\n", + pdi0_buffer_size, pdi1_buffer_size, tx_dma_bandwidth, rx_dma_bandwidth); + + ret = hda_sdw_bpt_open(cdns->dev->parent, /* PCI device */ + sdw->instance, &sdw->bpt_ctx.bpt_tx_stream, + &sdw->bpt_ctx.dmab_tx_bdl, pdi0_buffer_size, tx_dma_bandwidth, + &sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl, + pdi1_buffer_size, rx_dma_bandwidth); + if (ret < 0) { + dev_err(cdns->dev, "%s: hda_sdw_bpt_open failed %d\n", __func__, ret); + goto deprepare_stream; + } + + if (!command) { + ret = sdw_cdns_prepare_write_dma_buffer(msg->dev_num, msg->addr, msg->buf, + msg->len, data_per_frame, + sdw->bpt_ctx.dmab_tx_bdl.area, + pdi0_buffer_size, &tx_total_bytes); + } else { + ret = sdw_cdns_prepare_read_dma_buffer(msg->dev_num, msg->addr, msg->len, + data_per_frame, + sdw->bpt_ctx.dmab_tx_bdl.area, + pdi0_buffer_size, &tx_total_bytes); + } + + if (!ret) + return 0; + + dev_err(cdns->dev, "%s: sdw_prepare_%s_dma_buffer failed %d\n", + __func__, command ? "read" : "write", ret); + + ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */ + sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl, + sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl); + if (ret1 < 0) + dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n", + __func__, ret1); + +deprepare_stream: + sdw_deprepare_stream(cdns->bus.bpt_stream); + +remove_master: + ret1 = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream); + if (ret1 < 0) + dev_err(cdns->dev, "%s: remove master failed: %d\n", + __func__, ret1); + +remove_slave: + ret1 = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream); + if (ret1 < 0) + dev_err(cdns->dev, "%s: remove slave failed: %d\n", + __func__, ret1); + +release_stream: + sdw_release_stream(cdns->bus.bpt_stream); + cdns->bus.bpt_stream = NULL; + + return ret; +} + +static void intel_ace2x_bpt_close_stream(struct sdw_intel *sdw, struct sdw_slave *slave, + struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = &sdw->cdns; + int ret; + + ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->bpt_ctx.bpt_tx_stream, + &sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream, + &sdw->bpt_ctx.dmab_rx_bdl); + if (ret < 0) + dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n", + __func__, ret); + + ret = sdw_deprepare_stream(cdns->bus.bpt_stream); + if (ret < 0) + dev_err(cdns->dev, "%s: sdw_deprepare_stream failed: ret %d\n", + __func__, ret); + + ret = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream); + if (ret < 0) + dev_err(cdns->dev, "%s: remove master failed: %d\n", + __func__, ret); + + ret = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream); + if (ret < 0) + dev_err(cdns->dev, "%s: remove slave failed: %d\n", + __func__, ret); + + cdns->bus.bpt_stream = NULL; +} + +#define INTEL_BPT_MSG_BYTE_ALIGNMENT 32 + +static int intel_ace2x_bpt_send_async(struct sdw_intel *sdw, struct sdw_slave *slave, + struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = &sdw->cdns; + int ret; + + if (msg->len % INTEL_BPT_MSG_BYTE_ALIGNMENT) { + dev_err(cdns->dev, "BPT message length %d is not a multiple of %d bytes\n", + msg->len, INTEL_BPT_MSG_BYTE_ALIGNMENT); + return -EINVAL; + } + + dev_dbg(cdns->dev, "BPT Transfer start\n"); + + ret = intel_ace2x_bpt_open_stream(sdw, slave, msg); + if (ret < 0) + return ret; + + ret = hda_sdw_bpt_send_async(cdns->dev->parent, /* PCI device */ + sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream); + if (ret < 0) { + dev_err(cdns->dev, "%s: hda_sdw_bpt_send_async failed: %d\n", + __func__, ret); + + intel_ace2x_bpt_close_stream(sdw, slave, msg); + + return ret; + } + + ret = sdw_enable_stream(cdns->bus.bpt_stream); + if (ret < 0) { + dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n", + __func__, ret); + intel_ace2x_bpt_close_stream(sdw, slave, msg); + } + + return ret; +} + +static int intel_ace2x_bpt_wait(struct sdw_intel *sdw, struct sdw_slave *slave, + struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = &sdw->cdns; + int ret; + + dev_dbg(cdns->dev, "BPT Transfer wait\n"); + + ret = hda_sdw_bpt_wait(cdns->dev->parent, /* PCI device */ + sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream); + if (ret < 0) + dev_err(cdns->dev, "%s: hda_sdw_bpt_wait failed: %d\n", __func__, ret); + + ret = sdw_disable_stream(cdns->bus.bpt_stream); + if (ret < 0) { + dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n", + __func__, ret); + goto err; + } + + if (msg->flags & SDW_MSG_FLAG_WRITE) { + ret = sdw_cdns_check_write_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area, + sdw->bpt_ctx.pdi1_buffer_size, + sdw->bpt_ctx.num_frames); + if (ret < 0) + dev_err(cdns->dev, "%s: BPT Write failed %d\n", __func__, ret); + } else { + ret = sdw_cdns_check_read_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area, + sdw->bpt_ctx.pdi1_buffer_size, + msg->buf, msg->len, sdw->bpt_ctx.num_frames, + sdw->bpt_ctx.data_per_frame); + if (ret < 0) + dev_err(cdns->dev, "%s: BPT Read failed %d\n", __func__, ret); + } + +err: + intel_ace2x_bpt_close_stream(sdw, slave, msg); + + return ret; +} + /* * shim vendor-specific (vs) ops */ @@ -23,49 +334,101 @@ static void intel_shim_vs_init(struct sdw_intel *sdw) { void __iomem *shim_vs = sdw->link_res->shim_vs; + struct sdw_bus *bus = &sdw->cdns.bus; + struct sdw_intel_prop *intel_prop; + u16 clde; + u16 doaise2; + u16 dodse2; + u16 clds; + u16 clss; + u16 doaise; + u16 doais; + u16 dodse; + u16 dods; u16 act; + intel_prop = bus->vendor_specific_prop; + clde = intel_prop->clde; + doaise2 = intel_prop->doaise2; + dodse2 = intel_prop->dodse2; + clds = intel_prop->clds; + clss = intel_prop->clss; + doaise = intel_prop->doaise; + doais = intel_prop->doais; + dodse = intel_prop->dodse; + dods = intel_prop->dods; + act = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL); - u16p_replace_bits(&act, 0x1, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS); + u16p_replace_bits(&act, clde, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDE); + u16p_replace_bits(&act, doaise2, SDW_SHIM3_INTEL_VS_ACTMCTL_DOAISE2); + u16p_replace_bits(&act, dodse2, SDW_SHIM3_INTEL_VS_ACTMCTL_DODSE2); + u16p_replace_bits(&act, clds, SDW_SHIM3_INTEL_VS_ACTMCTL_CLDS); + u16p_replace_bits(&act, clss, SDW_SHIM3_INTEL_VS_ACTMCTL_CLSS); + u16p_replace_bits(&act, doaise, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAISE); + u16p_replace_bits(&act, doais, SDW_SHIM2_INTEL_VS_ACTMCTL_DOAIS); + u16p_replace_bits(&act, dodse, SDW_SHIM2_INTEL_VS_ACTMCTL_DODSE); + u16p_replace_bits(&act, dods, SDW_SHIM2_INTEL_VS_ACTMCTL_DODS); act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DACTQE; - act |= SDW_SHIM2_INTEL_VS_ACTMCTL_DODS; intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_ACTMCTL, act); usleep_range(10, 15); } -static int intel_shim_check_wake(struct sdw_intel *sdw) +static void intel_shim_vs_set_clock_source(struct sdw_intel *sdw, u32 source) { - void __iomem *shim_vs; - u16 wake_sts; + void __iomem *shim_vs = sdw->link_res->shim_vs; + u32 val; + + val = intel_readl(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL); + + u32p_replace_bits(&val, source, SDW_SHIM2_INTEL_VS_LVSCTL_MLCS); + + intel_writel(shim_vs, SDW_SHIM2_INTEL_VS_LVSCTL, val); + + dev_dbg(sdw->cdns.dev, "clock source %d LVSCTL %#x\n", source, val); +} - shim_vs = sdw->link_res->shim_vs; - wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS); +static int intel_shim_check_wake(struct sdw_intel *sdw) +{ + /* + * We follow the HDaudio example and resume unconditionally + * without checking the WAKESTS bit for that specific link + */ - return wake_sts & SDW_SHIM2_INTEL_VS_WAKEEN_PWS; + return 1; } static void intel_shim_wake(struct sdw_intel *sdw, bool wake_enable) { - void __iomem *shim_vs = sdw->link_res->shim_vs; + u16 lsdiid = 0; u16 wake_en; u16 wake_sts; + int ret; - wake_en = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN); + mutex_lock(sdw->link_res->shim_lock); + + ret = hdac_bus_eml_sdw_get_lsdiid_unlocked(sdw->link_res->hbus, sdw->instance, &lsdiid); + if (ret < 0) + goto unlock; + + wake_en = snd_hdac_chip_readw(sdw->link_res->hbus, WAKEEN); if (wake_enable) { /* Enable the wakeup */ - wake_en |= SDW_SHIM2_INTEL_VS_WAKEEN_PWE; - intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en); + wake_en |= lsdiid; + + snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en); } else { /* Disable the wake up interrupt */ - wake_en &= ~SDW_SHIM2_INTEL_VS_WAKEEN_PWE; - intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKEEN, wake_en); + wake_en &= ~lsdiid; + snd_hdac_chip_writew(sdw->link_res->hbus, WAKEEN, wake_en); /* Clear wake status (W1C) */ - wake_sts = intel_readw(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS); - wake_sts |= SDW_SHIM2_INTEL_VS_WAKEEN_PWS; - intel_writew(shim_vs, SDW_SHIM2_INTEL_VS_WAKESTS, wake_sts); + wake_sts = snd_hdac_chip_readw(sdw->link_res->hbus, STATESTS); + wake_sts |= lsdiid; + snd_hdac_chip_writew(sdw->link_res->hbus, STATESTS, wake_sts); } +unlock: + mutex_unlock(sdw->link_res->shim_lock); } static int intel_link_power_up(struct sdw_intel *sdw) @@ -74,36 +437,45 @@ static int intel_link_power_up(struct sdw_intel *sdw) struct sdw_master_prop *prop = &bus->prop; u32 *shim_mask = sdw->link_res->shim_mask; unsigned int link_id = sdw->instance; + u32 clock_source; u32 syncprd; int ret; + if (prop->mclk_freq % 6000000) { + if (prop->mclk_freq % 2400000) { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24_576; + clock_source = SDW_SHIM2_MLCS_CARDINAL_CLK; + } else { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; + clock_source = SDW_SHIM2_MLCS_XTAL_CLK; + } + } else { + syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_96; + clock_source = SDW_SHIM2_MLCS_AUDIO_PLL_CLK; + } + mutex_lock(sdw->link_res->shim_lock); + ret = hdac_bus_eml_sdw_power_up_unlocked(sdw->link_res->hbus, link_id); + if (ret < 0) { + dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_up failed: %d\n", + __func__, ret); + goto out; + } + + intel_shim_vs_set_clock_source(sdw, clock_source); + if (!*shim_mask) { /* we first need to program the SyncPRD/CPU registers */ dev_dbg(sdw->cdns.dev, "first link up, programming SYNCPRD\n"); - if (prop->mclk_freq % 6000000) - syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_38_4; - else - syncprd = SDW_SHIM_SYNC_SYNCPRD_VAL_24; - ret = hdac_bus_eml_sdw_set_syncprd_unlocked(sdw->link_res->hbus, syncprd); if (ret < 0) { dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_set_syncprd failed: %d\n", __func__, ret); goto out; } - } - ret = hdac_bus_eml_sdw_power_up_unlocked(sdw->link_res->hbus, link_id); - if (ret < 0) { - dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_up failed: %d\n", - __func__, ret); - goto out; - } - - if (!*shim_mask) { /* SYNCPU will change once link is active */ ret = hdac_bus_eml_sdw_wait_syncpu_unlocked(sdw->link_res->hbus); if (ret < 0) { @@ -111,6 +483,9 @@ static int intel_link_power_up(struct sdw_intel *sdw) __func__, ret); goto out; } + + hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true, + AZX_REG_ML_LEPTR_ID_SDW, true); } *shim_mask |= BIT(link_id); @@ -137,6 +512,10 @@ static int intel_link_power_down(struct sdw_intel *sdw) *shim_mask &= ~BIT(link_id); + if (!*shim_mask) + hdac_bus_eml_enable_interrupt_unlocked(sdw->link_res->hbus, true, + AZX_REG_ML_LEPTR_ID_SDW, false); + ret = hdac_bus_eml_sdw_power_down_unlocked(sdw->link_res->hbus, link_id); if (ret < 0) { dev_err(sdw->cdns.dev, "%s: hdac_bus_eml_sdw_power_down failed: %d\n", @@ -247,7 +626,6 @@ static int intel_hw_params(struct snd_pcm_substream *substream, struct sdw_cdns_dai_runtime *dai_runtime; struct sdw_cdns_pdi *pdi; struct sdw_stream_config sconfig; - struct sdw_port_config *pconfig; int ch, dir; int ret; @@ -262,11 +640,13 @@ static int intel_hw_params(struct snd_pcm_substream *substream, dir = SDW_DATA_DIR_TX; pdi = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, ch, dir, dai->id); + if (!pdi) + return -EINVAL; - if (!pdi) { - ret = -EINVAL; - goto error; - } + /* use same definitions for alh_id as previous generations */ + pdi->intel_alh_id = (sdw->instance * 16) + pdi->num + 3; + if (pdi->num >= 2) + pdi->intel_alh_id += 2; /* the SHIM will be configured in the callback functions */ @@ -282,7 +662,7 @@ static int intel_hw_params(struct snd_pcm_substream *substream, sdw->instance, pdi->intel_alh_id); if (ret) - goto error; + return ret; sconfig.direction = dir; sconfig.ch_count = ch; @@ -292,11 +672,10 @@ static int intel_hw_params(struct snd_pcm_substream *substream, sconfig.bps = snd_pcm_format_width(params_format(params)); /* Port configuration */ - pconfig = kzalloc(sizeof(*pconfig), GFP_KERNEL); - if (!pconfig) { - ret = -ENOMEM; - goto error; - } + struct sdw_port_config *pconfig __free(kfree) = kzalloc(sizeof(*pconfig), + GFP_KERNEL); + if (!pconfig) + return -ENOMEM; pconfig->num = pdi->num; pconfig->ch_mask = (1 << ch) - 1; @@ -306,19 +685,18 @@ static int intel_hw_params(struct snd_pcm_substream *substream, if (ret) dev_err(cdns->dev, "add master to stream failed:%d\n", ret); - kfree(pconfig); -error: return ret; } static int intel_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai); struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_cdns_dai_runtime *dai_runtime; + struct snd_pcm_hw_params *hw_params; int ch, dir; - int ret = 0; dai_runtime = cdns->dai_runtime_array[dai->id]; if (!dai_runtime) { @@ -327,12 +705,8 @@ static int intel_prepare(struct snd_pcm_substream *substream, return -EIO; } + hw_params = &rtd->dpcm[substream->stream].hw_params; if (dai_runtime->suspended) { - struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); - struct snd_pcm_hw_params *hw_params; - - hw_params = &rtd->dpcm[substream->stream].hw_params; - dai_runtime->suspended = false; /* @@ -353,15 +727,11 @@ static int intel_prepare(struct snd_pcm_substream *substream, /* the SHIM will be configured in the callback functions */ sdw_cdns_config_stream(cdns, ch, dir, dai_runtime->pdi); - - /* Inform DSP about PDI stream number */ - ret = intel_params_stream(sdw, substream, dai, - hw_params, - sdw->instance, - dai_runtime->pdi->intel_alh_id); } - return ret; + /* Inform DSP about PDI stream number */ + return intel_params_stream(sdw, substream, dai, hw_params, sdw->instance, + dai_runtime->pdi->intel_alh_id); } static int @@ -644,10 +1014,30 @@ static void intel_program_sdi(struct sdw_intel *sdw, int dev_num) __func__, sdw->instance, dev_num); } +static int intel_get_link_count(struct sdw_intel *sdw) +{ + int ret; + + ret = hdac_bus_eml_get_count(sdw->link_res->hbus, true, AZX_REG_ML_LEPTR_ID_SDW); + if (!ret) { + dev_err(sdw->cdns.dev, "%s: could not retrieve link count\n", __func__); + return -ENODEV; + } + + if (ret > SDW_INTEL_MAX_LINKS) { + dev_err(sdw->cdns.dev, "%s: link count %d exceed max %d\n", __func__, ret, SDW_INTEL_MAX_LINKS); + return -EINVAL; + } + + return ret; +} + const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = { .debugfs_init = intel_ace2x_debugfs_init, .debugfs_exit = intel_ace2x_debugfs_exit, + .get_link_count = intel_get_link_count, + .register_dai = intel_register_dai, .check_clock_stop = intel_check_clock_stop, @@ -671,7 +1061,11 @@ const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = { .sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked, .program_sdi = intel_program_sdi, + + .bpt_send_async = intel_ace2x_bpt_send_async, + .bpt_wait = intel_ace2x_bpt_wait, }; -EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, SOUNDWIRE_INTEL); +EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, "SOUNDWIRE_INTEL"); -MODULE_IMPORT_NS(SND_SOC_SOF_HDA_MLINK); +MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK"); +MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_SDW_BPT"); diff --git a/drivers/soundwire/intel_ace2x_debugfs.c b/drivers/soundwire/intel_ace2x_debugfs.c index 3d24661ffd37..206a8d511ebd 100644 --- a/drivers/soundwire/intel_ace2x_debugfs.c +++ b/drivers/soundwire/intel_ace2x_debugfs.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0-only -// Copyright(c) 2023 Intel Corporation. All rights reserved. +// Copyright(c) 2023 Intel Corporation #include <linux/acpi.h> #include <linux/debugfs.h> diff --git a/drivers/soundwire/intel_auxdevice.c b/drivers/soundwire/intel_auxdevice.c index 95125cc2fc59..10a602d4843a 100644 --- a/drivers/soundwire/intel_auxdevice.c +++ b/drivers/soundwire/intel_auxdevice.c @@ -41,12 +41,17 @@ static int md_flags; module_param_named(sdw_md_flags, md_flags, int, 0444); MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)"); +static int mclk_divider; +module_param_named(sdw_mclk_divider, mclk_divider, int, 0444); +MODULE_PARM_DESC(sdw_mclk_divider, "SoundWire Intel mclk divider"); + struct wake_capable_part { const u16 mfg_id; const u16 part_id; }; static struct wake_capable_part wake_capable_list[] = { + {0x01fa, 0x4243}, {0x025d, 0x5682}, {0x025d, 0x700}, {0x025d, 0x711}, @@ -74,6 +79,27 @@ static bool is_wake_capable(struct sdw_slave *slave) return false; } +static int generic_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, + struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_intel *sdw = cdns_to_intel(cdns); + + if (sdw->link_res->hw_ops->bpt_send_async) + return sdw->link_res->hw_ops->bpt_send_async(sdw, slave, msg); + return -EOPNOTSUPP; +} + +static int generic_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg) +{ + struct sdw_cdns *cdns = bus_to_cdns(bus); + struct sdw_intel *sdw = cdns_to_intel(cdns); + + if (sdw->link_res->hw_ops->bpt_wait) + return sdw->link_res->hw_ops->bpt_wait(sdw, slave, msg); + return -EOPNOTSUPP; +} + static int generic_pre_bank_switch(struct sdw_bus *bus) { struct sdw_cdns *cdns = bus_to_cdns(bus); @@ -122,6 +148,7 @@ static void generic_new_peripheral_assigned(struct sdw_bus *bus, static int sdw_master_read_intel_prop(struct sdw_bus *bus) { struct sdw_master_prop *prop = &bus->prop; + struct sdw_intel_prop *intel_prop; struct fwnode_handle *link; char name[32]; u32 quirk_mask; @@ -140,8 +167,12 @@ static int sdw_master_read_intel_prop(struct sdw_bus *bus) "intel-sdw-ip-clock", &prop->mclk_freq); - /* the values reported by BIOS are the 2x clock, not the bus clock */ - prop->mclk_freq /= 2; + if (mclk_divider) + /* use kernel parameter for BIOS or board work-arounds */ + prop->mclk_freq /= mclk_divider; + else + /* the values reported by BIOS are the 2x clock, not the bus clock */ + prop->mclk_freq /= 2; fwnode_property_read_u32(link, "intel-quirk-mask", @@ -153,6 +184,60 @@ static int sdw_master_read_intel_prop(struct sdw_bus *bus) prop->quirks = SDW_MASTER_QUIRKS_CLEAR_INITIAL_CLASH | SDW_MASTER_QUIRKS_CLEAR_INITIAL_PARITY; + intel_prop = devm_kzalloc(bus->dev, sizeof(*intel_prop), GFP_KERNEL); + if (!intel_prop) { + fwnode_handle_put(link); + return -ENOMEM; + } + + /* initialize with hardware defaults, in case the properties are not found */ + intel_prop->clde = 0x0; + intel_prop->doaise2 = 0x0; + intel_prop->dodse2 = 0x0; + intel_prop->clds = 0x0; + intel_prop->clss = 0x0; + intel_prop->doaise = 0x1; + intel_prop->doais = 0x3; + intel_prop->dodse = 0x0; + intel_prop->dods = 0x1; + + fwnode_property_read_u16(link, + "intel-sdw-clde", + &intel_prop->clde); + fwnode_property_read_u16(link, + "intel-sdw-doaise2", + &intel_prop->doaise2); + fwnode_property_read_u16(link, + "intel-sdw-dodse2", + &intel_prop->dodse2); + fwnode_property_read_u16(link, + "intel-sdw-clds", + &intel_prop->clds); + fwnode_property_read_u16(link, + "intel-sdw-clss", + &intel_prop->clss); + fwnode_property_read_u16(link, + "intel-sdw-doaise", + &intel_prop->doaise); + fwnode_property_read_u16(link, + "intel-sdw-doais", + &intel_prop->doais); + fwnode_property_read_u16(link, + "intel-sdw-dodse", + &intel_prop->dodse); + fwnode_property_read_u16(link, + "intel-sdw-dods", + &intel_prop->dods); + bus->vendor_specific_prop = intel_prop; + + dev_dbg(bus->dev, "doaise %#x doais %#x dodse %#x dods %#x\n", + intel_prop->doaise, + intel_prop->doais, + intel_prop->dodse, + intel_prop->dods); + + fwnode_handle_put(link); + return 0; } @@ -203,6 +288,9 @@ static struct sdw_master_ops sdw_intel_ops = { .get_device_num = intel_get_device_num_ida, .put_device_num = intel_put_device_num_ida, .new_peripheral_assigned = generic_new_peripheral_assigned, + + .bpt_send_async = generic_bpt_send_async, + .bpt_wait = generic_bpt_wait, }; /* @@ -240,6 +328,20 @@ static int intel_link_probe(struct auxiliary_device *auxdev, bus->link_id = auxdev->id; bus->clk_stop_timeout = 1; + /* + * paranoia check: make sure ACPI-reported number of links is aligned with + * hardware capabilities. + */ + ret = sdw_intel_get_link_count(sdw); + if (ret < 0) { + dev_err(dev, "%s: sdw_intel_get_link_count failed: %d\n", __func__, ret); + return ret; + } + if (ret <= sdw->instance) { + dev_err(dev, "%s: invalid link id %d, link count %d\n", __func__, auxdev->id, ret); + return -EINVAL; + } + sdw_cdns_probe(cdns); /* Set ops */ @@ -251,9 +353,6 @@ static int intel_link_probe(struct auxiliary_device *auxdev, /* use generic bandwidth allocation algorithm */ sdw->cdns.bus.compute_params = sdw_compute_params; - /* avoid resuming from pm_runtime suspend if it's not required */ - dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND); - ret = sdw_bus_master_add(bus, dev, dev->fwnode); if (ret) { dev_err(dev, "sdw_bus_master_add fail: %d\n", ret); @@ -398,6 +497,7 @@ static void intel_link_remove(struct auxiliary_device *auxdev) */ if (!bus->prop.hw_disabled) { sdw_intel_debugfs_exit(sdw); + cancel_delayed_work_sync(&cdns->attach_dwork); sdw_cdns_enable_interrupt(cdns, false); } sdw_bus_master_delete(bus); @@ -440,7 +540,7 @@ int intel_link_process_wakeen_event(struct auxiliary_device *auxdev) * PM calls */ -static int intel_resume_child_device(struct device *dev, void *data) +int intel_resume_child_device(struct device *dev, void *data) { int ret; struct sdw_slave *slave = dev_to_sdw_dev(dev); @@ -454,9 +554,9 @@ static int intel_resume_child_device(struct device *dev, void *data) return 0; } - ret = pm_request_resume(dev); + ret = pm_runtime_resume(dev); if (ret < 0) { - dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret); + dev_err(dev, "%s: pm_runtime_resume failed: %d\n", __func__, ret); return ret; } @@ -499,9 +599,9 @@ static int __maybe_unused intel_pm_prepare(struct device *dev) * first resume the device for this link. This will also by construction * resume the PCI parent device. */ - ret = pm_request_resume(dev); + ret = pm_runtime_resume(dev); if (ret < 0) { - dev_err(dev, "%s: pm_request_resume failed: %d\n", __func__, ret); + dev_err(dev, "%s: pm_runtime_resume failed: %d\n", __func__, ret); return 0; } @@ -537,7 +637,10 @@ static int __maybe_unused intel_suspend(struct device *dev) return 0; } - if (pm_runtime_suspended(dev)) { + /* Prevent runtime PM from racing with the code below. */ + pm_runtime_disable(dev); + + if (pm_runtime_status_suspended(dev)) { dev_dbg(dev, "pm_runtime status: suspended\n"); clock_stop_quirks = sdw->link_res->clock_stop_quirks; @@ -545,7 +648,7 @@ static int __maybe_unused intel_suspend(struct device *dev) if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) || !clock_stop_quirks) { - if (pm_runtime_suspended(dev->parent)) { + if (pm_runtime_status_suspended(dev->parent)) { /* * paranoia check: this should not happen with the .prepare * resume to full power @@ -612,7 +715,6 @@ static int __maybe_unused intel_resume(struct device *dev) struct sdw_cdns *cdns = dev_get_drvdata(dev); struct sdw_intel *sdw = cdns_to_intel(cdns); struct sdw_bus *bus = &cdns->bus; - int link_flags; int ret; if (bus->prop.hw_disabled || !sdw->startup_done) { @@ -621,23 +723,6 @@ static int __maybe_unused intel_resume(struct device *dev) return 0; } - if (pm_runtime_suspended(dev)) { - dev_dbg(dev, "pm_runtime status was suspended, forcing active\n"); - - /* follow required sequence from runtime_pm.rst */ - pm_runtime_disable(dev); - pm_runtime_set_active(dev); - pm_runtime_mark_last_busy(dev); - pm_runtime_enable(dev); - - pm_runtime_resume(bus->dev); - - link_flags = md_flags >> (bus->link_id * 8); - - if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) - pm_runtime_idle(dev); - } - ret = sdw_intel_link_power_up(sdw); if (ret) { dev_err(dev, "%s failed: %d\n", __func__, ret); @@ -658,6 +743,14 @@ static int __maybe_unused intel_resume(struct device *dev) } /* + * Runtime PM has been disabled in intel_suspend(), so set the status + * to active because the device has just been resumed and re-enable + * runtime PM. + */ + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* * after system resume, the pm_runtime suspend() may kick in * during the enumeration, before any children device force the * master device to remain active. Using pm_runtime_get() diff --git a/drivers/soundwire/intel_auxdevice.h b/drivers/soundwire/intel_auxdevice.h index a00ecde95563..acaae0bd6592 100644 --- a/drivers/soundwire/intel_auxdevice.h +++ b/drivers/soundwire/intel_auxdevice.h @@ -6,6 +6,7 @@ int intel_link_startup(struct auxiliary_device *auxdev); int intel_link_process_wakeen_event(struct auxiliary_device *auxdev); +int intel_resume_child_device(struct device *dev, void *data); struct sdw_intel_link_dev { struct auxiliary_device auxdev; diff --git a/drivers/soundwire/intel_bus_common.c b/drivers/soundwire/intel_bus_common.c index e5ac3cc7cb79..ad1f8ebdbfc9 100644 --- a/drivers/soundwire/intel_bus_common.c +++ b/drivers/soundwire/intel_bus_common.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) -// Copyright(c) 2015-2023 Intel Corporation. All rights reserved. +// Copyright(c) 2015-2023 Intel Corporation #include <linux/acpi.h> #include <linux/soundwire/sdw_registers.h> @@ -16,6 +16,12 @@ int intel_start_bus(struct sdw_intel *sdw) struct sdw_bus *bus = &cdns->bus; int ret; + ret = sdw_cdns_soft_reset(cdns); + if (ret < 0) { + dev_err(dev, "%s: unable to soft-reset Cadence IP: %d\n", __func__, ret); + return ret; + } + /* * follow recommended programming flows to avoid timeouts when * gsync is enabled @@ -45,21 +51,24 @@ int intel_start_bus(struct sdw_intel *sdw) return ret; } - ret = sdw_cdns_exit_reset(cdns); + ret = sdw_cdns_enable_interrupt(cdns, true); if (ret < 0) { - dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret); + dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret); return ret; } - ret = sdw_cdns_enable_interrupt(cdns, true); + ret = sdw_cdns_exit_reset(cdns); if (ret < 0) { - dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret); + dev_err(dev, "%s: unable to exit bus reset sequence: %d\n", __func__, ret); return ret; } sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS); + schedule_delayed_work(&cdns->attach_dwork, + msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); + return 0; } @@ -136,21 +145,24 @@ int intel_start_bus_after_reset(struct sdw_intel *sdw) return ret; } - ret = sdw_cdns_exit_reset(cdns); + ret = sdw_cdns_enable_interrupt(cdns, true); if (ret < 0) { - dev_err(dev, "unable to exit bus reset sequence during resume\n"); + dev_err(dev, "cannot enable interrupts during resume\n"); return ret; } - ret = sdw_cdns_enable_interrupt(cdns, true); + ret = sdw_cdns_exit_reset(cdns); if (ret < 0) { - dev_err(dev, "cannot enable interrupts during resume\n"); + dev_err(dev, "unable to exit bus reset sequence during resume\n"); return ret; } } sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS); + schedule_delayed_work(&cdns->attach_dwork, + msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); + return 0; } @@ -184,6 +196,9 @@ int intel_start_bus_after_clock_stop(struct sdw_intel *sdw) sdw_cdns_check_self_clearing_bits(cdns, __func__, true, INTEL_MASTER_RESET_ITERATIONS); + schedule_delayed_work(&cdns->attach_dwork, + msecs_to_jiffies(SDW_INTEL_DELAYED_ENUMERATION_MS)); + return 0; } @@ -194,6 +209,8 @@ int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop) bool wake_enable = false; int ret; + cancel_delayed_work_sync(&cdns->attach_dwork); + if (clock_stop) { ret = sdw_cdns_clock_stop(cdns, true); if (ret < 0) diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 534c8795e7e8..5f53666514a4 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -16,6 +16,7 @@ #include <linux/pm_runtime.h> #include <linux/soundwire/sdw_intel.h> #include "cadence_master.h" +#include "bus.h" #include "intel.h" #include "intel_auxdevice.h" @@ -148,7 +149,7 @@ irqreturn_t sdw_intel_thread(int irq, void *dev_id) return IRQ_HANDLED; } -EXPORT_SYMBOL_NS(sdw_intel_thread, SOUNDWIRE_INTEL_INIT); +EXPORT_SYMBOL_NS(sdw_intel_thread, "SOUNDWIRE_INTEL_INIT"); static struct sdw_intel_ctx *sdw_intel_probe_controller(struct sdw_intel_res *res) @@ -251,17 +252,16 @@ static struct sdw_intel_ctx num_slaves++; } - ctx->ids = kcalloc(num_slaves, sizeof(*ctx->ids), GFP_KERNEL); - if (!ctx->ids) + ctx->peripherals = kmalloc(struct_size(ctx->peripherals, array, num_slaves), + GFP_KERNEL); + if (!ctx->peripherals) goto err; - - ctx->num_slaves = num_slaves; + ctx->peripherals->num_peripherals = num_slaves; i = 0; list_for_each_entry(link, &ctx->link_list, list) { bus = &link->cdns->bus; list_for_each_entry(slave, &bus->slaves, node) { - ctx->ids[i].id = slave->id; - ctx->ids[i].link_id = bus->link_id; + ctx->peripherals->array[i] = slave; i++; } } @@ -334,7 +334,7 @@ struct sdw_intel_ctx { return sdw_intel_probe_controller(res); } -EXPORT_SYMBOL_NS(sdw_intel_probe, SOUNDWIRE_INTEL_INIT); +EXPORT_SYMBOL_NS(sdw_intel_probe, "SOUNDWIRE_INTEL_INIT"); /** * sdw_intel_startup() - SoundWire Intel startup @@ -347,7 +347,7 @@ int sdw_intel_startup(struct sdw_intel_ctx *ctx) { return sdw_intel_startup_controller(ctx); } -EXPORT_SYMBOL_NS(sdw_intel_startup, SOUNDWIRE_INTEL_INIT); +EXPORT_SYMBOL_NS(sdw_intel_startup, "SOUNDWIRE_INTEL_INIT"); /** * sdw_intel_exit() - SoundWire Intel exit * @ctx: SoundWire context allocated in the probe @@ -356,12 +356,25 @@ EXPORT_SYMBOL_NS(sdw_intel_startup, SOUNDWIRE_INTEL_INIT); */ void sdw_intel_exit(struct sdw_intel_ctx *ctx) { + struct sdw_intel_link_res *link; + + /* we first resume links and devices and wait synchronously before the cleanup */ + list_for_each_entry(link, &ctx->link_list, list) { + struct sdw_bus *bus = &link->cdns->bus; + int ret; + + ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device); + if (ret < 0) + dev_err(bus->dev, "%s: intel_resume_child_device failed: %d\n", + __func__, ret); + } + sdw_intel_cleanup(ctx); - kfree(ctx->ids); + kfree(ctx->peripherals); kfree(ctx->ldev); kfree(ctx); } -EXPORT_SYMBOL_NS(sdw_intel_exit, SOUNDWIRE_INTEL_INIT); +EXPORT_SYMBOL_NS(sdw_intel_exit, "SOUNDWIRE_INTEL_INIT"); void sdw_intel_process_wakeen_event(struct sdw_intel_ctx *ctx) { @@ -384,7 +397,7 @@ void sdw_intel_process_wakeen_event(struct sdw_intel_ctx *ctx) intel_link_process_wakeen_event(&ldev->auxdev); } } -EXPORT_SYMBOL_NS(sdw_intel_process_wakeen_event, SOUNDWIRE_INTEL_INIT); +EXPORT_SYMBOL_NS(sdw_intel_process_wakeen_event, "SOUNDWIRE_INTEL_INIT"); MODULE_LICENSE("Dual BSD/GPL"); MODULE_DESCRIPTION("Intel Soundwire Init Library"); diff --git a/drivers/soundwire/irq.c b/drivers/soundwire/irq.c index 0c08cebb1235..c237e6d0766b 100644 --- a/drivers/soundwire/irq.c +++ b/drivers/soundwire/irq.c @@ -46,14 +46,18 @@ void sdw_irq_delete(struct sdw_bus *bus) irq_domain_remove(bus->domain); } +static void sdw_irq_dispose_mapping(void *data) +{ + struct sdw_slave *slave = data; + + irq_dispose_mapping(irq_find_mapping(slave->bus->domain, slave->dev_num)); +} + void sdw_irq_create_mapping(struct sdw_slave *slave) { slave->irq = irq_create_mapping(slave->bus->domain, slave->dev_num); if (!slave->irq) dev_warn(&slave->dev, "Failed to map IRQ\n"); -} -void sdw_irq_dispose_mapping(struct sdw_slave *slave) -{ - irq_dispose_mapping(irq_find_mapping(slave->bus->domain, slave->dev_num)); + devm_add_action_or_reset(&slave->dev, sdw_irq_dispose_mapping, slave); } diff --git a/drivers/soundwire/irq.h b/drivers/soundwire/irq.h index 58a58046d92b..86e2318409da 100644 --- a/drivers/soundwire/irq.h +++ b/drivers/soundwire/irq.h @@ -16,7 +16,6 @@ int sdw_irq_create(struct sdw_bus *bus, struct fwnode_handle *fwnode); void sdw_irq_delete(struct sdw_bus *bus); void sdw_irq_create_mapping(struct sdw_slave *slave); -void sdw_irq_dispose_mapping(struct sdw_slave *slave); #else /* CONFIG_IRQ_DOMAIN */ @@ -34,10 +33,6 @@ static inline void sdw_irq_create_mapping(struct sdw_slave *slave) { } -static inline void sdw_irq_dispose_mapping(struct sdw_slave *slave) -{ -} - #endif /* CONFIG_IRQ_DOMAIN */ #endif /* __SDW_IRQ_H */ diff --git a/drivers/soundwire/mipi_disco.c b/drivers/soundwire/mipi_disco.c index 55a9c51c84c1..65afb28ef8fa 100644 --- a/drivers/soundwire/mipi_disco.c +++ b/drivers/soundwire/mipi_disco.c @@ -23,6 +23,26 @@ #include <linux/soundwire/sdw.h> #include "bus.h" +static bool mipi_fwnode_property_read_bool(const struct fwnode_handle *fwnode, + const char *propname) +{ + int ret; + u8 val; + + if (!fwnode_property_present(fwnode, propname)) + return false; + ret = fwnode_property_read_u8_array(fwnode, propname, &val, 1); + if (ret < 0) + return false; + return !!val; +} + +static bool mipi_device_property_read_bool(const struct device *dev, + const char *propname) +{ + return mipi_fwnode_property_read_bool(dev_fwnode(dev), propname); +} + /** * sdw_master_read_prop() - Read Master properties * @bus: SDW bus instance @@ -31,8 +51,11 @@ int sdw_master_read_prop(struct sdw_bus *bus) { struct sdw_master_prop *prop = &bus->prop; struct fwnode_handle *link; + const char *scales_prop; char name[32]; - int nval, i; + int nval; + int ret; + int i; device_property_read_u32(bus->dev, "mipi-sdw-sw-interface-revision", @@ -48,11 +71,11 @@ int sdw_master_read_prop(struct sdw_bus *bus) return -EIO; } - if (fwnode_property_read_bool(link, + if (mipi_fwnode_property_read_bool(link, "mipi-sdw-clock-stop-mode0-supported")) prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE0); - if (fwnode_property_read_bool(link, + if (mipi_fwnode_property_read_bool(link, "mipi-sdw-clock-stop-mode1-supported")) prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE1); @@ -66,12 +89,16 @@ int sdw_master_read_prop(struct sdw_bus *bus) prop->clk_freq = devm_kcalloc(bus->dev, prop->num_clk_freq, sizeof(*prop->clk_freq), GFP_KERNEL); - if (!prop->clk_freq) + if (!prop->clk_freq) { + fwnode_handle_put(link); return -ENOMEM; + } - fwnode_property_read_u32_array(link, + ret = fwnode_property_read_u32_array(link, "mipi-sdw-clock-frequencies-supported", prop->clk_freq, prop->num_clk_freq); + if (ret < 0) + return ret; } /* @@ -86,19 +113,28 @@ int sdw_master_read_prop(struct sdw_bus *bus) } } - nval = fwnode_property_count_u32(link, "mipi-sdw-supported-clock-gears"); + scales_prop = "mipi-sdw-supported-clock-scales"; + nval = fwnode_property_count_u32(link, scales_prop); + if (nval == 0) { + scales_prop = "mipi-sdw-supported-clock-gears"; + nval = fwnode_property_count_u32(link, scales_prop); + } if (nval > 0) { prop->num_clk_gears = nval; prop->clk_gears = devm_kcalloc(bus->dev, prop->num_clk_gears, sizeof(*prop->clk_gears), GFP_KERNEL); - if (!prop->clk_gears) + if (!prop->clk_gears) { + fwnode_handle_put(link); return -ENOMEM; + } - fwnode_property_read_u32_array(link, - "mipi-sdw-supported-clock-gears", + ret = fwnode_property_read_u32_array(link, + scales_prop, prop->clk_gears, prop->num_clk_gears); + if (ret < 0) + return ret; } fwnode_property_read_u32(link, "mipi-sdw-default-frame-rate", @@ -110,12 +146,14 @@ int sdw_master_read_prop(struct sdw_bus *bus) fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size", &prop->default_col); - prop->dynamic_frame = fwnode_property_read_bool(link, + prop->dynamic_frame = mipi_fwnode_property_read_bool(link, "mipi-sdw-dynamic-frame-shape"); fwnode_property_read_u32(link, "mipi-sdw-command-error-threshold", &prop->err_threshold); + fwnode_handle_put(link); + return 0; } EXPORT_SYMBOL(sdw_master_read_prop); @@ -125,6 +163,7 @@ static int sdw_slave_read_dp0(struct sdw_slave *slave, struct sdw_dp0_prop *dp0) { int nval; + int ret; fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength", &dp0->max_word); @@ -142,20 +181,38 @@ static int sdw_slave_read_dp0(struct sdw_slave *slave, if (!dp0->words) return -ENOMEM; - fwnode_property_read_u32_array(port, + ret = fwnode_property_read_u32_array(port, "mipi-sdw-port-wordlength-configs", dp0->words, dp0->num_words); + if (ret < 0) + return ret; } - dp0->BRA_flow_controlled = fwnode_property_read_bool(port, + dp0->BRA_flow_controlled = mipi_fwnode_property_read_bool(port, "mipi-sdw-bra-flow-controlled"); - dp0->simple_ch_prep_sm = fwnode_property_read_bool(port, + dp0->simple_ch_prep_sm = mipi_fwnode_property_read_bool(port, "mipi-sdw-simplified-channel-prepare-sm"); - dp0->imp_def_interrupts = fwnode_property_read_bool(port, + dp0->imp_def_interrupts = mipi_fwnode_property_read_bool(port, "mipi-sdw-imp-def-dp0-interrupts-supported"); + nval = fwnode_property_count_u32(port, "mipi-sdw-lane-list"); + if (nval > 0) { + dp0->num_lanes = nval; + dp0->lane_list = devm_kcalloc(&slave->dev, + dp0->num_lanes, sizeof(*dp0->lane_list), + GFP_KERNEL); + if (!dp0->lane_list) + return -ENOMEM; + + ret = fwnode_property_read_u32_array(port, + "mipi-sdw-lane-list", + dp0->lane_list, dp0->num_lanes); + if (ret < 0) + return ret; + } + return 0; } @@ -165,9 +222,10 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, { struct fwnode_handle *node; u32 bit, i = 0; - int nval; unsigned long addr; char name[40]; + int nval; + int ret; addr = ports; /* valid ports are 1 to 14 so apply mask */ @@ -197,12 +255,16 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, dpn[i].num_words, sizeof(*dpn[i].words), GFP_KERNEL); - if (!dpn[i].words) + if (!dpn[i].words) { + fwnode_handle_put(node); return -ENOMEM; + } - fwnode_property_read_u32_array(node, + ret = fwnode_property_read_u32_array(node, "mipi-sdw-port-wordlength-configs", dpn[i].words, dpn[i].num_words); + if (ret < 0) + return ret; } fwnode_property_read_u32(node, "mipi-sdw-data-port-type", @@ -212,7 +274,7 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, "mipi-sdw-max-grouping-supported", &dpn[i].max_grouping); - dpn[i].simple_ch_prep_sm = fwnode_property_read_bool(node, + dpn[i].simple_ch_prep_sm = mipi_fwnode_property_read_bool(node, "mipi-sdw-simplified-channelprepare-sm"); fwnode_property_read_u32(node, @@ -236,12 +298,16 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, dpn[i].num_channels, sizeof(*dpn[i].channels), GFP_KERNEL); - if (!dpn[i].channels) + if (!dpn[i].channels) { + fwnode_handle_put(node); return -ENOMEM; + } - fwnode_property_read_u32_array(node, + ret = fwnode_property_read_u32_array(node, "mipi-sdw-channel-number-list", dpn[i].channels, dpn[i].num_channels); + if (ret < 0) + return ret; } nval = fwnode_property_count_u32(node, "mipi-sdw-channel-combination-list"); @@ -251,13 +317,17 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, dpn[i].num_ch_combinations, sizeof(*dpn[i].ch_combinations), GFP_KERNEL); - if (!dpn[i].ch_combinations) + if (!dpn[i].ch_combinations) { + fwnode_handle_put(node); return -ENOMEM; + } - fwnode_property_read_u32_array(node, + ret = fwnode_property_read_u32_array(node, "mipi-sdw-channel-combination-list", dpn[i].ch_combinations, dpn[i].num_ch_combinations); + if (ret < 0) + return ret; } fwnode_property_read_u32(node, @@ -266,13 +336,29 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer", &dpn[i].max_async_buffer); - dpn[i].block_pack_mode = fwnode_property_read_bool(node, + dpn[i].block_pack_mode = mipi_fwnode_property_read_bool(node, "mipi-sdw-block-packing-mode"); fwnode_property_read_u32(node, "mipi-sdw-port-encoding-type", &dpn[i].port_encoding); - /* TODO: Read audio mode */ + nval = fwnode_property_count_u32(node, "mipi-sdw-lane-list"); + if (nval > 0) { + dpn[i].num_lanes = nval; + dpn[i].lane_list = devm_kcalloc(&slave->dev, + dpn[i].num_lanes, sizeof(*dpn[i].lane_list), + GFP_KERNEL); + if (!dpn[i].lane_list) + return -ENOMEM; + + ret = fwnode_property_read_u32_array(node, + "mipi-sdw-lane-list", + dpn[i].lane_list, dpn[i].num_lanes); + if (ret < 0) + return ret; + } + + fwnode_handle_put(node); i++; } @@ -280,6 +366,44 @@ static int sdw_slave_read_dpn(struct sdw_slave *slave, return 0; } +/* + * In MIPI DisCo spec for SoundWire, lane mapping for a slave device is done with + * mipi-sdw-lane-x-mapping properties, where x is 1..7, and the values for those + * properties are mipi-sdw-manager-lane-x or mipi-sdw-peripheral-link-y, where x + * is an integer between 1 to 7 if the lane is connected to a manager lane, y is a + * character between A to E if the lane is connected to another peripheral lane. + */ +int sdw_slave_read_lane_mapping(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + struct device *dev = &slave->dev; + char prop_name[30]; + const char *prop_val; + size_t len; + int ret, i; + u8 lane; + + for (i = 0; i < SDW_MAX_LANES; i++) { + snprintf(prop_name, sizeof(prop_name), "mipi-sdw-lane-%d-mapping", i); + ret = device_property_read_string(dev, prop_name, &prop_val); + if (ret) + continue; + + len = strlen(prop_val); + if (len < 1) + return -EINVAL; + + /* The last character is enough to identify the connection */ + ret = kstrtou8(&prop_val[len - 1], 10, &lane); + if (ret) + return ret; + if (in_range(lane, 1, SDW_MAX_LANES - 1)) + prop->lane_maps[i] = lane; + } + return 0; +} +EXPORT_SYMBOL(sdw_slave_read_lane_mapping); + /** * sdw_slave_read_prop() - Read Slave properties * @slave: SDW Slave @@ -290,42 +414,46 @@ int sdw_slave_read_prop(struct sdw_slave *slave) struct device *dev = &slave->dev; struct fwnode_handle *port; int nval; + int ret; device_property_read_u32(dev, "mipi-sdw-sw-interface-revision", &prop->mipi_revision); - prop->wake_capable = device_property_read_bool(dev, + prop->wake_capable = mipi_device_property_read_bool(dev, "mipi-sdw-wake-up-unavailable"); prop->wake_capable = !prop->wake_capable; - prop->test_mode_capable = device_property_read_bool(dev, + prop->test_mode_capable = mipi_device_property_read_bool(dev, "mipi-sdw-test-mode-supported"); prop->clk_stop_mode1 = false; - if (device_property_read_bool(dev, + if (mipi_device_property_read_bool(dev, "mipi-sdw-clock-stop-mode1-supported")) prop->clk_stop_mode1 = true; - prop->simple_clk_stop_capable = device_property_read_bool(dev, + prop->simple_clk_stop_capable = mipi_device_property_read_bool(dev, "mipi-sdw-simplified-clockstopprepare-sm-supported"); device_property_read_u32(dev, "mipi-sdw-clockstopprepare-timeout", &prop->clk_stop_timeout); - device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout", - &prop->ch_prep_timeout); + ret = device_property_read_u32(dev, "mipi-sdw-peripheral-channelprepare-timeout", + &prop->ch_prep_timeout); + if (ret < 0) + device_property_read_u32(dev, "mipi-sdw-slave-channelprepare-timeout", + &prop->ch_prep_timeout); device_property_read_u32(dev, "mipi-sdw-clockstopprepare-hard-reset-behavior", &prop->reset_behave); - prop->high_PHY_capable = device_property_read_bool(dev, + prop->high_PHY_capable = mipi_device_property_read_bool(dev, "mipi-sdw-highPHY-capable"); - prop->paging_support = device_property_read_bool(dev, + prop->paging_support = mipi_device_property_read_bool(dev, "mipi-sdw-paging-support"); - prop->bank_delay_support = device_property_read_bool(dev, + prop->bank_delay_support = mipi_device_property_read_bool(dev, "mipi-sdw-bank-delay-support"); device_property_read_u32(dev, @@ -340,7 +468,17 @@ int sdw_slave_read_prop(struct sdw_slave *slave) device_property_read_u32(dev, "mipi-sdw-sink-port-list", &prop->sink_ports); - /* Read dp0 properties */ + device_property_read_u32(dev, "mipi-sdw-sdca-interrupt-register-list", + &prop->sdca_interrupt_register_list); + + prop->commit_register_supported = mipi_device_property_read_bool(dev, + "mipi-sdw-commit-register-supported"); + + /* + * Read dp0 properties - we don't rely on the 'mipi-sdw-dp-0-supported' + * property since the 'mipi-sdw-dp0-subproperties' property is logically + * equivalent. + */ port = device_get_named_child_node(dev, "mipi-sdw-dp-0-subproperties"); if (!port) { dev_dbg(dev, "DP0 node not found!!\n"); @@ -348,10 +486,14 @@ int sdw_slave_read_prop(struct sdw_slave *slave) prop->dp0_prop = devm_kzalloc(&slave->dev, sizeof(*prop->dp0_prop), GFP_KERNEL); - if (!prop->dp0_prop) + if (!prop->dp0_prop) { + fwnode_handle_put(port); return -ENOMEM; + } sdw_slave_read_dp0(slave, port, prop->dp0_prop); + + fwnode_handle_put(port); } /* @@ -382,6 +524,6 @@ int sdw_slave_read_prop(struct sdw_slave *slave) sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval, prop->sink_ports, "sink"); - return 0; + return sdw_slave_read_lane_mapping(slave); } EXPORT_SYMBOL(sdw_slave_read_prop); diff --git a/drivers/soundwire/qcom.c b/drivers/soundwire/qcom.c index 3c4d6debab1f..295a46dc2be7 100644 --- a/drivers/soundwire/qcom.c +++ b/drivers/soundwire/qcom.c @@ -156,6 +156,7 @@ struct qcom_swrm_port_config { u8 word_length; u8 blk_group_count; u8 lane_control; + u8 ch_mask; }; /* @@ -197,8 +198,7 @@ struct qcom_swrm_ctrl { int num_dout_ports; int cols_index; int rows_index; - unsigned long dout_port_mask; - unsigned long din_port_mask; + unsigned long port_mask; u32 intr_mask; u8 rcmd_id; u8 wcmd_id; @@ -905,6 +905,18 @@ static int qcom_swrm_init(struct qcom_swrm_ctrl *ctrl) return 0; } +static int qcom_swrm_read_prop(struct sdw_bus *bus) +{ + struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + + if (ctrl->version >= SWRM_VERSION_2_0_0) { + bus->multi_link = true; + bus->hw_sync_min_links = 3; + } + + return 0; +} + static enum sdw_command_response qcom_swrm_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg) { @@ -1037,9 +1049,13 @@ static int qcom_swrm_port_enable(struct sdw_bus *bus, { u32 reg = SWRM_DP_PORT_CTRL_BANK(enable_ch->port_num, bank); struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); + struct qcom_swrm_port_config *pcfg; u32 val; + pcfg = &ctrl->pconfig[enable_ch->port_num]; ctrl->reg_read(ctrl, reg, &val); + if (pcfg->ch_mask != SWR_INVALID_PARAM && pcfg->ch_mask != 0) + enable_ch->ch_mask = pcfg->ch_mask; if (enable_ch->enable) val |= (enable_ch->ch_mask << SWRM_DP_PORT_CTRL_EN_CHAN_SHFT); @@ -1056,11 +1072,12 @@ static const struct sdw_master_port_ops qcom_swrm_port_ops = { }; static const struct sdw_master_ops qcom_swrm_ops = { + .read_prop = qcom_swrm_read_prop, .xfer_msg = qcom_swrm_xfer_msg, .pre_bank_switch = qcom_swrm_pre_bank_switch, }; -static int qcom_swrm_compute_params(struct sdw_bus *bus) +static int qcom_swrm_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream) { struct qcom_swrm_ctrl *ctrl = to_qcom_sdw(bus); struct sdw_master_runtime *m_rt; @@ -1133,11 +1150,7 @@ static void qcom_swrm_stream_free_ports(struct qcom_swrm_ctrl *ctrl, mutex_lock(&ctrl->port_lock); list_for_each_entry(m_rt, &stream->master_list, stream_node) { - if (m_rt->direction == SDW_DATA_DIR_RX) - port_mask = &ctrl->dout_port_mask; - else - port_mask = &ctrl->din_port_mask; - + port_mask = &ctrl->port_mask; list_for_each_entry(p_rt, &m_rt->port_list, port_node) clear_bit(p_rt->num, port_mask); } @@ -1165,7 +1178,7 @@ static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, else sconfig.direction = SDW_DATA_DIR_RX; - /* hw parameters wil be ignored as we only support PDM */ + /* hw parameters will be ignored as we only support PDM */ sconfig.ch_count = 1; sconfig.frame_rate = params_rate(params); sconfig.type = stream->type; @@ -1173,13 +1186,18 @@ static int qcom_swrm_stream_alloc_ports(struct qcom_swrm_ctrl *ctrl, mutex_lock(&ctrl->port_lock); list_for_each_entry(m_rt, &stream->master_list, stream_node) { - if (m_rt->direction == SDW_DATA_DIR_RX) { - maxport = ctrl->num_dout_ports; - port_mask = &ctrl->dout_port_mask; - } else { - maxport = ctrl->num_din_ports; - port_mask = &ctrl->din_port_mask; - } + /* + * For streams with multiple masters: + * Allocate ports only for devices connected to this master. + * Such devices will have ports allocated by their own master + * and its qcom_swrm_stream_alloc_ports() call. + */ + if (ctrl->bus.id != m_rt->bus->id) + continue; + + port_mask = &ctrl->port_mask; + maxport = ctrl->num_dout_ports + ctrl->num_din_ports; + list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) { slave = s_rt->slave; @@ -1257,6 +1275,26 @@ static void *qcom_swrm_get_sdw_stream(struct snd_soc_dai *dai, int direction) return ctrl->sruntime[dai->id]; } +static int qcom_swrm_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, const unsigned int *tx_slot, + unsigned int rx_num, const unsigned int *rx_slot) +{ + struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev); + int i; + + if (tx_slot) { + for (i = 0; i < tx_num; i++) + ctrl->pconfig[i].ch_mask = tx_slot[i]; + } + + if (rx_slot) { + for (i = 0; i < rx_num; i++) + ctrl->pconfig[i].ch_mask = rx_slot[i]; + } + + return 0; +} + static int qcom_swrm_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1293,6 +1331,7 @@ static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = { .shutdown = qcom_swrm_shutdown, .set_stream = qcom_swrm_set_sdw_stream, .get_stream = qcom_swrm_get_sdw_stream, + .set_channel_map = qcom_swrm_set_channel_map, }; static const struct snd_soc_component_driver qcom_swrm_dai_component = { @@ -1379,8 +1418,7 @@ static int qcom_swrm_get_port_config(struct qcom_swrm_ctrl *ctrl) return -EINVAL; /* Valid port numbers are from 1-14, so mask out port 0 explicitly */ - set_bit(0, &ctrl->dout_port_mask); - set_bit(0, &ctrl->din_port_mask); + set_bit(0, &ctrl->port_mask); ret = of_property_read_u8_array(np, "qcom,ports-offset1", off1, nports); @@ -1636,14 +1674,12 @@ err_init: return ret; } -static int qcom_swrm_remove(struct platform_device *pdev) +static void qcom_swrm_remove(struct platform_device *pdev) { struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(&pdev->dev); sdw_bus_master_delete(&ctrl->bus); clk_disable_unprepare(ctrl->hclk); - - return 0; } static int __maybe_unused swrm_runtime_resume(struct device *dev) @@ -1769,7 +1805,7 @@ MODULE_DEVICE_TABLE(of, qcom_swrm_of_match); static struct platform_driver qcom_swrm_driver = { .probe = &qcom_swrm_probe, - .remove = &qcom_swrm_remove, + .remove = qcom_swrm_remove, .driver = { .name = "qcom-soundwire", .of_match_table = qcom_swrm_of_match, diff --git a/drivers/soundwire/slave.c b/drivers/soundwire/slave.c index 9963b92eb505..d2d99555ec5a 100644 --- a/drivers/soundwire/slave.c +++ b/drivers/soundwire/slave.c @@ -5,6 +5,7 @@ #include <linux/of.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_type.h> +#include <sound/sdca.h> #include "bus.h" #include "sysfs_local.h" @@ -12,6 +13,7 @@ static void sdw_slave_release(struct device *dev) { struct sdw_slave *slave = dev_to_sdw_dev(dev); + of_node_put(slave->dev.of_node); mutex_destroy(&slave->sdw_dev_lock); kfree(slave); } @@ -70,6 +72,17 @@ int sdw_slave_add(struct sdw_bus *bus, list_add_tail(&slave->node, &bus->slaves); mutex_unlock(&bus->bus_lock); + /* + * The Soundwire driver probe may optionally register SDCA + * sub-devices, one per Function. This means the information + * on the SDCA revision and the number/type of Functions need + * to be extracted from platform firmware before the SoundWire + * driver probe, and as a consequence before the SoundWire + * device_register() below. + */ + sdca_lookup_interface_revision(slave); + sdca_lookup_functions(slave); + ret = device_register(&slave->dev); if (ret) { dev_err(bus->dev, "Failed to add slave: ret %d\n", ret); @@ -97,18 +110,13 @@ static bool find_slave(struct sdw_bus *bus, struct acpi_device *adev, struct sdw_slave_id *id) { - u64 addr; unsigned int link_id; - acpi_status status; - - status = acpi_evaluate_integer(adev->handle, - METHOD_NAME__ADR, NULL, &addr); + u64 addr; + int ret; - if (ACPI_FAILURE(status)) { - dev_err(bus->dev, "_ADR resolution failed: %x\n", - status); + ret = acpi_get_local_u64_address(adev->handle, &addr); + if (ret < 0) return false; - } if (bus->ops->override_adr) addr = bus->ops->override_adr(bus, addr); @@ -264,3 +272,5 @@ int sdw_of_find_slaves(struct sdw_bus *bus) return 0; } + +MODULE_IMPORT_NS("SND_SOC_SDCA"); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 4e9e7d2a942d..a4bea742b5d9 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -14,6 +14,7 @@ #include <linux/soundwire/sdw_registers.h> #include <linux/soundwire/sdw.h> #include <linux/soundwire/sdw_type.h> +#include <linux/string_choices.h> #include <sound/soc.h> #include "bus.h" @@ -87,11 +88,14 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus, return ret; } - /* Program DPN_BlockCtrl3 register */ - ret = sdw_write_no_pm(slave, addr2, t_params->blk_pkg_mode); - if (ret < 0) { - dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n"); - return ret; + /* DP0 does not implement BlockCtrl3 */ + if (t_params->port_num) { + /* Program DPN_BlockCtrl3 register */ + ret = sdw_write_no_pm(slave, addr2, t_params->blk_pkg_mode); + if (ret < 0) { + dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n"); + return ret; + } } /* @@ -130,18 +134,28 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, struct sdw_port_params *p_params = &p_rt->port_params; struct sdw_slave_prop *slave_prop = &s_rt->slave->prop; u32 addr1, addr2, addr3, addr4, addr5, addr6; - struct sdw_dpn_prop *dpn_prop; + enum sdw_dpn_type port_type; + bool read_only_wordlength; int ret; u8 wbuf; if (s_rt->slave->is_mockup_device) return 0; - dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, - s_rt->direction, - t_params->port_num); - if (!dpn_prop) - return -EINVAL; + if (t_params->port_num) { + struct sdw_dpn_prop *dpn_prop; + + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, s_rt->direction, + t_params->port_num); + if (!dpn_prop) + return -EINVAL; + + read_only_wordlength = dpn_prop->read_only_wordlength; + port_type = dpn_prop->type; + } else { + read_only_wordlength = false; + port_type = SDW_DPN_FULL; + } addr1 = SDW_DPN_PORTCTRL(t_params->port_num); addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num); @@ -171,7 +185,7 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, return ret; } - if (!dpn_prop->read_only_wordlength) { + if (!read_only_wordlength) { /* Program DPN_BlockCtrl1 register */ ret = sdw_write_no_pm(s_rt->slave, addr2, (p_params->bps - 1)); if (ret < 0) { @@ -223,9 +237,9 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus, } } - if (dpn_prop->type != SDW_DPN_SIMPLE) { + if (port_type != SDW_DPN_SIMPLE) { ret = _sdw_program_slave_port_params(bus, s_rt->slave, - t_params, dpn_prop->type); + t_params, port_type); if (ret < 0) dev_err(&s_rt->slave->dev, "Transport reg write failed for port: %d\n", @@ -358,7 +372,7 @@ static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt, } else { dev_err(bus->dev, "dpn_port_enable_ch not supported, %s failed\n", - en ? "enable" : "disable"); + str_enable_disable(en)); return -EINVAL; } @@ -432,6 +446,9 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, struct completion *port_ready; struct sdw_dpn_prop *dpn_prop; struct sdw_prepare_ch prep_ch; + u32 imp_def_interrupts; + bool simple_ch_prep_sm; + u32 ch_prep_timeout; bool intr = false; int ret = 0, val; u32 addr; @@ -439,20 +456,35 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, prep_ch.num = p_rt->num; prep_ch.ch_mask = p_rt->ch_mask; - dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, - s_rt->direction, - prep_ch.num); - if (!dpn_prop) { - dev_err(bus->dev, - "Slave Port:%d properties not found\n", prep_ch.num); - return -EINVAL; + if (p_rt->num) { + dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, s_rt->direction, prep_ch.num); + if (!dpn_prop) { + dev_err(bus->dev, + "Slave Port:%d properties not found\n", prep_ch.num); + return -EINVAL; + } + + imp_def_interrupts = dpn_prop->imp_def_interrupts; + simple_ch_prep_sm = dpn_prop->simple_ch_prep_sm; + ch_prep_timeout = dpn_prop->ch_prep_timeout; + } else { + struct sdw_dp0_prop *dp0_prop = s_rt->slave->prop.dp0_prop; + + if (!dp0_prop) { + dev_err(bus->dev, + "Slave DP0 properties not found\n"); + return -EINVAL; + } + imp_def_interrupts = dp0_prop->imp_def_interrupts; + simple_ch_prep_sm = dp0_prop->simple_ch_prep_sm; + ch_prep_timeout = dp0_prop->ch_prep_timeout; } prep_ch.prepare = prep; prep_ch.bank = bus->params.next_bank; - if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm || + if (imp_def_interrupts || !simple_ch_prep_sm || bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) intr = true; @@ -463,7 +495,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, */ if (prep && intr) { ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, - dpn_prop->imp_def_interrupts); + imp_def_interrupts); if (ret < 0) return ret; } @@ -472,7 +504,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, sdw_do_port_prep(s_rt, prep_ch, prep ? SDW_OPS_PORT_PRE_PREP : SDW_OPS_PORT_PRE_DEPREP); /* Prepare Slave port implementing CP_SM */ - if (!dpn_prop->simple_ch_prep_sm) { + if (!simple_ch_prep_sm) { addr = SDW_DPN_PREPARECTRL(p_rt->num); if (prep) @@ -489,7 +521,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, /* Wait for completion on port ready */ port_ready = &s_rt->slave->port_ready[prep_ch.num]; wait_for_completion_timeout(port_ready, - msecs_to_jiffies(dpn_prop->ch_prep_timeout)); + msecs_to_jiffies(ch_prep_timeout)); val = sdw_read_no_pm(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num)); if ((val < 0) || (val & p_rt->ch_mask)) { @@ -506,7 +538,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus, /* Disable interrupt after Port de-prepare */ if (!prep && intr) ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep, - dpn_prop->imp_def_interrupts); + imp_def_interrupts); return ret; } @@ -629,8 +661,44 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt) static int sdw_program_params(struct sdw_bus *bus, bool prepare) { struct sdw_master_runtime *m_rt; + struct sdw_slave *slave; int ret = 0; + u32 addr1; + + /* Check if all Peripherals comply with SDCA */ + list_for_each_entry(slave, &bus->slaves, node) { + if (!slave->dev_num_sticky) + continue; + if (!is_clock_scaling_supported_by_slave(slave)) { + dev_dbg(&slave->dev, "The Peripheral doesn't comply with SDCA\n"); + goto manager_runtime; + } + } + + if (bus->params.next_bank) + addr1 = SDW_SCP_BUSCLOCK_SCALE_B1; + else + addr1 = SDW_SCP_BUSCLOCK_SCALE_B0; + + /* Program SDW_SCP_BUSCLOCK_SCALE if all Peripherals comply with SDCA */ + list_for_each_entry(slave, &bus->slaves, node) { + int scale_index; + u8 base; + + if (!slave->dev_num_sticky) + continue; + scale_index = sdw_slave_get_scale_index(slave, &base); + if (scale_index < 0) + return scale_index; + + ret = sdw_write_no_pm(slave, addr1, scale_index); + if (ret < 0) { + dev_err(&slave->dev, "SDW_SCP_BUSCLOCK_SCALE register write failed\n"); + return ret; + } + } +manager_runtime: list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) { /* @@ -971,7 +1039,8 @@ static int sdw_slave_port_is_valid_range(struct device *dev, int num) static int sdw_slave_port_config(struct sdw_slave *slave, struct sdw_slave_runtime *s_rt, - const struct sdw_port_config *port_config) + const struct sdw_port_config *port_config, + bool is_bpt_stream) { struct sdw_port_runtime *p_rt; int ret; @@ -983,9 +1052,13 @@ static int sdw_slave_port_config(struct sdw_slave *slave, * TODO: Check valid port range as defined by DisCo/ * slave */ - ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num); - if (ret < 0) - return ret; + if (!is_bpt_stream) { + ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num); + if (ret < 0) + return ret; + } else if (port_config[i].num) { + return -EINVAL; + } ret = sdw_port_config(p_rt, port_config, i); if (ret < 0) @@ -1154,6 +1227,20 @@ static struct sdw_master_runtime struct sdw_master_runtime *m_rt, *walk_m_rt; struct list_head *insert_after; + if (stream->type == SDW_STREAM_BPT) { + if (bus->stream_refcount > 0 || bus->bpt_stream_refcount > 0) { + dev_err(bus->dev, "%s: %d/%d audio/BPT stream already allocated\n", + __func__, bus->stream_refcount, bus->bpt_stream_refcount); + return ERR_PTR(-EBUSY); + } + } else { + if (bus->bpt_stream_refcount > 0) { + dev_err(bus->dev, "%s: BPT stream already allocated\n", + __func__); + return ERR_PTR(-EAGAIN); + } + } + m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL); if (!m_rt) return NULL; @@ -1181,6 +1268,10 @@ static struct sdw_master_runtime m_rt->bus = bus; m_rt->stream = stream; + bus->stream_refcount++; + if (stream->type == SDW_STREAM_BPT) + bus->bpt_stream_refcount++; + return m_rt; } @@ -1217,6 +1308,7 @@ static void sdw_master_rt_free(struct sdw_master_runtime *m_rt, struct sdw_stream_runtime *stream) { struct sdw_slave_runtime *s_rt, *_s_rt; + struct sdw_bus *bus = m_rt->bus; list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) { sdw_slave_port_free(s_rt->slave, stream); @@ -1226,6 +1318,10 @@ static void sdw_master_rt_free(struct sdw_master_runtime *m_rt, list_del(&m_rt->stream_node); list_del(&m_rt->bus_node); kfree(m_rt); + + if (stream->type == SDW_STREAM_BPT) + bus->bpt_stream_refcount--; + bus->stream_refcount--; } /** @@ -1289,6 +1385,11 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave, u8 num_ports; int i; + if (!port_num) { + dev_err(&slave->dev, "%s: port_num is zero\n", __func__); + return NULL; + } + if (direction == SDW_DATA_DIR_TX) { num_ports = hweight32(slave->prop.source_ports); dpn_prop = slave->prop.src_dpn_prop; @@ -1378,7 +1479,7 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream, /* Compute params */ if (bus->compute_params) { - ret = bus->compute_params(bus); + ret = bus->compute_params(bus, stream); if (ret < 0) { dev_err(bus->dev, "Compute params failed: %d\n", ret); @@ -1637,9 +1738,19 @@ EXPORT_SYMBOL(sdw_disable_stream); static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) { struct sdw_master_runtime *m_rt; + struct sdw_port_runtime *p_rt; + unsigned int multi_lane_bandwidth; + unsigned int bandwidth; struct sdw_bus *bus; + int state = stream->state; int ret = 0; + /* + * first mark the state as DEPREPARED so that it is not taken into account + * for bit allocation + */ + stream->state = SDW_STREAM_DEPREPARED; + list_for_each_entry(m_rt, &stream->master_list, stream_node) { bus = m_rt->bus; /* De-prepare port(s) */ @@ -1647,19 +1758,34 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) if (ret < 0) { dev_err(bus->dev, "De-prepare port(s) failed: %d\n", ret); + stream->state = state; return ret; } + multi_lane_bandwidth = 0; + + list_for_each_entry(p_rt, &m_rt->port_list, port_node) { + if (!p_rt->lane) + continue; + + bandwidth = m_rt->stream->params.rate * hweight32(p_rt->ch_mask) * + m_rt->stream->params.bps; + multi_lane_bandwidth += bandwidth; + bus->lane_used_bandwidth[p_rt->lane] -= bandwidth; + if (!bus->lane_used_bandwidth[p_rt->lane]) + p_rt->lane = 0; + } /* TODO: Update this during Device-Device support */ - bus->params.bandwidth -= m_rt->stream->params.rate * - m_rt->ch_count * m_rt->stream->params.bps; + bandwidth = m_rt->stream->params.rate * m_rt->ch_count * m_rt->stream->params.bps; + bus->params.bandwidth -= bandwidth - multi_lane_bandwidth; /* Compute params */ if (bus->compute_params) { - ret = bus->compute_params(bus); + ret = bus->compute_params(bus, stream); if (ret < 0) { dev_err(bus->dev, "Compute params failed: %d\n", ret); + stream->state = state; return ret; } } @@ -1668,11 +1794,11 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream) ret = sdw_program_params(bus, false); if (ret < 0) { dev_err(bus->dev, "%s: Program params failed: %d\n", __func__, ret); + stream->state = state; return ret; } } - stream->state = SDW_STREAM_DEPREPARED; return do_bank_switch(stream); } @@ -1739,12 +1865,13 @@ static int set_stream(struct snd_pcm_substream *substream, * sdw_alloc_stream() - Allocate and return stream runtime * * @stream_name: SoundWire stream name + * @type: stream type (could be PCM ,PDM or BPT) * * Allocates a SoundWire stream runtime instance. * sdw_alloc_stream should be called only once per stream. Typically * invoked from ALSA/ASoC machine/platform driver. */ -struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name) +struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type) { struct sdw_stream_runtime *stream; @@ -1756,6 +1883,7 @@ struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name) INIT_LIST_HEAD(&stream->master_list); stream->state = SDW_STREAM_ALLOCATED; stream->m_rt_count = 0; + stream->type = type; return stream; } @@ -1784,7 +1912,7 @@ int sdw_startup_stream(void *sdw_substream) if (!name) return -ENOMEM; - sdw_stream = sdw_alloc_stream(name); + sdw_stream = sdw_alloc_stream(name, SDW_STREAM_PCM); if (!sdw_stream) { dev_err(rtd->dev, "alloc stream failed for substream DAI %s\n", substream->name); ret = -ENOMEM; @@ -1891,6 +2019,12 @@ int sdw_stream_add_master(struct sdw_bus *bus, m_rt = sdw_master_rt_find(bus, stream); if (!m_rt) { m_rt = sdw_master_rt_alloc(bus, stream); + if (IS_ERR(m_rt)) { + ret = PTR_ERR(m_rt); + dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s: %d\n", + __func__, stream->name, ret); + goto unlock; + } if (!m_rt) { dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s\n", __func__, stream->name); @@ -2006,6 +2140,12 @@ int sdw_stream_add_slave(struct sdw_slave *slave, * So, allocate m_rt and add Slave to it. */ m_rt = sdw_master_rt_alloc(slave->bus, stream); + if (IS_ERR(m_rt)) { + ret = PTR_ERR(m_rt); + dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s: %d\n", + __func__, stream->name, ret); + goto unlock; + } if (!m_rt) { dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s\n", __func__, stream->name); @@ -2047,7 +2187,8 @@ int sdw_stream_add_slave(struct sdw_slave *slave, if (ret) goto unlock; - ret = sdw_slave_port_config(slave, s_rt, port_config); + ret = sdw_slave_port_config(slave, s_rt, port_config, + stream->type == SDW_STREAM_BPT); if (ret) goto unlock; diff --git a/drivers/soundwire/sysfs_local.h b/drivers/soundwire/sysfs_local.h index 7268bc24c538..fa048e112629 100644 --- a/drivers/soundwire/sysfs_local.h +++ b/drivers/soundwire/sysfs_local.h @@ -11,8 +11,10 @@ /* basic attributes to report status of Slave (attachment, dev_num) */ extern const struct attribute_group *sdw_slave_status_attr_groups[]; +/* attributes for all soundwire devices */ +extern const struct attribute_group *sdw_attr_groups[]; + /* additional device-managed properties reported after driver probe */ -int sdw_slave_sysfs_init(struct sdw_slave *slave); int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave); #endif /* __SDW_SYSFS_LOCAL_H */ diff --git a/drivers/soundwire/sysfs_slave.c b/drivers/soundwire/sysfs_slave.c index 3210359cd944..c5c22d1708ec 100644 --- a/drivers/soundwire/sysfs_slave.c +++ b/drivers/soundwire/sysfs_slave.c @@ -105,7 +105,10 @@ static struct attribute *slave_attrs[] = { &dev_attr_modalias.attr, NULL, }; -ATTRIBUTE_GROUPS(slave); + +static const struct attribute_group slave_attr_group = { + .attrs = slave_attrs, +}; static struct attribute *slave_dev_attrs[] = { &dev_attr_mipi_revision.attr, @@ -126,10 +129,6 @@ static struct attribute *slave_dev_attrs[] = { NULL, }; -/* - * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory - * for device-level properties - */ static const struct attribute_group sdw_slave_dev_attr_group = { .attrs = slave_dev_attrs, .name = "dev-properties", @@ -181,45 +180,42 @@ static struct attribute *dp0_attrs[] = { NULL, }; -/* - * we don't use ATTRIBUTES_GROUP here since we want to add a subdirectory - * for dp0-level properties - */ -static const struct attribute_group dp0_group = { - .attrs = dp0_attrs, - .name = "dp0", -}; - -int sdw_slave_sysfs_init(struct sdw_slave *slave) +static umode_t dp0_attr_visible(struct kobject *kobj, struct attribute *attr, + int n) { - int ret; + struct sdw_slave *slave = dev_to_sdw_dev(kobj_to_dev(kobj)); - ret = devm_device_add_groups(&slave->dev, slave_groups); - if (ret < 0) - return ret; + if (slave->prop.dp0_prop) + return attr->mode; + return 0; +} - ret = devm_device_add_group(&slave->dev, &sdw_slave_dev_attr_group); - if (ret < 0) - return ret; +static bool dp0_group_visible(struct kobject *kobj) +{ + struct sdw_slave *slave = dev_to_sdw_dev(kobj_to_dev(kobj)); - if (slave->prop.dp0_prop) { - ret = devm_device_add_group(&slave->dev, &dp0_group); - if (ret < 0) - return ret; - } + if (slave->prop.dp0_prop) + return true; + return false; +} +DEFINE_SYSFS_GROUP_VISIBLE(dp0); - if (slave->prop.source_ports || slave->prop.sink_ports) { - ret = sdw_slave_sysfs_dpn_init(slave); - if (ret < 0) - return ret; - } +static const struct attribute_group dp0_group = { + .attrs = dp0_attrs, + .is_visible = SYSFS_GROUP_VISIBLE(dp0), + .name = "dp0", +}; - return 0; -} +const struct attribute_group *sdw_attr_groups[] = { + &slave_attr_group, + &sdw_slave_dev_attr_group, + &dp0_group, + NULL, +}; /* * the status is shown in capital letters for UNATTACHED and RESERVED - * on purpose, to highligh users to the fact that these status values + * on purpose, to highlight users to the fact that these status values * are not expected. */ static const char *const slave_status[] = { diff --git a/drivers/soundwire/sysfs_slave_dpn.c b/drivers/soundwire/sysfs_slave_dpn.c index c4b6543c09fd..a3fb380ee519 100644 --- a/drivers/soundwire/sysfs_slave_dpn.c +++ b/drivers/soundwire/sysfs_slave_dpn.c @@ -283,6 +283,9 @@ int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave) int ret; int i; + if (!slave->prop.source_ports && !slave->prop.sink_ports) + return 0; + mask = slave->prop.source_ports; for_each_set_bit(i, &mask, 32) { ret = add_all_attributes(&slave->dev, i, 1); |