Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# SoundWire subsystem configuration
#
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
typically related to audio functions. SoundWire interface is
optimized to integrate audio devices in mobile or mobile inspired
systems. Say Y to enable this subsystem, N if you do not have such
a device
if SOUNDWIRE
comment "SoundWire Devices"
config SOUNDWIRE_AMD
tristate "AMD SoundWire Manager driver"
select SOUNDWIRE_GENERIC_ALLOCATION
depends on ACPI && SND_SOC
help
SoundWire AMD Manager driver.
If you have an AMD platform which has a SoundWire Manager then
enable this config option to get the SoundWire support for that
device.
config SOUNDWIRE_CADENCE
tristate
select CRC8
config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"
select SOUNDWIRE_CADENCE
select SOUNDWIRE_GENERIC_ALLOCATION
select AUXILIARY_BUS
depends on ACPI && SND_SOC
depends on SND_SOC_SOF_HDA_MLINK || !SND_SOC_SOF_HDA_MLINK
depends on SND_HDA_CORE || !SND_HDA_ALIGNED_MMIO
help
SoundWire Intel Master driver.
If you have an Intel platform which has a SoundWire Master then
enable this config option to get the SoundWire support for that
device.
config SOUNDWIRE_QCOM
tristate "Qualcomm SoundWire Master driver"
imply SLIMBUS
depends on SND_SOC
help
SoundWire Qualcomm Master driver.
If you have an Qualcomm platform which has a SoundWire Master then
enable this config option to get the SoundWire support for that
device
config SOUNDWIRE_GENERIC_ALLOCATION
tristate
endif
+38
View File
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for soundwire core
#
#Bus Objs
soundwire-bus-y := bus_type.o bus.o master.o slave.o mipi_disco.o stream.o \
sysfs_slave.o sysfs_slave_dpn.o
obj-$(CONFIG_SOUNDWIRE) += soundwire-bus.o
soundwire-generic-allocation-objs := generic_bandwidth_allocation.o
obj-$(CONFIG_SOUNDWIRE_GENERIC_ALLOCATION) += soundwire-generic-allocation.o
ifdef CONFIG_DEBUG_FS
soundwire-bus-y += debugfs.o
endif
ifdef CONFIG_IRQ_DOMAIN
soundwire-bus-y += irq.o
endif
#AMD driver
soundwire-amd-y := amd_init.o amd_manager.o
obj-$(CONFIG_SOUNDWIRE_AMD) += soundwire-amd.o
#Cadence Objs
soundwire-cadence-y := cadence_master.o
obj-$(CONFIG_SOUNDWIRE_CADENCE) += soundwire-cadence.o
#Intel driver
soundwire-intel-y := intel.o intel_ace2x.o intel_ace2x_debugfs.o \
intel_auxdevice.o intel_init.o dmi-quirks.o \
intel_bus_common.o
obj-$(CONFIG_SOUNDWIRE_INTEL) += soundwire-intel.o
#Qualcomm driver
soundwire-qcom-y := qcom.o
obj-$(CONFIG_SOUNDWIRE_QCOM) += soundwire-qcom.o
+229
View File
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
* SoundWire AMD Manager Initialize routines
*
* Initializes and creates SDW devices based on ACPI and Hardware values
*
* Copyright 2024 Advanced Micro Devices, Inc.
*/
#include <linux/acpi.h>
#include <linux/cleanup.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include "amd_init.h"
#define ACP_PAD_PULLDOWN_CTRL 0x0001448
#define ACP_SW_PAD_KEEPER_EN 0x0001454
#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 pad_keeper_en, pad_pulldown_ctrl_mask;
switch (link_mask) {
case 1:
pad_keeper_en = AMD_SDW0_PAD_EN;
pad_pulldown_ctrl_mask = AMD_SDW0_PAD_CTRL_MASK;
break;
case 2:
pad_keeper_en = AMD_SDW1_PAD_EN;
pad_pulldown_ctrl_mask = AMD_SDW1_PAD_CTRL_MASK;
break;
case 3:
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;
}
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;
}
static int sdw_amd_cleanup(struct sdw_amd_ctx *ctx)
{
int i;
for (i = 0; i < ctx->count; i++) {
if (!(ctx->link_mask & BIT(i)))
continue;
platform_device_unregister(ctx->pdev[i]);
}
return 0;
}
static struct sdw_amd_ctx *sdw_amd_probe_controller(struct sdw_amd_res *res)
{
struct sdw_amd_ctx *ctx;
struct acpi_device *adev;
struct acp_sdw_pdata sdw_pdata[2];
struct platform_device_info pdevinfo[2];
u32 link_mask;
int count, index;
int ret;
if (!res)
return NULL;
adev = acpi_fetch_acpi_dev(res->handle);
if (!adev)
return NULL;
if (!res->count)
return NULL;
count = res->count;
dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
ret = amd_enable_sdw_pads(res->mmio_base, res->link_mask, res->parent);
if (ret)
return NULL;
/*
* we need to alloc/free memory manually and can't use devm:
* this routine may be called from a workqueue, and not from
* the parent .probe.
* If devm_ was used, the memory might never be freed on errors.
*/
ctx = kzalloc_obj(*ctx);
if (!ctx)
return NULL;
ctx->count = count;
ctx->link_mask = res->link_mask;
struct resource *sdw_res __free(kfree) = kzalloc_obj(*sdw_res);
if (!sdw_res) {
kfree(ctx);
return NULL;
}
sdw_res->flags = IORESOURCE_MEM;
sdw_res->start = res->addr;
sdw_res->end = res->addr + res->reg_range;
memset(&pdevinfo, 0, sizeof(pdevinfo));
link_mask = ctx->link_mask;
for (index = 0; index < count; index++) {
if (!(link_mask & BIT(index)))
continue;
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;
pdevinfo[index].num_res = 1;
pdevinfo[index].res = sdw_res;
pdevinfo[index].data = &sdw_pdata[index];
pdevinfo[index].size_data = sizeof(struct acp_sdw_pdata);
pdevinfo[index].fwnode = acpi_fwnode_handle(adev);
ctx->pdev[index] = platform_device_register_full(&pdevinfo[index]);
if (IS_ERR(ctx->pdev[index]))
goto err;
}
return ctx;
err:
while (index--) {
if (!(link_mask & BIT(index)))
continue;
platform_device_unregister(ctx->pdev[index]);
}
kfree(ctx);
return NULL;
}
static int sdw_amd_startup(struct sdw_amd_ctx *ctx)
{
struct amd_sdw_manager *amd_manager;
int i, ret;
/* Startup SDW Manager devices */
for (i = 0; i < ctx->count; i++) {
if (!(ctx->link_mask & BIT(i)))
continue;
amd_manager = dev_get_drvdata(&ctx->pdev[i]->dev);
ret = amd_sdw_manager_start(amd_manager);
if (ret)
return ret;
}
return 0;
}
int sdw_amd_probe(struct sdw_amd_res *res, struct sdw_amd_ctx **sdw_ctx)
{
*sdw_ctx = sdw_amd_probe_controller(res);
if (!*sdw_ctx)
return -ENODEV;
return sdw_amd_startup(*sdw_ctx);
}
EXPORT_SYMBOL_NS(sdw_amd_probe, "SOUNDWIRE_AMD_INIT");
void sdw_amd_exit(struct sdw_amd_ctx *ctx)
{
sdw_amd_cleanup(ctx);
kfree(ctx->peripherals);
kfree(ctx);
}
EXPORT_SYMBOL_NS(sdw_amd_exit, "SOUNDWIRE_AMD_INIT");
int sdw_amd_get_slave_info(struct sdw_amd_ctx *ctx)
{
struct amd_sdw_manager *amd_manager;
struct sdw_bus *bus;
struct sdw_slave *slave;
struct list_head *node;
int index;
int i = 0;
int num_slaves = 0;
for (index = 0; index < ctx->count; index++) {
if (!(ctx->link_mask & BIT(index)))
continue;
amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
if (!amd_manager)
return -ENODEV;
bus = &amd_manager->bus;
/* Calculate number of slaves */
list_for_each(node, &bus->slaves)
num_slaves++;
}
ctx->peripherals = kmalloc_flex(*ctx->peripherals, array, num_slaves);
if (!ctx->peripherals)
return -ENOMEM;
ctx->peripherals->num_peripherals = num_slaves;
for (index = 0; index < ctx->count; index++) {
if (!(ctx->link_mask & BIT(index)))
continue;
amd_manager = dev_get_drvdata(&ctx->pdev[index]->dev);
if (amd_manager) {
bus = &amd_manager->bus;
list_for_each_entry(slave, &bus->slaves, node) {
ctx->peripherals->array[i] = slave;
i++;
}
}
}
return 0;
}
EXPORT_SYMBOL_NS(sdw_amd_get_slave_info, "SOUNDWIRE_AMD_INIT");
MODULE_AUTHOR("Vijendar.Mukunda@amd.com");
MODULE_DESCRIPTION("AMD SoundWire Init Library");
MODULE_LICENSE("Dual BSD/GPL");
+21
View File
@@ -0,0 +1,21 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/*
* Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved.
*/
#ifndef __AMD_INIT_H
#define __AMD_INIT_H
#include <linux/soundwire/sdw_amd.h>
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
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/*
* Copyright (C) 2023-24 Advanced Micro Devices, Inc. All rights reserved.
*/
#ifndef __AMD_MANAGER_H
#define __AMD_MANAGER_H
#include <linux/soundwire/sdw_amd.h>
#define SDW_MANAGER_REG_OFFSET 0xc00
#define AMD_SDW_DEFAULT_ROWS 50
#define AMD_SDW_DEFAULT_COLUMNS 10
#define ACP_PAD_PULLDOWN_CTRL 0x0001448
#define ACP_SW_PAD_KEEPER_EN 0x0001454
#define ACP_SW0_WAKE_EN 0x0001458
#define ACP_EXTERNAL_INTR_CNTL0 0x0001a04
#define ACP_EXTERNAL_INTR_STAT0 0x0001a0c
#define ACP_EXTERNAL_INTR_CNTL(i) (ACP_EXTERNAL_INTR_CNTL0 + ((i) * 4))
#define ACP_EXTERNAL_INTR_STAT(i) (ACP_EXTERNAL_INTR_STAT0 + ((i) * 4))
#define ACP_SW_WAKE_EN(i) (ACP_SW0_WAKE_EN + ((i) * 8))
#define ACP_SW_EN 0x0003000
#define ACP_SW_EN_STATUS 0x0003004
#define ACP_SW_FRAMESIZE 0x0003008
#define ACP_SW_SSP_COUNTER 0x000300c
#define ACP_SW_AUDIO0_TX_EN 0x0003010
#define ACP_SW_AUDIO0_TX_EN_STATUS 0x0003014
#define ACP_SW_AUDIO0_TX_FRAME_FORMAT 0x0003018
#define ACP_SW_AUDIO0_TX_SAMPLEINTERVAL 0x000301c
#define ACP_SW_AUDIO0_TX_HCTRL_DP0 0x0003020
#define ACP_SW_AUDIO0_TX_HCTRL_DP1 0x0003024
#define ACP_SW_AUDIO0_TX_HCTRL_DP2 0x0003028
#define ACP_SW_AUDIO0_TX_HCTRL_DP3 0x000302c
#define ACP_SW_AUDIO0_TX_OFFSET_DP0 0x0003030
#define ACP_SW_AUDIO0_TX_OFFSET_DP1 0x0003034
#define ACP_SW_AUDIO0_TX_OFFSET_DP2 0x0003038
#define ACP_SW_AUDIO0_TX_OFFSET_DP3 0x000303c
#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0 0x0003040
#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP1 0x0003044
#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP2 0x0003048
#define ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP3 0x000304c
#define ACP_SW_AUDIO1_TX_EN 0x0003050
#define ACP_SW_AUDIO1_TX_EN_STATUS 0x0003054
#define ACP_SW_AUDIO1_TX_FRAME_FORMAT 0x0003058
#define ACP_SW_AUDIO1_TX_SAMPLEINTERVAL 0x000305c
#define ACP_SW_AUDIO1_TX_HCTRL 0x0003060
#define ACP_SW_AUDIO1_TX_OFFSET 0x0003064
#define ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0 0x0003068
#define ACP_SW_AUDIO2_TX_EN 0x000306c
#define ACP_SW_AUDIO2_TX_EN_STATUS 0x0003070
#define ACP_SW_AUDIO2_TX_FRAME_FORMAT 0x0003074
#define ACP_SW_AUDIO2_TX_SAMPLEINTERVAL 0x0003078
#define ACP_SW_AUDIO2_TX_HCTRL 0x000307c
#define ACP_SW_AUDIO2_TX_OFFSET 0x0003080
#define ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0 0x0003084
#define ACP_SW_AUDIO0_RX_EN 0x0003088
#define ACP_SW_AUDIO0_RX_EN_STATUS 0x000308c
#define ACP_SW_AUDIO0_RX_FRAME_FORMAT 0x0003090
#define ACP_SW_AUDIO0_RX_SAMPLEINTERVAL 0x0003094
#define ACP_SW_AUDIO0_RX_HCTRL_DP0 0x0003098
#define ACP_SW_AUDIO0_RX_HCTRL_DP1 0x000309c
#define ACP_SW_AUDIO0_RX_HCTRL_DP2 0x0003100
#define ACP_SW_AUDIO0_RX_HCTRL_DP3 0x0003104
#define ACP_SW_AUDIO0_RX_OFFSET_DP0 0x0003108
#define ACP_SW_AUDIO0_RX_OFFSET_DP1 0x000310c
#define ACP_SW_AUDIO0_RX_OFFSET_DP2 0x0003110
#define ACP_SW_AUDIO0_RX_OFFSET_DP3 0x0003114
#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0 0x0003118
#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP1 0x000311c
#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP2 0x0003120
#define ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP3 0x0003124
#define ACP_SW_AUDIO1_RX_EN 0x0003128
#define ACP_SW_AUDIO1_RX_EN_STATUS 0x000312c
#define ACP_SW_AUDIO1_RX_FRAME_FORMAT 0x0003130
#define ACP_SW_AUDIO1_RX_SAMPLEINTERVAL 0x0003134
#define ACP_SW_AUDIO1_RX_HCTRL 0x0003138
#define ACP_SW_AUDIO1_RX_OFFSET 0x000313c
#define ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0 0x0003140
#define ACP_SW_AUDIO2_RX_EN 0x0003144
#define ACP_SW_AUDIO2_RX_EN_STATUS 0x0003148
#define ACP_SW_AUDIO2_RX_FRAME_FORMAT 0x000314c
#define ACP_SW_AUDIO2_RX_SAMPLEINTERVAL 0x0003150
#define ACP_SW_AUDIO2_RX_HCTRL 0x0003154
#define ACP_SW_AUDIO2_RX_OFFSET 0x0003158
#define ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0 0x000315c
#define ACP_SW_BPT_PORT_EN 0x0003160
#define ACP_SW_BPT_PORT_EN_STATUS 0x0003164
#define ACP_SW_BPT_PORT_FRAME_FORMAT 0x0003168
#define ACP_SW_BPT_PORT_SAMPLEINTERVAL 0x000316c
#define ACP_SW_BPT_PORT_HCTRL 0x0003170
#define ACP_SW_BPT_PORT_OFFSET 0x0003174
#define ACP_SW_BPT_PORT_CHANNEL_ENABLE 0x0003178
#define ACP_SW_BPT_PORT_FIRST_BYTE_ADDR 0x000317c
#define ACP_SW_CLK_RESUME_CTRL 0x0003180
#define ACP_SW_CLK_RESUME_DELAY_CNTR 0x0003184
#define ACP_SW_BUS_RESET_CTRL 0x0003188
#define ACP_SW_PRBS_ERR_STATUS 0x000318c
#define ACP_SW_IMM_CMD_UPPER_WORD 0x0003230
#define ACP_SW_IMM_CMD_LOWER_QWORD 0x0003234
#define ACP_SW_IMM_RESP_UPPER_WORD 0x0003238
#define ACP_SW_IMM_RESP_LOWER_QWORD 0x000323c
#define ACP_SW_IMM_CMD_STS 0x0003240
#define ACP_SW_BRA_BASE_ADDRESS 0x0003244
#define ACP_SW_BRA_TRANSFER_SIZE 0x0003248
#define ACP_SW_BRA_DMA_BUSY 0x000324c
#define ACP_SW_BRA_RESP 0x0003250
#define ACP_SW_BRA_RESP_FRAME_ADDR 0x0003254
#define ACP_SW_BRA_CURRENT_TRANSFER_SIZE 0x0003258
#define ACP_SW_STATE_CHANGE_STATUS_0TO7 0x000325c
#define ACP_SW_STATE_CHANGE_STATUS_8TO11 0x0003260
#define ACP_SW_STATE_CHANGE_STATUS_MASK_0TO7 0x0003264
#define ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11 0x0003268
#define ACP_SW_CLK_FREQUENCY_CTRL 0x000326c
#define ACP_SW_ERROR_INTR_MASK 0x0003270
#define ACP_SW_PHY_TEST_MODE_DATA_OFF 0x0003274
#define ACP_DELAY_US 10
#define AMD_SDW_TIMEOUT 1000
#define AMD_SDW_DEFAULT_CLK_FREQ 12000000
#define AMD_SDW_MCP_RESP_ACK BIT(0)
#define AMD_SDW_MCP_RESP_NACK BIT(1)
#define AMD_SDW_MCP_RESP_RDATA GENMASK(14, 7)
#define AMD_SDW_MCP_CMD_SSP_TAG BIT(31)
#define AMD_SDW_MCP_CMD_COMMAND GENMASK(14, 12)
#define AMD_SDW_MCP_CMD_DEV_ADDR GENMASK(11, 8)
#define AMD_SDW_MCP_CMD_REG_ADDR_HIGH GENMASK(7, 0)
#define AMD_SDW_MCP_CMD_REG_ADDR_LOW GENMASK(31, 24)
#define AMD_SDW_MCP_CMD_REG_DATA GENMASK(14, 7)
#define AMD_SDW_MCP_SLAVE_STAT_0_3 GENMASK(14, 7)
#define AMD_SDW_MCP_SLAVE_STAT_4_11 GENMASK_ULL(39, 24)
#define AMD_SDW_MCP_SLAVE_STATUS_MASK GENMASK(1, 0)
#define AMD_SDW_MCP_SLAVE_STATUS_BITS GENMASK(3, 2)
#define AMD_SDW_MCP_SLAVE_STATUS_8TO_11 GENMASK_ULL(15, 0)
#define AMD_SDW_MCP_SLAVE_STATUS_VALID_MASK(x) BIT(((x) * 4))
#define AMD_SDW_MCP_SLAVE_STAT_SHIFT_MASK(x) (((x) * 4) + 1)
#define AMD_SDW_MASTER_SUSPEND_DELAY_MS 2000
#define AMD_SDW_QUIRK_MASK_BUS_ENABLE BIT(0)
#define AMD_SDW_IMM_RES_VALID 1
#define AMD_SDW_IMM_CMD_BUSY 2
#define AMD_SDW_ENABLE 1
#define AMD_SDW_DISABLE 0
#define AMD_SDW_BUS_RESET_CLEAR_REQ 0
#define AMD_SDW_BUS_RESET_REQ 1
#define AMD_SDW_BUS_RESET_DONE 2
#define AMD_SDW_BUS_BASE_FREQ 24000000
#define AMD_SDW0_EXT_INTR_MASK 0x200000
#define AMD_SDW1_EXT_INTR_MASK 4
#define AMD_SDW_IRQ_MASK_0TO7 0x77777777
#define AMD_SDW_IRQ_MASK_8TO11 0x000c7777
#define AMD_SDW_IRQ_ERROR_MASK 0xff
#define AMD_SDW_MAX_FREQ_NUM 1
#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
#define AMD_DPN_FRAME_FMT_PFM GENMASK(1, 0)
#define AMD_DPN_FRAME_FMT_PDM GENMASK(3, 2)
#define AMD_DPN_FRAME_FMT_BLK_PKG_MODE BIT(4)
#define AMD_DPN_FRAME_FMT_BLK_GRP_CTRL GENMASK(6, 5)
#define AMD_DPN_FRAME_FMT_WORD_LEN GENMASK(12, 7)
#define AMD_DPN_FRAME_FMT_PCM_OR_PDM BIT(13)
#define AMD_DPN_HCTRL_HSTOP GENMASK(3, 0)
#define AMD_DPN_HCTRL_HSTART GENMASK(7, 4)
#define AMD_DPN_OFFSET_CTRL_1 GENMASK(7, 0)
#define AMD_DPN_OFFSET_CTRL_2 GENMASK(15, 8)
#define AMD_DPN_CH_EN_LCTRL GENMASK(2, 0)
#define AMD_DPN_CH_EN_CHMASK GENMASK(10, 3)
#define AMD_SDW_STAT_MAX_RETRY_COUNT 100
#define AMD_SDW0_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7f9f
#define AMD_SDW1_PAD_PULLDOWN_CTRL_ENABLE_MASK 0x7ffa
#define AMD_SDW0_PAD_PULLDOWN_CTRL_DISABLE_MASK 0x60
#define AMD_SDW1_PAD_PULLDOWN_CTRL_DISABLE_MASK 5
#define AMD_SDW0_PAD_KEEPER_EN_MASK 1
#define AMD_SDW1_PAD_KEEPER_EN_MASK 0x10
#define AMD_SDW0_PAD_KEEPER_DISABLE_MASK 0x1e
#define AMD_SDW1_PAD_KEEPER_DISABLE_MASK 0xf
#define AMD_SDW_PREQ_INTR_STAT BIT(19)
#define AMD_SDW_CLK_STOP_DONE 1
#define AMD_SDW_CLK_RESUME_REQ 2
#define AMD_SDW_CLK_RESUME_DONE 3
#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
struct sdw_manager_dp_reg {
u32 frame_fmt_reg;
u32 sample_int_reg;
u32 hctrl_dp0_reg;
u32 offset_reg;
u32 lane_ctrl_ch_en_reg;
};
/*
* SDW0 Manager instance registers 6 CPU DAI (3 TX & 3 RX Ports)
* whereas SDW1 Manager Instance registers 2 CPU DAI (one TX & one RX port)
* Below is the CPU DAI <->Manager port number mapping
* i.e SDW0 Pin0 -> port number 0 -> AUDIO0 TX
* SDW0 Pin1 -> Port number 1 -> AUDIO1 TX
* SDW0 Pin2 -> Port number 2 -> AUDIO2 TX
* SDW0 Pin3 -> port number 3 -> AUDIO0 RX
* SDW0 Pin4 -> Port number 4 -> AUDIO1 RX
* SDW0 Pin5 -> Port number 5 -> AUDIO2 RX
* Whereas for SDW1 instance
* SDW1 Pin0 -> port number 0 -> AUDIO1 TX
* SDW1 Pin1 -> Port number 1 -> AUDIO1 RX
* Same mapping should be used for programming DMA controller registers in SoundWire DMA driver.
* i.e if AUDIO0 TX channel is selected then we need to use AUDIO0 TX registers for DMA programming
* in SoundWire DMA driver.
*/
static struct sdw_manager_dp_reg 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,
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
{ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
{ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
{ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
{ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
};
static struct sdw_manager_dp_reg 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
};
#endif
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/* Copyright(c) 2015-17 Intel Corporation. */
#ifndef __SDW_BUS_H
#define __SDW_BUS_H
#define DEFAULT_BANK_SWITCH_TIMEOUT 3000
#define DEFAULT_PROBE_TIMEOUT 2000
u64 sdw_dmi_override_adr(struct sdw_bus *bus, u64 addr);
#if IS_ENABLED(CONFIG_ACPI)
int sdw_acpi_find_slaves(struct sdw_bus *bus);
#else
static inline int sdw_acpi_find_slaves(struct sdw_bus *bus)
{
return -ENOTSUPP;
}
#endif
int sdw_of_find_slaves(struct sdw_bus *bus);
void sdw_extract_slave_id(struct sdw_bus *bus,
u64 addr, struct sdw_slave_id *id);
int sdw_slave_add(struct sdw_bus *bus, struct sdw_slave_id *id,
struct fwnode_handle *fwnode);
int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
struct fwnode_handle *fwnode);
int sdw_master_device_del(struct sdw_bus *bus);
#ifdef CONFIG_DEBUG_FS
void sdw_bus_debugfs_init(struct sdw_bus *bus);
void sdw_bus_debugfs_exit(struct sdw_bus *bus);
void sdw_slave_debugfs_init(struct sdw_slave *slave);
void sdw_slave_debugfs_exit(struct sdw_slave *slave);
void sdw_debugfs_init(void);
void sdw_debugfs_exit(void);
#else
static inline void sdw_bus_debugfs_init(struct sdw_bus *bus) {}
static inline void sdw_bus_debugfs_exit(struct sdw_bus *bus) {}
static inline void sdw_slave_debugfs_init(struct sdw_slave *slave) {}
static inline void sdw_slave_debugfs_exit(struct sdw_slave *slave) {}
static inline void sdw_debugfs_init(void) {}
static inline void sdw_debugfs_exit(void) {}
#endif
enum {
SDW_MSG_FLAG_READ = 0,
SDW_MSG_FLAG_WRITE,
};
/**
* struct sdw_msg - Message structure
* @addr: Register address accessed in the Slave
* @len: number of messages
* @dev_num: Slave device number
* @addr_page1: SCP address page 1 Slave register
* @addr_page2: SCP address page 2 Slave register
* @flags: transfer flags, indicate if xfer is read or write
* @buf: message data buffer
* @ssp_sync: Send message at SSP (Stream Synchronization Point)
* @page: address requires paging
*/
struct sdw_msg {
u16 addr;
u16 len;
u8 dev_num;
u8 addr_page1;
u8 addr_page2;
u8 flags;
u8 *buf;
bool ssp_sync;
bool page;
};
/**
* struct sdw_btp_section - Message section 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.
* @buf: section data buffer (filled by host for write, filled
* by Peripheral hardware for reads)
*/
struct sdw_bpt_section {
u32 addr;
u32 len;
u8 *buf;
};
/**
* struct sdw_btp_msg - Message structure
* @sec: Pointer to array of sections
* @sections: Number of sections in the array
* @dev_num: Slave device number
* @flags: transfer flags, indicate if xfer is read or write
*/
struct sdw_bpt_msg {
struct sdw_bpt_section *sec;
int sections;
u8 dev_num;
u8 flags;
};
#define SDW_DOUBLE_RATE_FACTOR 2
#define SDW_STRM_RATE_GROUPING 1
extern int sdw_rows[SDW_FRAME_ROWS];
extern int sdw_cols[SDW_FRAME_COLS];
int sdw_find_row_index(int row);
int sdw_find_col_index(int col);
/**
* sdw_port_runtime: Runtime port parameters for Master or Slave
*
* @num: Port number. For audio streams, valid port number ranges from
* [1,14]
* @ch_mask: Channel mask
* @transport_params: Transport parameters
* @port_params: Port parameters
* @port_node: List node for Master or Slave port_list
* @lane: Which lane is used
*
* SoundWire spec has no mention of ports for Master interface but the
* concept is logically extended.
*/
struct sdw_port_runtime {
int num;
int ch_mask;
struct sdw_transport_params transport_params;
struct sdw_port_params port_params;
struct list_head port_node;
unsigned int lane;
};
/**
* sdw_slave_runtime: Runtime Stream parameters for Slave
*
* @slave: Slave handle
* @direction: Data direction for Slave
* @ch_count: Number of channels handled by the Slave for
* this stream
* @m_rt_node: sdw_master_runtime list node
* @port_list: List of Slave Ports configured for this stream
*/
struct sdw_slave_runtime {
struct sdw_slave *slave;
enum sdw_data_direction direction;
unsigned int ch_count;
struct list_head m_rt_node;
struct list_head port_list;
};
/**
* sdw_master_runtime: Runtime stream parameters for Master
*
* @bus: Bus handle
* @stream: Stream runtime handle
* @direction: Data direction for Master
* @ch_count: Number of channels handled by the Master for
* this stream, can be zero.
* @slave_rt_list: Slave runtime list
* @port_list: List of Master Ports configured for this stream, can be zero.
* @stream_node: sdw_stream_runtime master_list node
* @bus_node: sdw_bus m_rt_list node
*/
struct sdw_master_runtime {
struct sdw_bus *bus;
struct sdw_stream_runtime *stream;
enum sdw_data_direction direction;
unsigned int ch_count;
struct list_head slave_rt_list;
struct list_head port_list;
struct list_head stream_node;
struct list_head bus_node;
};
struct sdw_transport_data {
int hstart;
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,
enum sdw_data_direction direction,
unsigned int port_num);
int sdw_configure_dpn_intr(struct sdw_slave *slave, int port,
bool enable, int mask);
int sdw_transfer(struct sdw_bus *bus, struct sdw_msg *msg);
int sdw_transfer_defer(struct sdw_bus *bus, struct sdw_msg *msg);
#define SDW_READ_INTR_CLEAR_RETRY 10
int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
u32 addr, size_t count, u16 dev_num, u8 flags, u8 *buf);
/* Fill transport parameter data structure */
static inline void sdw_fill_xport_params(struct sdw_transport_params *params,
int port_num, bool grp_ctrl_valid,
int grp_ctrl, int sample_int,
int off1, int off2,
int hstart, int hstop,
int pack_mode, int lane_ctrl)
{
params->port_num = port_num;
params->blk_grp_ctrl_valid = grp_ctrl_valid;
params->blk_grp_ctrl = grp_ctrl;
params->sample_interval = sample_int;
params->offset1 = off1;
params->offset2 = off2;
params->hstart = hstart;
params->hstop = hstop;
params->blk_pkg_mode = pack_mode;
params->lane_ctrl = lane_ctrl;
}
/* Fill port parameter data structure */
static inline void sdw_fill_port_params(struct sdw_port_params *params,
int port_num, int bps,
int flow_mode, int data_mode)
{
params->num = port_num;
params->bps = bps;
params->flow_mode = flow_mode;
params->data_mode = data_mode;
}
/* broadcast read/write for tests */
int sdw_bread_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr);
int sdw_bwrite_no_pm_unlocked(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value);
/*
* At the moment we only track Master-initiated hw_reset.
* Additional fields can be added as needed
*/
#define SDW_UNATTACH_REQUEST_MASTER_RESET BIT(0)
void sdw_clear_slave_status(struct sdw_bus *bus, u32 request);
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size);
void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
struct sdw_transport_data *t_data);
#endif /* __SDW_BUS_H */
+245
View File
@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2015-17 Intel Corporation.
#include <linux/module.h>
#include <linux/pm_domain.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "irq.h"
#include "sysfs_local.h"
/**
* sdw_get_device_id - find the matching SoundWire device id
* @slave: SoundWire Slave Device
* @drv: SoundWire Slave Driver
*
* The match is done by comparing the mfg_id and part_id from the
* struct sdw_device_id.
*/
static const struct sdw_device_id *
sdw_get_device_id(struct sdw_slave *slave, const struct sdw_driver *drv)
{
const struct sdw_device_id *id;
for (id = drv->id_table; id && id->mfg_id; id++)
if (slave->id.mfg_id == id->mfg_id &&
slave->id.part_id == id->part_id &&
(!id->sdw_version ||
slave->id.sdw_version == id->sdw_version) &&
(!id->class_id ||
slave->id.class_id == id->class_id))
return id;
return NULL;
}
static int sdw_bus_match(struct device *dev, const struct device_driver *ddrv)
{
struct sdw_slave *slave;
const struct sdw_driver *drv;
int ret = 0;
if (is_sdw_slave(dev)) {
slave = dev_to_sdw_dev(dev);
drv = drv_to_sdw_driver(ddrv);
ret = !!sdw_get_device_id(slave, drv);
}
return ret;
}
int sdw_slave_modalias(const struct sdw_slave *slave, char *buf, size_t size)
{
/* modalias is sdw:m<mfg_id>p<part_id>v<version>c<class_id> */
return snprintf(buf, size, "sdw:m%04Xp%04Xv%02Xc%02X\n",
slave->id.mfg_id, slave->id.part_id,
slave->id.sdw_version, slave->id.class_id);
}
int sdw_slave_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct sdw_slave *slave = dev_to_sdw_dev(dev);
char modalias[32];
sdw_slave_modalias(slave, modalias, sizeof(modalias));
if (add_uevent_var(env, "MODALIAS=%s", modalias))
return -ENOMEM;
return 0;
}
static int sdw_bus_probe(struct device *dev)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
const struct sdw_device_id *id;
int ret;
/*
* fw description is mandatory to bind
*/
if (!dev->fwnode)
return -ENODEV;
if (!IS_ENABLED(CONFIG_ACPI) && !dev->of_node)
return -ENODEV;
id = sdw_get_device_id(slave, drv);
if (!id)
return -ENODEV;
/*
* attach to power domain but don't turn on (last arg)
*/
ret = dev_pm_domain_attach(dev, 0);
if (ret)
return ret;
ret = ida_alloc_max(&slave->bus->slave_ida, SDW_FW_MAX_DEVICES - 1, GFP_KERNEL);
if (ret < 0) {
dev_err(dev, "Failed to allocated ID: %d\n", ret);
return ret;
}
slave->index = ret;
ret = drv->probe(slave, id);
if (ret) {
ida_free(&slave->bus->slave_ida, slave->index);
return ret;
}
mutex_lock(&slave->sdw_dev_lock);
/* device is probed so let's read the properties now */
if (drv->ops && drv->ops->read_prop)
drv->ops->read_prop(slave);
if (slave->prop.use_domain_irq)
sdw_irq_create_mapping(slave);
/* init the dynamic sysfs attributes we need */
ret = sdw_slave_sysfs_dpn_init(slave);
if (ret < 0)
dev_warn(dev, "failed to initialise sysfs: %d\n", ret);
/*
* Check for valid clk_stop_timeout, use DisCo worst case value of
* 300ms
*
* TODO: check the timeouts and driver removal case
*/
if (slave->prop.clk_stop_timeout == 0)
slave->prop.clk_stop_timeout = 300;
slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout,
slave->prop.clk_stop_timeout);
slave->probed = true;
/*
* if the probe happened after the bus was started, notify the codec driver
* of the current hardware status to e.g. start the initialization.
* Errors are only logged as warnings to avoid failing the probe.
*/
if (drv->ops && drv->ops->update_status) {
ret = drv->ops->update_status(slave, slave->status);
if (ret < 0)
dev_warn(dev, "failed to update status at probe: %d\n", ret);
}
mutex_unlock(&slave->sdw_dev_lock);
dev_dbg(dev, "probe complete\n");
return 0;
}
static void sdw_bus_remove(struct device *dev)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
mutex_lock(&slave->sdw_dev_lock);
slave->probed = false;
mutex_unlock(&slave->sdw_dev_lock);
if (drv->remove)
drv->remove(slave);
ida_free(&slave->bus->slave_ida, slave->index);
}
static void sdw_bus_shutdown(struct device *dev)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
struct sdw_driver *drv = drv_to_sdw_driver(dev->driver);
if (dev->driver && drv->shutdown)
drv->shutdown(slave);
}
const struct bus_type sdw_bus_type = {
.name = "soundwire",
.match = sdw_bus_match,
.probe = sdw_bus_probe,
.remove = sdw_bus_remove,
.shutdown = sdw_bus_shutdown,
};
EXPORT_SYMBOL_GPL(sdw_bus_type);
/**
* __sdw_register_driver() - register a SoundWire Slave driver
* @drv: driver to register
* @owner: owning module/driver
*
* Return: zero on success, else a negative error code.
*/
int __sdw_register_driver(struct sdw_driver *drv, struct module *owner)
{
drv->driver.bus = &sdw_bus_type;
if (!drv->probe) {
pr_err("driver %s didn't provide SDW probe routine\n",
drv->driver.name);
return -EINVAL;
}
drv->driver.owner = owner;
drv->driver.dev_groups = sdw_attr_groups;
return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__sdw_register_driver);
/**
* sdw_unregister_driver() - unregisters the SoundWire Slave driver
* @drv: driver to unregister
*/
void sdw_unregister_driver(struct sdw_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(sdw_unregister_driver);
static int __init sdw_bus_init(void)
{
sdw_debugfs_init();
return bus_register(&sdw_bus_type);
}
static void __exit sdw_bus_exit(void)
{
sdw_debugfs_exit();
bus_unregister(&sdw_bus_type);
}
postcore_initcall(sdw_bus_init);
module_exit(sdw_bus_exit);
MODULE_DESCRIPTION("SoundWire bus");
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,238 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/* Copyright(c) 2015-17 Intel Corporation. */
#include <sound/soc.h>
#include "bus.h"
#ifndef __SDW_CADENCE_H
#define __SDW_CADENCE_H
#define SDW_CADENCE_GSYNC_KHZ 4 /* 4 kHz */
#define SDW_CADENCE_GSYNC_HZ (SDW_CADENCE_GSYNC_KHZ * 1000)
/*
* The Cadence IP supports up to 32 entries in the FIFO, though implementations
* can configure the IP to have a smaller FIFO.
*/
#define CDNS_MCP_IP_MAX_CMD_LEN 32
#define SDW_CADENCE_MCP_IP_OFFSET 0x4000
/**
* struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
*
* @num: pdi number
* @intel_alh_id: link identifier
* @l_ch_num: low channel for PDI
* @h_ch_num: high channel for PDI
* @ch_count: total channel count for PDI
* @dir: data direction
* @type: stream type, (only PCM supported)
*/
struct sdw_cdns_pdi {
int num;
int intel_alh_id;
int l_ch_num;
int h_ch_num;
int ch_count;
enum sdw_data_direction dir;
enum sdw_stream_type type;
};
/**
* struct sdw_cdns_streams: Cadence stream data structure
*
* @num_bd: number of bidirectional streams
* @num_in: number of input streams
* @num_out: number of output streams
* @num_ch_bd: number of bidirectional stream channels
* @num_ch_bd: number of input stream channels
* @num_ch_bd: number of output stream channels
* @num_pdi: total number of PDIs
* @bd: bidirectional streams
* @in: input streams
* @out: output streams
*/
struct sdw_cdns_streams {
unsigned int num_bd;
unsigned int num_in;
unsigned int num_out;
unsigned int num_ch_bd;
unsigned int num_ch_in;
unsigned int num_ch_out;
unsigned int num_pdi;
struct sdw_cdns_pdi *bd;
struct sdw_cdns_pdi *in;
struct sdw_cdns_pdi *out;
};
/**
* struct sdw_cdns_stream_config: stream configuration
*
* @pcm_bd: number of bidirectional PCM streams supported
* @pcm_in: number of input PCM streams supported
* @pcm_out: number of output PCM streams supported
*/
struct sdw_cdns_stream_config {
unsigned int pcm_bd;
unsigned int pcm_in;
unsigned int pcm_out;
};
/**
* struct sdw_cdns_dai_runtime: Cadence DAI runtime data
*
* @name: SoundWire stream name
* @stream: stream runtime
* @pdi: PDI used for this dai
* @bus: Bus handle
* @stream_type: Stream type
* @link_id: Master link id
* @suspended: status set when suspended, to be used in .prepare
* @paused: status set in .trigger, to be used in suspend
* @direction: stream direction
*/
struct sdw_cdns_dai_runtime {
char *name;
struct sdw_stream_runtime *stream;
struct sdw_cdns_pdi *pdi;
struct sdw_bus *bus;
enum sdw_stream_type stream_type;
int link_id;
bool suspended;
bool paused;
int direction;
};
/**
* struct sdw_cdns - Cadence driver context
* @dev: Linux device
* @bus: Bus handle
* @instance: instance number
* @ip_offset: version-dependent offset to access IP_MCP registers and fields
* @response_buf: SoundWire response buffer
* @tx_complete: Tx completion
* @ports: Data ports
* @num_ports: Total number of data ports
* @pcm: PCM streams
* @registers: Cadence registers
* @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;
struct sdw_bus bus;
unsigned int instance;
u32 ip_offset;
/*
* The datasheet says the RX FIFO AVAIL can be 2 entries more
* than the FIFO capacity, so allow for this.
*/
u32 response_buf[CDNS_MCP_IP_MAX_CMD_LEN + 2];
struct completion tx_complete;
struct sdw_cdns_port *ports;
int num_ports;
struct sdw_cdns_streams pcm;
int pdi_loopback_source;
int pdi_loopback_target;
void __iomem *registers;
bool link_up;
unsigned int msg_count;
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)
/* Exported symbols */
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);
int sdw_cdns_exit_reset(struct sdw_cdns *cdns);
int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns, bool state);
bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns);
int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake);
int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset);
#ifdef CONFIG_DEBUG_FS
void sdw_cdns_debugfs_init(struct sdw_cdns *cdns, struct dentry *root);
#endif
struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
u32 ch, u32 dir, int dai_id);
void sdw_cdns_config_stream(struct sdw_cdns *cdns,
u32 ch, u32 dir, struct sdw_cdns_pdi *pdi);
enum sdw_command_response
cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg);
enum sdw_command_response
cdns_xfer_msg_defer(struct sdw_bus *bus);
u32 cdns_read_ping_status(struct sdw_bus *bus);
int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params);
int cdns_set_sdw_stream(struct snd_soc_dai *dai,
void *stream, int direction);
void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string,
bool initial_delay, int reset_iterations);
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_bandwidth(int command, /* 0: write, 1: read */
int row, int col, int frame_rate,
unsigned int *tx_dma_bandwidth,
unsigned int *rx_dma_bandwidth);
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, struct sdw_bpt_section *sec, int num_sec,
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, struct sdw_bpt_section *sec, int num_sec,
int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
int *dma_buffer_total_bytes, unsigned int fake_size);
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,
struct sdw_bpt_section *sec, int num_sec, int num_frames,
int data_per_frame);
#endif /* __SDW_CADENCE_H */
+384
View File
@@ -0,0 +1,384 @@
// 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/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;
void sdw_bus_debugfs_init(struct sdw_bus *bus)
{
char name[16];
if (!sdw_debugfs_root)
return;
/* create the debugfs master-N */
snprintf(name, sizeof(name), "master-%d-%d", bus->controller_id, bus->link_id);
bus->debugfs = debugfs_create_dir(name, sdw_debugfs_root);
}
void sdw_bus_debugfs_exit(struct sdw_bus *bus)
{
debugfs_remove_recursive(bus->debugfs);
}
#define RD_BUF (3 * PAGE_SIZE)
static ssize_t sdw_sprintf(struct sdw_slave *slave,
char *buf, size_t pos, unsigned int reg)
{
int value;
value = sdw_read_no_pm(slave, reg);
if (value < 0)
return scnprintf(buf + pos, RD_BUF - pos, "%3x\tXX\n", reg);
else
return scnprintf(buf + pos, RD_BUF - pos,
"%3x\t%2x\n", reg, value);
}
static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
{
struct sdw_slave *slave = s_file->private;
ssize_t ret;
int i, j;
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);
return ret;
}
ret = scnprintf(buf, RD_BUF, "Register Value\n");
/* DP0 non-banked registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP0\n");
for (i = SDW_DP0_INT; i <= SDW_DP0_PREPARECTRL; i++)
ret += sdw_sprintf(slave, buf, ret, i);
/* DP0 Bank 0 registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n");
ret += sdw_sprintf(slave, buf, ret, SDW_DP0_CHANNELEN);
for (i = SDW_DP0_SAMPLECTRL1; i <= SDW_DP0_LANECTRL; i++)
ret += sdw_sprintf(slave, buf, ret, i);
/* DP0 Bank 1 registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n");
ret += sdw_sprintf(slave, buf, ret,
SDW_DP0_CHANNELEN + SDW_BANK1_OFFSET);
for (i = SDW_DP0_SAMPLECTRL1 + SDW_BANK1_OFFSET;
i <= SDW_DP0_LANECTRL + SDW_BANK1_OFFSET; i++)
ret += sdw_sprintf(slave, buf, ret, i);
/* SCP registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "\nSCP\n");
for (i = SDW_SCP_INT1; i <= SDW_SCP_BUS_CLOCK_BASE; i++)
ret += sdw_sprintf(slave, buf, ret, i);
for (i = SDW_SCP_DEVID_0; i <= SDW_SCP_DEVID_5; i++)
ret += sdw_sprintf(slave, buf, ret, i);
for (i = SDW_SCP_SDCA_INT1; i <= SDW_SCP_SDCA_INTMASK4; i++)
ret += sdw_sprintf(slave, buf, ret, i);
for (i = SDW_SCP_FRAMECTRL_B0; i <= SDW_SCP_BUSCLOCK_SCALE_B0; i++)
ret += sdw_sprintf(slave, buf, ret, i);
for (i = SDW_SCP_FRAMECTRL_B1; i <= SDW_SCP_BUSCLOCK_SCALE_B1; i++)
ret += sdw_sprintf(slave, buf, ret, i);
for (i = SDW_SCP_PHY_OUT_CTRL_0; i <= SDW_SCP_PHY_OUT_CTRL_7; i++)
ret += sdw_sprintf(slave, buf, ret, i);
/*
* SCP Bank 0/1 registers are read-only and cannot be
* retrieved from the Slave. The Master typically keeps track
* of the current frame size so the information can be found
* in other places
*/
/* DP1..14 registers */
for (i = 1; SDW_VALID_PORT_RANGE(i); i++) {
/* DPi registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "\nDP%d\n", i);
for (j = SDW_DPN_INT(i); j <= SDW_DPN_PREPARECTRL(i); j++)
ret += sdw_sprintf(slave, buf, ret, j);
/* DPi Bank0 registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "Bank0\n");
for (j = SDW_DPN_CHANNELEN_B0(i);
j <= SDW_DPN_LANECTRL_B0(i); j++)
ret += sdw_sprintf(slave, buf, ret, j);
/* DPi Bank1 registers */
ret += scnprintf(buf + ret, RD_BUF - ret, "Bank1\n");
for (j = SDW_DPN_CHANNELEN_B1(i);
j <= SDW_DPN_LANECTRL_B1(i); j++)
ret += sdw_sprintf(slave, buf, ret, j);
}
seq_printf(s_file, "%s", buf);
pm_runtime_mark_last_busy(&slave->dev);
pm_runtime_put(&slave->dev);
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};
struct sdw_bpt_section *sec;
sec = kzalloc_objs(*sec, 1);
if (!sec)
return -ENOMEM;
msg.sections = 1;
sec[0].addr = start_addr;
sec[0].len = num_bytes;
msg.sec = sec;
msg.dev_num = slave->dev_num;
if (write)
msg.flags = SDW_MSG_FLAG_WRITE;
else
msg.flags = SDW_MSG_FLAG_READ;
sec[0].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();
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));
out:
if (fw)
release_firmware(fw);
pm_runtime_mark_last_busy(&slave->dev);
pm_runtime_put(&slave->dev);
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;
struct dentry *d;
char name[32];
master = slave->bus->debugfs;
/* create the debugfs slave-name */
snprintf(name, sizeof(name), "%s", dev_name(&slave->dev));
d = debugfs_create_dir(name, master);
debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops);
/* 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);
if (firmware_file)
debugfs_create_str("firmware_file", 0200, d, &firmware_file);
slave->debugfs = d;
}
void sdw_slave_debugfs_exit(struct sdw_slave *slave)
{
debugfs_remove_recursive(slave->debugfs);
}
void sdw_debugfs_init(void)
{
if (!firmware_file)
firmware_file = kstrdup("", GFP_KERNEL);
sdw_debugfs_root = debugfs_create_dir("soundwire", NULL);
}
void sdw_debugfs_exit(void)
{
debugfs_remove_recursive(sdw_debugfs_root);
kfree(firmware_file);
firmware_file = NULL;
}
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2021 Intel Corporation.
/*
* Soundwire DMI quirks
*/
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/soundwire/sdw.h>
#include "bus.h"
struct adr_remap {
u64 adr;
u64 remapped_adr;
};
/*
* Some TigerLake devices based on an initial Intel BIOS do not expose
* the correct _ADR in the DSDT.
* Remap the bad _ADR values to the ones reported by hardware
*/
static const struct adr_remap intel_tgl_bios[] = {
{
0x000010025D070100ull,
0x000020025D071100ull
},
{
0x000110025d070100ull,
0x000120025D130800ull
},
{}
};
/*
* The initial version of the Dell SKU 0A3E did not expose the devices
* on the correct links.
*/
static const struct adr_remap dell_sku_0A3E[] = {
/* rt715 on link0 */
{
0x00020025d071100ull,
0x00021025d071500ull
},
/* rt711 on link1 */
{
0x000120025d130800ull,
0x000120025d071100ull,
},
/* rt1308 on link2 */
{
0x000220025d071500ull,
0x000220025d130800ull
},
{}
};
/*
* The HP Omen 16-k0005TX does not expose the correct version of RT711 on link0
* and does not expose a RT1316 on link3
*/
static const struct adr_remap hp_omen_16[] = {
/* rt711-sdca on link0 */
{
0x000020025d071100ull,
0x000030025d071101ull
},
/* rt1316-sdca on link3 */
{
0x000120025d071100ull,
0x000330025d131601ull
},
{}
};
/*
* Intel NUC M15 LAPRC510 and LAPRC710
*/
static const struct adr_remap intel_rooks_county[] = {
/* rt711-sdca on link0 */
{
0x000020025d071100ull,
0x000030025d071101ull
},
/* rt1316-sdca on link2 */
{
0x000120025d071100ull,
0x000230025d131601ull
},
{}
};
/*
* Many platforms have ghost realtek devices in the ACPI that don't physically
* exist, remove those devices.
*/
static const struct adr_remap ghost_realtek[] = {
/* rt722 on link3 */
{
0x000330025d072201ull,
0x0000000000000000ull
},
{}
};
static const struct dmi_system_id adr_remap_quirk_table[] = {
/* TGL devices */
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "HP Spectre x360 Conv"),
},
.driver_data = (void *)intel_tgl_bios,
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_BOARD_NAME, "8709"),
},
.driver_data = (void *)intel_tgl_bios,
},
{
/* quirk used for NUC15 'Bishop County' LAPBC510 and LAPBC710 skews */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_MATCH(DMI_PRODUCT_NAME, "LAPBC"),
},
.driver_data = (void *)intel_tgl_bios,
},
{
/* quirk used for NUC15 LAPBC710 skew */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "LAPBC710"),
},
.driver_data = (void *)intel_tgl_bios,
},
{
/*
* quirk used for Avell B.ON (OEM rebrand of NUC15 'Bishop County'
* LAPBC510 and LAPBC710)
*/
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Avell High Performance"),
DMI_MATCH(DMI_PRODUCT_NAME, "B.ON"),
},
.driver_data = (void *)intel_tgl_bios,
},
{
/* quirk used for NUC15 'Rooks County' LAPRC510 and LAPRC710 skews */
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Intel(R) Client Systems"),
DMI_MATCH(DMI_PRODUCT_NAME, "LAPRC"),
},
.driver_data = (void *)intel_rooks_county,
},
{
/* quirk used for NUC15 LAPRC710 skew */
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "Intel Corporation"),
DMI_MATCH(DMI_BOARD_NAME, "LAPRC710"),
},
.driver_data = (void *)intel_rooks_county,
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc"),
DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "0A3E")
},
.driver_data = (void *)dell_sku_0A3E,
},
/* ADL devices */
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "HP"),
DMI_MATCH(DMI_PRODUCT_NAME, "OMEN by HP Gaming Laptop 16"),
},
.driver_data = (void *)hp_omen_16,
},
/* PTL devices */
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUS"),
DMI_MATCH(DMI_BOARD_NAME, "UX5406AA"),
},
.driver_data = (void *)ghost_realtek,
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "83QK"),
},
.driver_data = (void *)ghost_realtek,
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "83SF"),
},
.driver_data = (void *)ghost_realtek,
},
{}
};
u64 sdw_dmi_override_adr(struct sdw_bus *bus, u64 addr)
{
const struct dmi_system_id *dmi_id;
/* check if any address remap quirk applies */
dmi_id = dmi_first_match(adr_remap_quirk_table);
if (dmi_id) {
struct adr_remap *map;
for (map = dmi_id->driver_data; map->adr; map++) {
if (map->adr == addr) {
dev_dbg(bus->dev, "remapped _ADR 0x%llx as 0x%llx\n",
addr, map->remapped_adr);
addr = map->remapped_adr;
break;
}
}
}
return addr;
}
@@ -0,0 +1,687 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
// Copyright(c) 2015-2020 Intel Corporation.
/*
* Bandwidth management algorithm based on 2^n gears
*
*/
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/soundwire/sdw.h>
#include "bus.h"
#define SDW_STRM_RATE_GROUPING 1
struct sdw_group_params {
unsigned int rate;
unsigned int lane;
int full_bw;
int payload_bw;
int hwidth;
};
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,
struct sdw_transport_data *t_data)
{
struct sdw_slave_runtime *s_rt = NULL;
struct sdw_port_runtime *p_rt;
int port_bo, sample_int;
unsigned int rate, bps, ch = 0;
unsigned int slave_total_ch;
struct sdw_bus_params *b_params = &m_rt->bus->params;
port_bo = t_data->block_offset;
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
sample_int = (m_rt->bus->params.curr_dr_freq / rate);
slave_total_ch = 0;
list_for_each_entry(p_rt, &s_rt->port_list, port_node) {
if (p_rt->lane != t_data->lane)
continue;
ch = hweight32(p_rt->ch_mask);
sdw_fill_xport_params(&p_rt->transport_params,
p_rt->num, false,
SDW_BLK_GRP_CNT_1,
sample_int, port_bo, port_bo >> 8,
t_data->hstart,
t_data->hstop,
SDW_BLK_PKG_PER_PORT, p_rt->lane);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->s_data_mode);
port_bo += bps * ch;
slave_total_ch += ch;
}
if (m_rt->direction == SDW_DATA_DIR_TX &&
m_rt->ch_count == slave_total_ch) {
/*
* Slave devices were configured to access all channels
* of the stream, which indicates that they operate in
* 'mirror mode'. Make sure we reset the port offset for
* the next device in the list
*/
port_bo = t_data->block_offset;
}
}
}
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) {
/* DP0 is for BPT only */
if (m_rt->stream->type != SDW_STREAM_BPT)
continue;
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)
{
struct sdw_transport_data t_data = {0};
struct sdw_port_runtime *p_rt;
struct sdw_bus *bus = m_rt->bus;
struct sdw_bus_params *b_params = &bus->params;
int sample_int, hstart = 0;
unsigned int rate, bps, ch;
rate = m_rt->stream->params.rate;
bps = m_rt->stream->params.bps;
ch = m_rt->ch_count;
sample_int = (bus->params.curr_dr_freq / rate);
if (rate != params->rate)
return;
t_data.hstop = hstop;
hstart = hstop - params->hwidth + 1;
t_data.hstart = hstart;
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
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, p_rt->lane);
sdw_fill_port_params(&p_rt->port_params,
p_rt->num, bps,
SDW_PORT_FLOW_MODE_ISOCH,
b_params->m_data_mode);
/* Check for first entry */
if (!(p_rt == list_first_entry(&m_rt->port_list,
struct sdw_port_runtime,
port_node))) {
(*port_bo) += bps * ch;
continue;
}
t_data.hstart = hstart;
t_data.hstop = hstop;
t_data.block_offset = *port_bo;
t_data.sub_block_offset = 0;
(*port_bo) += bps * ch;
}
t_data.lane = params->lane;
sdw_compute_slave_ports(m_rt, &t_data);
}
static void _sdw_compute_port_params(struct sdw_bus *bus,
struct sdw_group_params *params, int count)
{
struct sdw_master_runtime *m_rt;
int port_bo, i, l;
int hstop;
/* Run loop for all groups to compute transport parameters */
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) {
/*
* Only runtimes with CONFIGURED, PREPARED, ENABLED, and DISABLED
* states should be included in the bandwidth calculation.
*/
if (m_rt->stream->state > SDW_STREAM_DISABLED ||
m_rt->stream->state < SDW_STREAM_CONFIGURED)
continue;
sdw_compute_master_ports(m_rt, &params[i], &port_bo, hstop);
}
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,
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, l, column_needed;
/* Calculate bandwidth per group */
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) {
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 < group->count; i++) {
if (rate == params[i].rate && p_rt->lane == params[i].lane)
params[i].payload_bw += bps * ch;
}
}
}
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;
/* 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;
}
}
return 0;
}
static int sdw_add_element_group_count(struct sdw_group *group,
unsigned int rate, unsigned int lane)
{
int num = group->count;
int i;
for (i = 0; i < num; i++) {
if (rate == group->rates[i] && lane == group->lanes[i])
return 0;
}
if (group->count >= group->max_size) {
unsigned int *rates;
unsigned int *lanes;
rates = krealloc_array(group->rates, group->max_size + 1,
sizeof(*group->rates), GFP_KERNEL);
if (!rates)
return -ENOMEM;
group->rates = rates;
lanes = krealloc_array(group->lanes, group->max_size + 1,
sizeof(*group->lanes), GFP_KERNEL);
if (!lanes)
return -ENOMEM;
group->lanes = lanes;
group->max_size += 1;
}
group->rates[group->count] = rate;
group->lanes[group->count++] = lane;
return 0;
}
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;
group->count = 0;
group->max_size = SDW_STRM_RATE_GROUPING;
group->rates = kcalloc(group->max_size, sizeof(int), GFP_KERNEL);
if (!group->rates)
return -ENOMEM;
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;
}
/*
* 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;
}
}
}
return ret;
}
/**
* 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, struct sdw_stream_runtime *stream)
{
struct sdw_group_params *params = NULL;
struct sdw_group group;
int ret;
ret = sdw_get_group_count(bus, &group);
if (ret < 0)
return ret;
if (group.count == 0)
goto out;
params = kzalloc_objs(*params, group.count);
if (!params) {
ret = -ENOMEM;
goto out;
}
/* Compute transport parameters for grouped streams */
ret = sdw_compute_group_params(bus, stream, params, &group);
if (ret < 0)
goto free_params;
_sdw_compute_port_params(bus, params, group.count);
free_params:
kfree(params);
out:
kfree(group.rates);
kfree(group.lanes);
return ret;
}
static int sdw_select_row_col(struct sdw_bus *bus, int clk_freq)
{
struct sdw_master_prop *prop = &bus->prop;
int r, c;
for (c = 0; c < SDW_FRAME_COLS; c++) {
for (r = 0; r < SDW_FRAME_ROWS; r++) {
if (sdw_rows[r] != prop->default_row ||
sdw_cols[c] != prop->default_col)
continue;
if (clk_freq * (sdw_cols[c] - 1) <
bus->params.bandwidth * sdw_cols[c])
continue;
bus->params.row = sdw_rows[r];
bus->params.col = sdw_cols[c];
return 0;
}
}
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
*
* @bus: SDW Bus instance
*/
static int sdw_compute_bus_params(struct sdw_bus *bus)
{
struct sdw_master_prop *mstr_prop = &bus->prop;
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) {
clk_values = mstr_prop->num_clk_gears;
clk_buf = mstr_prop->clk_gears;
is_gear = true;
} else if (mstr_prop->num_clk_freq) {
clk_values = mstr_prop->num_clk_freq;
clk_buf = mstr_prop->clk_freq;
} else {
clk_values = 1;
clk_buf = NULL;
}
/* 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;
else
curr_dr_freq = (is_gear) ?
(bus->params.max_dr_freq >> clk_buf[i]) :
clk_buf[i] * SDW_DOUBLE_RATE_FACTOR;
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);
/*
* 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
* whether given clock rate is supported with glitchless
* transition.
*/
}
if (i == clk_values) {
dev_err(bus->dev, "%s: could not find clock value for bandwidth %d\n",
__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) {
dev_err(bus->dev, "%s: could not find frame configuration for bus dr_freq %d\n",
__func__, curr_dr_freq);
return -EINVAL;
}
bus->params.curr_dr_freq = curr_dr_freq;
return 0;
}
/**
* sdw_compute_params: Compute bus, transport and port parameters
*
* @bus: SDW Bus instance
* @stream: Soundwire stream
*/
int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
{
int ret;
/* Computes clock frequency, frame shape and frame frequency */
ret = sdw_compute_bus_params(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, stream);
if (ret < 0) {
dev_err(bus->dev, "Compute transport params failed: %d\n", ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL(sdw_compute_params);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SoundWire Generic Bandwidth Allocation");
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/* Copyright(c) 2015-17 Intel Corporation. */
#ifndef __SDW_INTEL_LOCAL_H
#define __SDW_INTEL_LOCAL_H
struct hdac_bus;
/**
* struct sdw_intel_link_res - Soundwire Intel link resource structure,
* typically populated by the controller driver.
* @hw_ops: platform-specific ops
* @mmio_base: mmio base of SoundWire registers
* @registers: Link IO registers base
* @ip_offset: offset for MCP_IP registers
* @shim: Audio shim pointer
* @shim_vs: Audio vendor-specific shim pointer
* @alh: ALH (Audio Link Hub) pointer
* @irq: Interrupt line
* @ops: Shim callback ops
* @dev: device implementing hw_params and free callbacks
* @shim_lock: mutex to handle access to shared SHIM registers
* @shim_mask: global pointer to check SHIM register initialization
* @clock_stop_quirks: mask defining requested behavior on pm_suspend
* @mic_privacy: ACE version supports microphone privacy
* @link_mask: global mask needed for power-up/down sequences
* @cdns: Cadence master descriptor
* @list: used to walk-through all masters exposed by the same controller
* @hbus: hdac_bus pointer, needed for power management
*/
struct sdw_intel_link_res {
const struct sdw_intel_hw_ops *hw_ops;
void __iomem *mmio_base; /* not strictly needed, useful for debug */
void __iomem *registers;
u32 ip_offset;
void __iomem *shim;
void __iomem *shim_vs;
void __iomem *alh;
int irq;
const struct sdw_intel_ops *ops;
struct device *dev;
struct mutex *shim_lock; /* protect shared registers */
u32 *shim_mask;
u32 clock_stop_quirks;
bool mic_privacy;
u32 link_mask;
struct sdw_cdns *cdns;
struct list_head list;
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,
INTEL_PDI_BD = 2,
};
/*
* Read, write helpers for HW registers
*/
static inline int intel_readl(void __iomem *base, int offset)
{
return readl(base + offset);
}
static inline void intel_writel(void __iomem *base, int offset, int value)
{
writel(value, base + offset);
}
static inline u16 intel_readw(void __iomem *base, int offset)
{
return readw(base + offset);
}
static inline void intel_writew(void __iomem *base, int offset, u16 value)
{
writew(value, base + offset);
}
#define cdns_to_intel(_cdns) container_of(_cdns, struct sdw_intel, cdns)
#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)
#ifdef CONFIG_DEBUG_FS
void intel_ace2x_debugfs_init(struct sdw_intel *sdw);
void intel_ace2x_debugfs_exit(struct sdw_intel *sdw);
#else
static inline void intel_ace2x_debugfs_init(struct sdw_intel *sdw) {}
static inline void intel_ace2x_debugfs_exit(struct sdw_intel *sdw) {}
#endif
static inline void sdw_intel_debugfs_init(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, debugfs_init))
SDW_INTEL_OPS(sdw, debugfs_init)(sdw);
}
static inline void sdw_intel_debugfs_exit(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, debugfs_exit))
SDW_INTEL_OPS(sdw, debugfs_exit)(sdw);
}
static inline int sdw_intel_register_dai(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, register_dai))
return SDW_INTEL_OPS(sdw, register_dai)(sdw);
return -ENOTSUPP;
}
static inline void sdw_intel_check_clock_stop(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, check_clock_stop))
SDW_INTEL_OPS(sdw, check_clock_stop)(sdw);
}
static inline int sdw_intel_start_bus(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, start_bus))
return SDW_INTEL_OPS(sdw, start_bus)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_start_bus_after_reset(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_reset))
return SDW_INTEL_OPS(sdw, start_bus_after_reset)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, start_bus_after_clock_stop))
return SDW_INTEL_OPS(sdw, start_bus_after_clock_stop)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
{
if (SDW_INTEL_CHECK_OPS(sdw, stop_bus))
return SDW_INTEL_OPS(sdw, stop_bus)(sdw, clock_stop);
return -ENOTSUPP;
}
static inline int sdw_intel_link_power_up(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, link_power_up))
return SDW_INTEL_OPS(sdw, link_power_up)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_link_power_down(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, link_power_down))
return SDW_INTEL_OPS(sdw, link_power_down)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_shim_check_wake(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, shim_check_wake))
return SDW_INTEL_OPS(sdw, shim_check_wake)(sdw);
return -ENOTSUPP;
}
static inline void sdw_intel_shim_wake(struct sdw_intel *sdw, bool wake_enable)
{
if (SDW_INTEL_CHECK_OPS(sdw, shim_wake))
SDW_INTEL_OPS(sdw, shim_wake)(sdw, wake_enable);
}
static inline void sdw_intel_sync_arm(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, sync_arm))
SDW_INTEL_OPS(sdw, sync_arm)(sdw);
}
static inline int sdw_intel_sync_go_unlocked(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, sync_go_unlocked))
return SDW_INTEL_OPS(sdw, sync_go_unlocked)(sdw);
return -ENOTSUPP;
}
static inline int sdw_intel_sync_go(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, sync_go))
return SDW_INTEL_OPS(sdw, sync_go)(sdw);
return -ENOTSUPP;
}
static inline bool sdw_intel_sync_check_cmdsync_unlocked(struct sdw_intel *sdw)
{
if (SDW_INTEL_CHECK_OPS(sdw, sync_check_cmdsync_unlocked))
return SDW_INTEL_OPS(sdw, sync_check_cmdsync_unlocked)(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);
void intel_check_clock_stop(struct sdw_intel *sdw);
int intel_start_bus_after_clock_stop(struct sdw_intel *sdw);
int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop);
/* common bank switch routines */
int intel_pre_bank_switch(struct sdw_intel *sdw);
int intel_post_bank_switch(struct sdw_intel *sdw);
#endif /* __SDW_INTEL_LOCAL_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2023 Intel Corporation
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/pm_runtime.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include <linux/soundwire/sdw_registers.h>
#include "bus.h"
#include "cadence_master.h"
#include "intel.h"
/*
* debugfs
*/
#ifdef CONFIG_DEBUG_FS
#define RD_BUF (2 * PAGE_SIZE)
static ssize_t intel_sprintf(void __iomem *mem, bool l,
char *buf, size_t pos, unsigned int reg)
{
int value;
if (l)
value = intel_readl(mem, reg);
else
value = intel_readw(mem, reg);
return scnprintf(buf + pos, RD_BUF - pos, "%4x\t%4x\n", reg, value);
}
static int intel_reg_show(struct seq_file *s_file, void *data)
{
struct sdw_intel *sdw = s_file->private;
void __iomem *s = sdw->link_res->shim;
void __iomem *vs_s = sdw->link_res->shim_vs;
ssize_t ret;
u32 pcm_cap;
int pcm_bd;
char *buf;
int j;
buf = kzalloc(RD_BUF, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = scnprintf(buf, RD_BUF, "Register Value\n");
ret += scnprintf(buf + ret, RD_BUF - ret, "\nShim\n");
ret += intel_sprintf(s, true, buf, ret, SDW_SHIM2_LECAP);
ret += intel_sprintf(s, false, buf, ret, SDW_SHIM2_PCMSCAP);
pcm_cap = intel_readw(s, SDW_SHIM2_PCMSCAP);
pcm_bd = FIELD_GET(SDW_SHIM2_PCMSCAP_BSS, pcm_cap);
for (j = 0; j < pcm_bd; j++) {
ret += intel_sprintf(s, false, buf, ret,
SDW_SHIM2_PCMSYCHM(j));
ret += intel_sprintf(s, false, buf, ret,
SDW_SHIM2_PCMSYCHC(j));
}
ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS CLK controls\n");
ret += intel_sprintf(vs_s, true, buf, ret, SDW_SHIM2_INTEL_VS_LVSCTL);
ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS Wake registers\n");
ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_WAKEEN);
ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_WAKESTS);
ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS IOCTL, ACTMCTL\n");
ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_IOCTL);
ret += intel_sprintf(vs_s, false, buf, ret, SDW_SHIM2_INTEL_VS_ACTMCTL);
if (sdw->link_res->mic_privacy) {
ret += scnprintf(buf + ret, RD_BUF - ret, "\nVS PVCCS\n");
ret += intel_sprintf(vs_s, false, buf, ret,
SDW_SHIM2_INTEL_VS_PVCCS);
}
seq_printf(s_file, "%s", buf);
kfree(buf);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(intel_reg);
static int intel_set_m_datamode(void *data, u64 value)
{
struct sdw_intel *sdw = data;
struct sdw_bus *bus = &sdw->cdns.bus;
if (value > SDW_PORT_DATA_MODE_STATIC_1)
return -EINVAL;
/* Userspace changed the hardware state behind the kernel's back */
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
bus->params.m_data_mode = value;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(intel_set_m_datamode_fops, NULL,
intel_set_m_datamode, "%llu\n");
static int intel_set_s_datamode(void *data, u64 value)
{
struct sdw_intel *sdw = data;
struct sdw_bus *bus = &sdw->cdns.bus;
if (value > SDW_PORT_DATA_MODE_STATIC_1)
return -EINVAL;
/* Userspace changed the hardware state behind the kernel's back */
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
bus->params.s_data_mode = value;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(intel_set_s_datamode_fops, NULL,
intel_set_s_datamode, "%llu\n");
void intel_ace2x_debugfs_init(struct sdw_intel *sdw)
{
struct dentry *root = sdw->cdns.bus.debugfs;
if (!root)
return;
sdw->debugfs = debugfs_create_dir("intel-sdw", root);
debugfs_create_file("intel-registers", 0400, sdw->debugfs, sdw,
&intel_reg_fops);
debugfs_create_file("intel-m-datamode", 0200, sdw->debugfs, sdw,
&intel_set_m_datamode_fops);
debugfs_create_file("intel-s-datamode", 0200, sdw->debugfs, sdw,
&intel_set_s_datamode_fops);
sdw_cdns_debugfs_init(&sdw->cdns, sdw->debugfs);
}
void intel_ace2x_debugfs_exit(struct sdw_intel *sdw)
{
debugfs_remove_recursive(sdw->debugfs);
}
#endif /* CONFIG_DEBUG_FS */
@@ -0,0 +1,877 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-22 Intel Corporation.
/*
* Soundwire Intel Manager Driver
*/
#include <linux/acpi.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/auxiliary_bus.h>
#include <sound/pcm_params.h>
#include <linux/pm_runtime.h>
#include <sound/soc.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include "cadence_master.h"
#include "bus.h"
#include "intel.h"
#include "intel_auxdevice.h"
#define INTEL_MASTER_SUSPEND_DELAY_MS 3000
/*
* debug/config flags for the Intel SoundWire Master.
*
* Since we may have multiple masters active, we can have up to 8
* flags reused in each byte, with master0 using the ls-byte, etc.
*/
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME BIT(0)
#define SDW_INTEL_MASTER_DISABLE_CLOCK_STOP BIT(1)
#define SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE BIT(2)
#define SDW_INTEL_MASTER_DISABLE_MULTI_LINK BIT(3)
static int md_flags;
module_param_named(sdw_md_flags, md_flags, int, 0444);
MODULE_PARM_DESC(sdw_md_flags, "SoundWire Intel Master device flags (0x0 all off)");
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, 0x2A30},
{0x01fa, 0x2A3B},
{0x01fa, 0x4243},
{0x01fa, 0x4245},
{0x01fa, 0x4249},
{0x01fa, 0x4747},
{0x025d, 0x5682},
{0x025d, 0x700},
{0x025d, 0x711},
{0x025d, 0x1712},
{0x025d, 0x1713},
{0x025d, 0x1716},
{0x025d, 0x1717},
{0x025d, 0x712},
{0x025d, 0x713},
{0x025d, 0x714},
{0x025d, 0x715},
{0x025d, 0x716},
{0x025d, 0x717},
{0x025d, 0x721},
{0x025d, 0x722},
{0x04b3, 0x9356},
};
static bool is_wake_capable(struct sdw_slave *slave)
{
int i;
for (i = 0; i < ARRAY_SIZE(wake_capable_list); i++)
if (slave->id.part_id == wake_capable_list[i].part_id &&
slave->id.mfg_id == wake_capable_list[i].mfg_id)
return true;
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);
struct sdw_intel *sdw = cdns_to_intel(cdns);
return sdw->link_res->hw_ops->pre_bank_switch(sdw);
}
static int generic_post_bank_switch(struct sdw_bus *bus)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
struct sdw_intel *sdw = cdns_to_intel(cdns);
return sdw->link_res->hw_ops->post_bank_switch(sdw);
}
static void generic_new_peripheral_assigned(struct sdw_bus *bus,
struct sdw_slave *slave,
int dev_num)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
struct sdw_intel *sdw = cdns_to_intel(cdns);
int dev_num_min;
int dev_num_max;
bool wake_capable = slave->prop.wake_capable || is_wake_capable(slave);
if (wake_capable) {
dev_num_min = SDW_INTEL_DEV_NUM_IDA_MIN;
dev_num_max = SDW_MAX_DEVICES;
} else {
dev_num_min = 1;
dev_num_max = SDW_INTEL_DEV_NUM_IDA_MIN - 1;
}
/* paranoia check, this should never happen */
if (dev_num < dev_num_min || dev_num > dev_num_max) {
dev_err(bus->dev, "%s: invalid dev_num %d, wake supported %d\n",
__func__, dev_num, slave->prop.wake_capable);
return;
}
if (sdw->link_res->hw_ops->program_sdi && wake_capable)
sdw->link_res->hw_ops->program_sdi(sdw, dev_num);
}
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;
/* Find master handle */
snprintf(name, sizeof(name),
"mipi-sdw-link-%d-subproperties", bus->link_id);
link = device_get_named_child_node(bus->dev, name);
if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
}
fwnode_property_read_u32(link,
"intel-sdw-ip-clock",
&prop->mclk_freq);
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",
&quirk_mask);
if (quirk_mask & SDW_INTEL_QUIRK_MASK_BUS_DISABLE)
prop->hw_disabled = true;
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;
}
static int intel_prop_read(struct sdw_bus *bus)
{
/* Initialize with default handler to read all DisCo properties */
sdw_master_read_prop(bus);
/* read Intel-specific properties */
sdw_master_read_intel_prop(bus);
return 0;
}
static DEFINE_IDA(intel_peripheral_ida);
static int intel_get_device_num_ida(struct sdw_bus *bus, struct sdw_slave *slave)
{
int bit;
if (slave->prop.wake_capable || is_wake_capable(slave))
return ida_alloc_range(&intel_peripheral_ida,
SDW_INTEL_DEV_NUM_IDA_MIN, SDW_MAX_DEVICES,
GFP_KERNEL);
bit = find_first_zero_bit(slave->bus->assigned, SDW_MAX_DEVICES);
if (bit == SDW_MAX_DEVICES)
return -ENODEV;
return bit;
}
static void intel_put_device_num_ida(struct sdw_bus *bus, struct sdw_slave *slave)
{
if (slave->prop.wake_capable || is_wake_capable(slave))
ida_free(&intel_peripheral_ida, slave->dev_num);
}
static struct sdw_master_ops sdw_intel_ops = {
.read_prop = intel_prop_read,
.override_adr = sdw_dmi_override_adr,
.xfer_msg = cdns_xfer_msg,
.xfer_msg_defer = cdns_xfer_msg_defer,
.set_bus_conf = cdns_bus_conf,
.pre_bank_switch = generic_pre_bank_switch,
.post_bank_switch = generic_post_bank_switch,
.read_ping_status = cdns_read_ping_status,
.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,
};
/*
* probe and init (aux_dev_id argument is required by function prototype but not used)
*/
static int intel_link_probe(struct auxiliary_device *auxdev,
const struct auxiliary_device_id *aux_dev_id)
{
struct device *dev = &auxdev->dev;
struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
struct sdw_intel *sdw;
struct sdw_cdns *cdns;
struct sdw_bus *bus;
int ret;
sdw = devm_kzalloc(dev, sizeof(*sdw), GFP_KERNEL);
if (!sdw)
return -ENOMEM;
cdns = &sdw->cdns;
bus = &cdns->bus;
sdw->instance = auxdev->id;
sdw->link_res = &ldev->link_res;
cdns->dev = dev;
cdns->registers = sdw->link_res->registers;
cdns->ip_offset = sdw->link_res->ip_offset;
cdns->instance = sdw->instance;
cdns->msg_count = 0;
/* single controller for all SoundWire links */
bus->controller_id = 0;
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 */
bus->ops = &sdw_intel_ops;
/* set driver data, accessed by snd_soc_dai_get_drvdata() */
auxiliary_set_drvdata(auxdev, cdns);
/* use generic bandwidth allocation algorithm */
sdw->cdns.bus.compute_params = sdw_compute_params;
ret = sdw_bus_master_add(bus, dev, dev->fwnode);
if (ret) {
dev_err(dev, "sdw_bus_master_add fail: %d\n", ret);
return ret;
}
if (bus->prop.hw_disabled)
dev_info(dev,
"SoundWire master %d is disabled, will be ignored\n",
bus->link_id);
/*
* Ignore BIOS err_threshold, it's a really bad idea when dealing
* with multiple hardware synchronized links
*/
bus->prop.err_threshold = 0;
return 0;
}
int intel_link_startup(struct auxiliary_device *auxdev)
{
struct device *dev = &auxdev->dev;
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
int link_flags;
bool multi_link;
u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled) {
dev_info(dev,
"SoundWire master %d is disabled, ignoring\n",
sdw->instance);
return 0;
}
link_flags = md_flags >> (bus->link_id * 8);
multi_link = !(link_flags & SDW_INTEL_MASTER_DISABLE_MULTI_LINK);
if (!multi_link) {
dev_dbg(dev, "Multi-link is disabled\n");
} else {
/*
* hardware-based synchronization is required regardless
* of the number of segments used by a stream: SSP-based
* synchronization is gated by gsync when the multi-master
* mode is set.
*/
bus->hw_sync_min_links = 1;
}
bus->multi_link = multi_link;
/* Initialize shim, controller */
ret = sdw_intel_link_power_up(sdw);
if (ret)
goto err_init;
/* Register DAIs */
ret = sdw_intel_register_dai(sdw);
if (ret) {
dev_err(dev, "DAI registration failed: %d\n", ret);
goto err_power_up;
}
sdw_intel_debugfs_init(sdw);
/* Enable runtime PM */
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME)) {
pm_runtime_set_autosuspend_delay(dev,
INTEL_MASTER_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(dev);
pm_runtime_mark_last_busy(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_resume(bus->dev);
}
/* start bus */
ret = sdw_intel_start_bus(sdw);
if (ret) {
dev_err(dev, "bus start failed: %d\n", ret);
goto err_pm_runtime;
}
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_NOT_ALLOWED) {
/*
* To keep the clock running we need to prevent
* pm_runtime suspend from happening by increasing the
* reference count.
* This quirk is specified by the parent PCI device in
* case of specific latency requirements. It will have
* no effect if pm_runtime is disabled by the user via
* a module parameter for testing purposes.
*/
pm_runtime_get_noresume(dev);
}
/*
* The runtime PM status of Slave devices is "Unsupported"
* until they report as ATTACHED. If they don't, e.g. because
* there are no Slave devices populated or if the power-on is
* delayed or dependent on a power switch, the Master will
* remain active and prevent its parent from suspending.
*
* Conditionally force the pm_runtime core to re-evaluate the
* Master status in the absence of any Slave activity. A quirk
* is provided to e.g. deal with Slaves that may be powered on
* with a delay. A more complete solution would require the
* definition of Master properties.
*/
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME_IDLE)) {
pm_runtime_mark_last_busy(bus->dev);
pm_runtime_mark_last_busy(dev);
pm_runtime_idle(dev);
}
sdw->startup_done = true;
return 0;
err_pm_runtime:
if (!(link_flags & SDW_INTEL_MASTER_DISABLE_PM_RUNTIME))
pm_runtime_disable(dev);
err_power_up:
sdw_intel_link_power_down(sdw);
err_init:
return ret;
}
static void intel_link_remove(struct auxiliary_device *auxdev)
{
struct sdw_cdns *cdns = auxiliary_get_drvdata(auxdev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
/*
* Since pm_runtime is already disabled, we don't decrease
* the refcount when the clock_stop_quirk is
* SDW_INTEL_CLK_STOP_NOT_ALLOWED
*/
if (!bus->prop.hw_disabled) {
sdw_intel_debugfs_exit(sdw);
cancel_delayed_work_sync(&cdns->attach_dwork);
sdw_cdns_enable_interrupt(cdns, false);
}
sdw_bus_master_delete(bus);
}
int intel_link_process_wakeen_event(struct auxiliary_device *auxdev)
{
struct device *dev = &auxdev->dev;
struct sdw_intel *sdw;
struct sdw_bus *bus;
sdw = auxiliary_get_drvdata(auxdev);
bus = &sdw->cdns.bus;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
if (!sdw_intel_shim_check_wake(sdw))
return 0;
/* disable WAKEEN interrupt ASAP to prevent interrupt flood */
sdw_intel_shim_wake(sdw, false);
/*
* resume the Master, which will generate a bus reset and result in
* Slaves re-attaching and be re-enumerated. The SoundWire physical
* device which generated the wake will trigger an interrupt, which
* will in turn cause the corresponding Linux Slave device to be
* resumed and the Slave codec driver to check the status.
*/
pm_request_resume(dev);
return 0;
}
/*
* PM calls
*/
int intel_resume_child_device(struct device *dev, void *data)
{
int ret;
struct sdw_slave *slave = dev_to_sdw_dev(dev);
if (!slave->probed) {
dev_dbg(dev, "skipping device, no probed driver\n");
return 0;
}
if (!slave->dev_num_sticky) {
dev_dbg(dev, "skipping device, never detected on bus\n");
return 0;
}
ret = pm_runtime_resume(dev);
if (ret < 0) {
dev_err(dev, "%s: pm_runtime_resume failed: %d\n", __func__, ret);
return ret;
}
return 0;
}
static int __maybe_unused intel_pm_prepare(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
if (pm_runtime_suspended(dev) &&
pm_runtime_suspended(dev->parent) &&
((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
!clock_stop_quirks)) {
/*
* if we've enabled clock stop, and the parent is suspended, the SHIM registers
* are not accessible and the shim wake cannot be disabled.
* The only solution is to resume the entire bus to full power
*/
/*
* If any operation in this block fails, we keep going since we don't want
* to prevent system suspend from happening and errors should be recoverable
* on resume.
*/
/*
* first resume the device for this link. This will also by construction
* resume the PCI parent device.
*/
ret = pm_runtime_resume(dev);
if (ret < 0) {
dev_err(dev, "%s: pm_runtime_resume failed: %d\n", __func__, ret);
return 0;
}
/*
* Continue resuming the entire bus (parent + child devices) to exit
* the clock stop mode. If there are no devices connected on this link
* this is a no-op.
* The resume to full power could have been implemented with a .prepare
* step in SoundWire codec drivers. This would however require a lot
* of code to handle an Intel-specific corner case. It is simpler in
* practice to add a loop at the link level.
*/
ret = device_for_each_child(bus->dev, NULL, intel_resume_child_device);
if (ret < 0)
dev_err(dev, "%s: intel_resume_child_device failed: %d\n", __func__, ret);
}
return 0;
}
static int __maybe_unused intel_suspend(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
/* 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;
if ((clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) ||
!clock_stop_quirks) {
if (pm_runtime_status_suspended(dev->parent)) {
/*
* paranoia check: this should not happen with the .prepare
* resume to full power
*/
dev_err(dev, "%s: invalid config: parent is suspended\n", __func__);
} else {
sdw_intel_shim_wake(sdw, false);
}
}
return 0;
}
ret = sdw_intel_stop_bus(sdw, false);
if (ret < 0) {
dev_err(dev, "%s: cannot stop bus: %d\n", __func__, ret);
return ret;
}
return 0;
}
static int __maybe_unused intel_suspend_runtime(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
ret = sdw_intel_stop_bus(sdw, false);
if (ret < 0) {
dev_err(dev, "%s: cannot stop bus during teardown: %d\n",
__func__, ret);
return ret;
}
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET || !clock_stop_quirks) {
ret = sdw_intel_stop_bus(sdw, true);
if (ret < 0) {
dev_err(dev, "%s: cannot stop bus during clock_stop: %d\n",
__func__, ret);
return ret;
}
} else {
dev_err(dev, "%s clock_stop_quirks %x unsupported\n",
__func__, clock_stop_quirks);
ret = -EINVAL;
}
return ret;
}
static int __maybe_unused intel_resume(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
int ret;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
ret = sdw_intel_link_power_up(sdw);
if (ret) {
dev_err(dev, "%s failed: %d\n", __func__, ret);
return ret;
}
/*
* make sure all Slaves are tagged as UNATTACHED and provide
* reason for reinitialization
*/
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
ret = sdw_intel_start_bus(sdw);
if (ret < 0) {
dev_err(dev, "cannot start bus during resume\n");
sdw_intel_link_power_down(sdw);
return ret;
}
/*
* 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()
* routines is not really possible, since it'd prevent the
* master from suspending.
* A reasonable compromise is to update the pm_runtime
* counters and delay the pm_runtime suspend by several
* seconds, by when all enumeration should be complete.
*/
pm_runtime_mark_last_busy(bus->dev);
pm_runtime_mark_last_busy(dev);
return 0;
}
static int __maybe_unused intel_resume_runtime(struct device *dev)
{
struct sdw_cdns *cdns = dev_get_drvdata(dev);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_bus *bus = &cdns->bus;
u32 clock_stop_quirks;
int ret;
if (bus->prop.hw_disabled || !sdw->startup_done) {
dev_dbg(dev, "SoundWire master %d is disabled or not-started, ignoring\n",
bus->link_id);
return 0;
}
/* unconditionally disable WAKEEN interrupt */
sdw_intel_shim_wake(sdw, false);
clock_stop_quirks = sdw->link_res->clock_stop_quirks;
if (clock_stop_quirks & SDW_INTEL_CLK_STOP_TEARDOWN) {
ret = sdw_intel_link_power_up(sdw);
if (ret) {
dev_err(dev, "%s: power_up failed after teardown: %d\n", __func__, ret);
return ret;
}
/*
* make sure all Slaves are tagged as UNATTACHED and provide
* reason for reinitialization
*/
sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET);
ret = sdw_intel_start_bus(sdw);
if (ret < 0) {
dev_err(dev, "%s: cannot start bus after teardown: %d\n", __func__, ret);
sdw_intel_link_power_down(sdw);
return ret;
}
} else if (clock_stop_quirks & SDW_INTEL_CLK_STOP_BUS_RESET) {
ret = sdw_intel_link_power_up(sdw);
if (ret) {
dev_err(dev, "%s: power_up failed after bus reset: %d\n", __func__, ret);
return ret;
}
ret = sdw_intel_start_bus_after_reset(sdw);
if (ret < 0) {
dev_err(dev, "%s: cannot start bus after reset: %d\n", __func__, ret);
sdw_intel_link_power_down(sdw);
return ret;
}
} else if (!clock_stop_quirks) {
sdw_intel_check_clock_stop(sdw);
ret = sdw_intel_link_power_up(sdw);
if (ret) {
dev_err(dev, "%s: power_up failed: %d\n", __func__, ret);
return ret;
}
ret = sdw_intel_start_bus_after_clock_stop(sdw);
if (ret < 0) {
dev_err(dev, "%s: cannot start bus after clock stop: %d\n", __func__, ret);
sdw_intel_link_power_down(sdw);
return ret;
}
} else {
dev_err(dev, "%s: clock_stop_quirks %x unsupported\n",
__func__, clock_stop_quirks);
ret = -EINVAL;
}
return ret;
}
static const struct dev_pm_ops intel_pm = {
.prepare = intel_pm_prepare,
SET_SYSTEM_SLEEP_PM_OPS(intel_suspend, intel_resume)
SET_RUNTIME_PM_OPS(intel_suspend_runtime, intel_resume_runtime, NULL)
};
static const struct auxiliary_device_id intel_link_id_table[] = {
{ .name = "soundwire_intel.link" },
{},
};
MODULE_DEVICE_TABLE(auxiliary, intel_link_id_table);
static struct auxiliary_driver sdw_intel_drv = {
.probe = intel_link_probe,
.remove = intel_link_remove,
.driver = {
/* auxiliary_driver_register() sets .name to be the modname */
.pm = &intel_pm,
},
.id_table = intel_link_id_table
};
module_auxiliary_driver(sdw_intel_drv);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Intel Soundwire Link Driver");
@@ -0,0 +1,19 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
/* Copyright(c) 2015-2022 Intel Corporation. */
#ifndef __SDW_INTEL_AUXDEVICE_H
#define __SDW_INTEL_AUXDEVICE_H
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;
struct sdw_intel_link_res link_res;
};
#define auxiliary_dev_to_sdw_intel_link_dev(auxiliary_dev) \
container_of(auxiliary_dev, struct sdw_intel_link_dev, auxdev)
#endif /* __SDW_INTEL_AUXDEVICE_H */
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
// Copyright(c) 2015-2023 Intel Corporation
#include <linux/acpi.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
#include "cadence_master.h"
#include "bus.h"
#include "intel.h"
int intel_start_bus(struct sdw_intel *sdw)
{
struct device *dev = sdw->cdns.dev;
struct sdw_cdns *cdns = &sdw->cdns;
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
*/
if (bus->multi_link)
sdw_intel_sync_arm(sdw);
ret = sdw_cdns_init(cdns);
if (ret < 0) {
dev_err(dev, "%s: unable to initialize Cadence IP: %d\n", __func__, ret);
return ret;
}
sdw_cdns_config_update(cdns);
if (bus->multi_link) {
ret = sdw_intel_sync_go(sdw);
if (ret < 0) {
dev_err(dev, "%s: sync go failed: %d\n", __func__, ret);
return ret;
}
}
ret = sdw_cdns_config_update_set_wait(cdns);
if (ret < 0) {
dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__);
return ret;
}
ret = sdw_cdns_enable_interrupt(cdns, true);
if (ret < 0) {
dev_err(dev, "%s: cannot enable interrupts: %d\n", __func__, ret);
return ret;
}
ret = sdw_cdns_exit_reset(cdns);
if (ret < 0) {
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;
}
int intel_start_bus_after_reset(struct sdw_intel *sdw)
{
struct device *dev = sdw->cdns.dev;
struct sdw_cdns *cdns = &sdw->cdns;
struct sdw_bus *bus = &cdns->bus;
bool clock_stop0;
int status;
int ret;
/*
* An exception condition occurs for the CLK_STOP_BUS_RESET
* case if one or more masters remain active. In this condition,
* all the masters are powered on for they are in the same power
* domain. Master can preserve its context for clock stop0, so
* there is no need to clear slave status and reset bus.
*/
clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
if (!clock_stop0) {
/*
* make sure all Slaves are tagged as UNATTACHED and
* provide reason for reinitialization
*/
status = SDW_UNATTACH_REQUEST_MASTER_RESET;
sdw_clear_slave_status(bus, status);
/*
* follow recommended programming flows to avoid
* timeouts when gsync is enabled
*/
if (bus->multi_link)
sdw_intel_sync_arm(sdw);
/*
* Re-initialize the IP since it was powered-off
*/
sdw_cdns_init(&sdw->cdns);
} else {
ret = sdw_cdns_enable_interrupt(cdns, true);
if (ret < 0) {
dev_err(dev, "cannot enable interrupts during resume\n");
return ret;
}
}
ret = sdw_cdns_clock_restart(cdns, !clock_stop0);
if (ret < 0) {
dev_err(dev, "unable to restart clock during resume\n");
if (!clock_stop0)
sdw_cdns_enable_interrupt(cdns, false);
return ret;
}
if (!clock_stop0) {
sdw_cdns_config_update(cdns);
if (bus->multi_link) {
ret = sdw_intel_sync_go(sdw);
if (ret < 0) {
dev_err(sdw->cdns.dev, "sync go failed during resume\n");
return ret;
}
}
ret = sdw_cdns_config_update_set_wait(cdns);
if (ret < 0) {
dev_err(dev, "%s: CONFIG_UPDATE BIT still set\n", __func__);
return ret;
}
ret = sdw_cdns_enable_interrupt(cdns, true);
if (ret < 0) {
dev_err(dev, "cannot enable interrupts during resume\n");
return ret;
}
ret = sdw_cdns_exit_reset(cdns);
if (ret < 0) {
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;
}
void intel_check_clock_stop(struct sdw_intel *sdw)
{
struct device *dev = sdw->cdns.dev;
bool clock_stop0;
clock_stop0 = sdw_cdns_is_clock_stop(&sdw->cdns);
if (!clock_stop0)
dev_err(dev, "%s: invalid configuration, clock was not stopped\n", __func__);
}
int intel_start_bus_after_clock_stop(struct sdw_intel *sdw)
{
struct device *dev = sdw->cdns.dev;
struct sdw_cdns *cdns = &sdw->cdns;
int ret;
ret = sdw_cdns_clock_restart(cdns, false);
if (ret < 0) {
dev_err(dev, "%s: unable to restart clock: %d\n", __func__, ret);
return ret;
}
ret = sdw_cdns_enable_interrupt(cdns, true);
if (ret < 0) {
dev_err(dev, "%s: cannot enable interrupts: %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;
}
int intel_stop_bus(struct sdw_intel *sdw, bool clock_stop)
{
struct device *dev = sdw->cdns.dev;
struct sdw_cdns *cdns = &sdw->cdns;
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)
dev_err(dev, "%s: cannot stop clock: %d\n", __func__, ret);
else
wake_enable = true;
}
ret = sdw_cdns_enable_interrupt(cdns, false);
if (ret < 0) {
dev_err(dev, "%s: cannot disable interrupts: %d\n", __func__, ret);
return ret;
}
ret = sdw_intel_link_power_down(sdw);
if (ret) {
dev_err(dev, "%s: Link power down failed: %d\n", __func__, ret);
return ret;
}
sdw_intel_shim_wake(sdw, wake_enable);
return 0;
}
/*
* bank switch routines
*/
int intel_pre_bank_switch(struct sdw_intel *sdw)
{
struct sdw_cdns *cdns = &sdw->cdns;
struct sdw_bus *bus = &cdns->bus;
/* Write to register only for multi-link */
if (!bus->multi_link)
return 0;
sdw_intel_sync_arm(sdw);
return 0;
}
int intel_post_bank_switch(struct sdw_intel *sdw)
{
struct sdw_cdns *cdns = &sdw->cdns;
struct sdw_bus *bus = &cdns->bus;
int ret = 0;
/* Write to register only for multi-link */
if (!bus->multi_link)
return 0;
mutex_lock(sdw->link_res->shim_lock);
/*
* post_bank_switch() ops is called from the bus in loop for
* all the Masters in the steam with the expectation that
* we trigger the bankswitch for the only first Master in the list
* and do nothing for the other Masters
*
* So, set the SYNCGO bit only if CMDSYNC bit is set for any Master.
*/
if (sdw_intel_sync_check_cmdsync_unlocked(sdw))
ret = sdw_intel_sync_go_unlocked(sdw);
mutex_unlock(sdw->link_res->shim_lock);
if (ret < 0)
dev_err(sdw->cdns.dev, "Post bank switch failed: %d\n", ret);
return ret;
}
+403
View File
@@ -0,0 +1,403 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-17 Intel Corporation.
/*
* SDW Intel Init Routines
*
* Initializes and creates SDW devices based on ACPI and Hardware values
*/
#include <linux/acpi.h>
#include <linux/export.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/auxiliary_bus.h>
#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"
static void intel_link_dev_release(struct device *dev)
{
struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
struct sdw_intel_link_dev *ldev = auxiliary_dev_to_sdw_intel_link_dev(auxdev);
kfree(ldev);
}
/* alloc, init and add link devices */
static struct sdw_intel_link_dev *intel_link_dev_register(struct sdw_intel_res *res,
struct sdw_intel_ctx *ctx,
struct fwnode_handle *fwnode,
const char *name,
int link_id)
{
struct sdw_intel_link_dev *ldev;
struct sdw_intel_link_res *link;
struct auxiliary_device *auxdev;
int ret;
ldev = kzalloc_obj(*ldev);
if (!ldev)
return ERR_PTR(-ENOMEM);
auxdev = &ldev->auxdev;
auxdev->name = name;
auxdev->dev.parent = res->parent;
auxdev->dev.fwnode = fwnode;
auxdev->dev.release = intel_link_dev_release;
/* we don't use an IDA since we already have a link ID */
auxdev->id = link_id;
/*
* keep a handle on the allocated memory, to be used in all other functions.
* Since the same pattern is used to skip links that are not enabled, there is
* no need to check if ctx->ldev[i] is NULL later on.
*/
ctx->ldev[link_id] = ldev;
/* Add link information used in the driver probe */
link = &ldev->link_res;
link->hw_ops = res->hw_ops;
link->mmio_base = res->mmio_base;
if (!res->ext) {
link->registers = res->mmio_base + SDW_LINK_BASE
+ (SDW_LINK_SIZE * link_id);
link->ip_offset = 0;
link->shim = res->mmio_base + res->shim_base;
link->alh = res->mmio_base + res->alh_base;
link->shim_lock = &ctx->shim_lock;
} else {
link->registers = res->mmio_base + SDW_IP_BASE(link_id);
link->ip_offset = SDW_CADENCE_MCP_IP_OFFSET;
link->shim = res->mmio_base + SDW_SHIM2_GENERIC_BASE(link_id);
link->shim_vs = res->mmio_base + SDW_SHIM2_VS_BASE(link_id);
link->shim_lock = res->eml_lock;
link->mic_privacy = res->mic_privacy;
}
link->ops = res->ops;
link->dev = res->dev;
link->clock_stop_quirks = res->clock_stop_quirks;
link->shim_mask = &ctx->shim_mask;
link->link_mask = ctx->link_mask;
link->hbus = res->hbus;
/* now follow the two-step init/add sequence */
ret = auxiliary_device_init(auxdev);
if (ret < 0) {
dev_err(res->parent, "failed to initialize link dev %s link_id %d\n",
name, link_id);
kfree(ldev);
return ERR_PTR(ret);
}
ret = auxiliary_device_add(&ldev->auxdev);
if (ret < 0) {
dev_err(res->parent, "failed to add link dev %s link_id %d\n",
ldev->auxdev.name, link_id);
/* ldev will be freed with the put_device() and .release sequence */
auxiliary_device_uninit(&ldev->auxdev);
return ERR_PTR(ret);
}
return ldev;
}
static void intel_link_dev_unregister(struct sdw_intel_link_dev *ldev)
{
auxiliary_device_delete(&ldev->auxdev);
auxiliary_device_uninit(&ldev->auxdev);
}
static int sdw_intel_cleanup(struct sdw_intel_ctx *ctx)
{
struct sdw_intel_link_dev *ldev;
u32 link_mask;
int i;
link_mask = ctx->link_mask;
for (i = 0; i < ctx->count; i++) {
if (!(link_mask & BIT(i)))
continue;
ldev = ctx->ldev[i];
pm_runtime_disable(&ldev->auxdev.dev);
if (!ldev->link_res.clock_stop_quirks)
pm_runtime_put_noidle(ldev->link_res.dev);
intel_link_dev_unregister(ldev);
}
return 0;
}
irqreturn_t sdw_intel_thread(int irq, void *dev_id)
{
struct sdw_intel_ctx *ctx = dev_id;
struct sdw_intel_link_res *link;
list_for_each_entry(link, &ctx->link_list, list)
sdw_cdns_irq(irq, link->cdns);
return IRQ_HANDLED;
}
EXPORT_SYMBOL_NS(sdw_intel_thread, "SOUNDWIRE_INTEL_INIT");
static struct sdw_intel_ctx
*sdw_intel_probe_controller(struct sdw_intel_res *res)
{
struct sdw_intel_link_res *link;
struct sdw_intel_link_dev *ldev;
struct sdw_intel_ctx *ctx;
struct acpi_device *adev;
struct sdw_slave *slave;
struct list_head *node;
struct sdw_bus *bus;
u32 link_mask;
int num_slaves = 0;
int count;
int i;
if (!res)
return NULL;
adev = acpi_fetch_acpi_dev(res->handle);
if (!adev)
return NULL;
if (!res->count)
return NULL;
count = res->count;
dev_dbg(&adev->dev, "Creating %d SDW Link devices\n", count);
/*
* we need to alloc/free memory manually and can't use devm:
* this routine may be called from a workqueue, and not from
* the parent .probe.
* If devm_ was used, the memory might never be freed on errors.
*/
ctx = kzalloc_obj(*ctx);
if (!ctx)
return NULL;
ctx->count = count;
/*
* allocate the array of pointers. The link-specific data is allocated
* as part of the first loop below and released with the auxiliary_device_uninit().
* If some links are disabled, the link pointer will remain NULL. Given that the
* number of links is small, this is simpler than using a list to keep track of links.
*/
ctx->ldev = kzalloc_objs(*ctx->ldev, ctx->count);
if (!ctx->ldev) {
kfree(ctx);
return NULL;
}
ctx->mmio_base = res->mmio_base;
ctx->shim_base = res->shim_base;
ctx->alh_base = res->alh_base;
ctx->link_mask = res->link_mask;
ctx->handle = res->handle;
mutex_init(&ctx->shim_lock);
link_mask = ctx->link_mask;
INIT_LIST_HEAD(&ctx->link_list);
for (i = 0; i < count; i++) {
if (!(link_mask & BIT(i)))
continue;
/*
* init and add a device for each link
*
* The name of the device will be soundwire_intel.link.[i],
* with the "soundwire_intel" module prefix automatically added
* by the auxiliary bus core.
*/
ldev = intel_link_dev_register(res,
ctx,
acpi_fwnode_handle(adev),
"link",
i);
if (IS_ERR(ldev))
goto err;
link = &ldev->link_res;
link->cdns = auxiliary_get_drvdata(&ldev->auxdev);
if (!link->cdns) {
dev_err(&adev->dev, "failed to get link->cdns\n");
/*
* 1 will be subtracted from i in the err label, but we need to call
* intel_link_dev_unregister for this ldev, so plus 1 now
*/
i++;
goto err;
}
list_add_tail(&link->list, &ctx->link_list);
bus = &link->cdns->bus;
/* Calculate number of slaves */
list_for_each(node, &bus->slaves)
num_slaves++;
}
ctx->peripherals = kmalloc_flex(*ctx->peripherals, array, num_slaves);
if (!ctx->peripherals)
goto err;
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->peripherals->array[i] = slave;
i++;
}
}
return ctx;
err:
while (i--) {
if (!(link_mask & BIT(i)))
continue;
ldev = ctx->ldev[i];
intel_link_dev_unregister(ldev);
}
kfree(ctx->ldev);
kfree(ctx);
return NULL;
}
static int
sdw_intel_startup_controller(struct sdw_intel_ctx *ctx)
{
struct acpi_device *adev = acpi_fetch_acpi_dev(ctx->handle);
struct sdw_intel_link_dev *ldev;
u32 link_mask;
int i;
if (!adev)
return -EINVAL;
if (!ctx->ldev)
return -EINVAL;
link_mask = ctx->link_mask;
/* Startup SDW Master devices */
for (i = 0; i < ctx->count; i++) {
if (!(link_mask & BIT(i)))
continue;
ldev = ctx->ldev[i];
intel_link_startup(&ldev->auxdev);
if (!ldev->link_res.clock_stop_quirks) {
/*
* we need to prevent the parent PCI device
* from entering pm_runtime suspend, so that
* power rails to the SoundWire IP are not
* turned off.
*/
pm_runtime_get_noresume(ldev->link_res.dev);
}
}
return 0;
}
/**
* sdw_intel_probe() - SoundWire Intel probe routine
* @res: resource data
*
* This registers an auxiliary device for each Master handled by the controller,
* and SoundWire Master and Slave devices will be created by the auxiliary
* device probe. All the information necessary is stored in the context, and
* the res argument pointer can be freed after this step.
* This function will be called after sdw_intel_acpi_scan() by SOF probe.
*/
struct sdw_intel_ctx
*sdw_intel_probe(struct sdw_intel_res *res)
{
return sdw_intel_probe_controller(res);
}
EXPORT_SYMBOL_NS(sdw_intel_probe, "SOUNDWIRE_INTEL_INIT");
/**
* sdw_intel_startup() - SoundWire Intel startup
* @ctx: SoundWire context allocated in the probe
*
* Startup Intel SoundWire controller. This function will be called after
* Intel Audio DSP is powered up.
*/
int sdw_intel_startup(struct sdw_intel_ctx *ctx)
{
return sdw_intel_startup_controller(ctx);
}
EXPORT_SYMBOL_NS(sdw_intel_startup, "SOUNDWIRE_INTEL_INIT");
/**
* sdw_intel_exit() - SoundWire Intel exit
* @ctx: SoundWire context allocated in the probe
*
* Delete the controller instances created and cleanup
*/
void sdw_intel_exit(struct sdw_intel_ctx *ctx)
{
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->peripherals);
kfree(ctx->ldev);
kfree(ctx);
}
EXPORT_SYMBOL_NS(sdw_intel_exit, "SOUNDWIRE_INTEL_INIT");
void sdw_intel_process_wakeen_event(struct sdw_intel_ctx *ctx)
{
struct sdw_intel_link_dev *ldev;
u32 link_mask;
int i;
if (!ctx->ldev)
return;
link_mask = ctx->link_mask;
/* Startup SDW Master devices */
for (i = 0; i < ctx->count; i++) {
if (!(link_mask & BIT(i)))
continue;
ldev = ctx->ldev[i];
intel_link_process_wakeen_event(&ldev->auxdev);
}
}
EXPORT_SYMBOL_NS(sdw_intel_process_wakeen_event, "SOUNDWIRE_INTEL_INIT");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Intel Soundwire Init Library");
+63
View File
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2023 Cirrus Logic, Inc. and
// Cirrus Logic International Semiconductor Ltd.
#include <linux/device.h>
#include <linux/fwnode.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/soundwire/sdw.h>
#include "irq.h"
static int sdw_irq_map(struct irq_domain *h, unsigned int virq,
irq_hw_number_t hw)
{
struct sdw_bus *bus = h->host_data;
irq_set_chip_data(virq, bus);
irq_set_chip(virq, &bus->irq_chip);
irq_set_nested_thread(virq, 1);
irq_set_noprobe(virq);
return 0;
}
static const struct irq_domain_ops sdw_domain_ops = {
.map = sdw_irq_map,
};
int sdw_irq_create(struct sdw_bus *bus,
struct fwnode_handle *fwnode)
{
bus->irq_chip.name = dev_name(bus->dev);
bus->domain = irq_domain_create_linear(fwnode, SDW_FW_MAX_DEVICES,
&sdw_domain_ops, bus);
if (!bus->domain) {
dev_err(bus->dev, "Failed to add IRQ domain\n");
return -EINVAL;
}
return 0;
}
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(slave->irq);
}
void sdw_irq_create_mapping(struct sdw_slave *slave)
{
slave->irq = irq_create_mapping(slave->bus->domain, slave->index);
if (!slave->irq)
dev_warn(&slave->dev, "Failed to map IRQ\n");
devm_add_action_or_reset(&slave->dev, sdw_irq_dispose_mapping, slave);
}
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (C) 2023 Cirrus Logic, Inc. and
* Cirrus Logic International Semiconductor Ltd.
*/
#ifndef __SDW_IRQ_H
#define __SDW_IRQ_H
#include <linux/soundwire/sdw.h>
#include <linux/fwnode.h>
#if IS_ENABLED(CONFIG_IRQ_DOMAIN)
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);
#else /* CONFIG_IRQ_DOMAIN */
static inline int sdw_irq_create(struct sdw_bus *bus,
struct fwnode_handle *fwnode)
{
return 0;
}
static inline void sdw_irq_delete(struct sdw_bus *bus)
{
}
static inline void sdw_irq_create_mapping(struct sdw_slave *slave)
{
}
#endif /* CONFIG_IRQ_DOMAIN */
#endif /* __SDW_IRQ_H */
+188
View File
@@ -0,0 +1,188 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2019-2020 Intel Corporation.
#include <linux/device.h>
#include <linux/acpi.h>
#include <linux/pm_runtime.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
/*
* The 3s value for autosuspend will only be used if there are no
* devices physically attached on a bus segment. In practice enabling
* the bus operation will result in children devices become active and
* the master device will only suspend when all its children are no
* longer active.
*/
#define SDW_MASTER_SUSPEND_DELAY_MS 3000
/*
* The sysfs for properties reflects the MIPI description as given
* in the MIPI DisCo spec
*
* Base file is:
* sdw-master-N
* |---- revision
* |---- clk_stop_modes
* |---- max_clk_freq
* |---- clk_freq
* |---- clk_gears
* |---- default_row
* |---- default_col
* |---- dynamic_shape
* |---- err_threshold
*/
#define sdw_master_attr(field, format_string) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_master_device *md = dev_to_sdw_master_device(dev); \
return sprintf(buf, format_string, md->bus->prop.field); \
} \
static DEVICE_ATTR_RO(field)
sdw_master_attr(revision, "0x%x\n");
sdw_master_attr(clk_stop_modes, "0x%x\n");
sdw_master_attr(max_clk_freq, "%d\n");
sdw_master_attr(default_row, "%d\n");
sdw_master_attr(default_col, "%d\n");
sdw_master_attr(default_frame_rate, "%d\n");
sdw_master_attr(dynamic_frame, "%d\n");
sdw_master_attr(err_threshold, "%d\n");
static ssize_t clock_frequencies_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_master_device *md = dev_to_sdw_master_device(dev);
ssize_t size = 0;
int i;
for (i = 0; i < md->bus->prop.num_clk_freq; i++)
size += sprintf(buf + size, "%8d ",
md->bus->prop.clk_freq[i]);
size += sprintf(buf + size, "\n");
return size;
}
static DEVICE_ATTR_RO(clock_frequencies);
static ssize_t clock_gears_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_master_device *md = dev_to_sdw_master_device(dev);
ssize_t size = 0;
int i;
for (i = 0; i < md->bus->prop.num_clk_gears; i++)
size += sprintf(buf + size, "%8d ",
md->bus->prop.clk_gears[i]);
size += sprintf(buf + size, "\n");
return size;
}
static DEVICE_ATTR_RO(clock_gears);
static struct attribute *master_node_attrs[] = {
&dev_attr_revision.attr,
&dev_attr_clk_stop_modes.attr,
&dev_attr_max_clk_freq.attr,
&dev_attr_default_row.attr,
&dev_attr_default_col.attr,
&dev_attr_default_frame_rate.attr,
&dev_attr_dynamic_frame.attr,
&dev_attr_err_threshold.attr,
&dev_attr_clock_frequencies.attr,
&dev_attr_clock_gears.attr,
NULL,
};
ATTRIBUTE_GROUPS(master_node);
static void sdw_master_device_release(struct device *dev)
{
struct sdw_master_device *md = dev_to_sdw_master_device(dev);
kfree(md);
}
static const struct dev_pm_ops master_dev_pm = {
SET_RUNTIME_PM_OPS(pm_generic_runtime_suspend,
pm_generic_runtime_resume, NULL)
};
const struct device_type sdw_master_type = {
.name = "soundwire_master",
.release = sdw_master_device_release,
.pm = &master_dev_pm,
};
/**
* sdw_master_device_add() - create a Linux Master Device representation.
* @bus: SDW bus instance
* @parent: parent device
* @fwnode: firmware node handle
*/
int sdw_master_device_add(struct sdw_bus *bus, struct device *parent,
struct fwnode_handle *fwnode)
{
struct sdw_master_device *md;
int ret;
if (!parent)
return -EINVAL;
md = kzalloc_obj(*md);
if (!md)
return -ENOMEM;
md->dev.bus = &sdw_bus_type;
md->dev.type = &sdw_master_type;
md->dev.parent = parent;
md->dev.groups = master_node_groups;
md->dev.of_node = parent->of_node;
md->dev.fwnode = fwnode;
md->dev.dma_mask = parent->dma_mask;
dev_set_name(&md->dev, "sdw-master-%d-%d", bus->controller_id, bus->link_id);
ret = device_register(&md->dev);
if (ret) {
dev_err(parent, "Failed to add master: ret %d\n", ret);
/*
* On err, don't free but drop ref as this will be freed
* when release method is invoked.
*/
put_device(&md->dev);
goto device_register_err;
}
/* add shortcuts to improve code readability/compactness */
md->bus = bus;
bus->dev = &md->dev;
bus->md = md;
pm_runtime_set_autosuspend_delay(&bus->md->dev, SDW_MASTER_SUSPEND_DELAY_MS);
pm_runtime_use_autosuspend(&bus->md->dev);
pm_runtime_mark_last_busy(&bus->md->dev);
pm_runtime_set_active(&bus->md->dev);
pm_runtime_enable(&bus->md->dev);
pm_runtime_idle(&bus->md->dev);
device_register_err:
return ret;
}
/**
* sdw_master_device_del() - delete a Linux Master Device representation.
* @bus: bus handle
*
* This function is the dual of sdw_master_device_add()
*/
int sdw_master_device_del(struct sdw_bus *bus)
{
pm_runtime_disable(&bus->md->dev);
device_unregister(bus->dev);
return 0;
}
+528
View File
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-17 Intel Corporation.
/*
* MIPI Discovery And Configuration (DisCo) Specification for SoundWire
* specifies properties to be implemented for SoundWire Masters and Slaves.
* The DisCo spec doesn't mandate these properties. However, SDW bus cannot
* work without knowing these values.
*
* The helper functions read the Master and Slave properties. Implementers
* of Master or Slave drivers can use any of the below three mechanisms:
* a) Use these APIs here as .read_prop() callback for Master and Slave
* b) Implement own methods and set those as .read_prop(), but invoke
* APIs in this file for generic read and override the values with
* platform specific data
* c) Implement ones own methods which do not use anything provided
* here
*/
#include <linux/device.h>
#include <linux/property.h>
#include <linux/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
*/
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;
int ret;
int i;
device_property_read_u32(bus->dev,
"mipi-sdw-sw-interface-revision",
&prop->revision);
/* Find master handle */
snprintf(name, sizeof(name),
"mipi-sdw-link-%d-subproperties", bus->link_id);
link = device_get_named_child_node(bus->dev, name);
if (!link) {
dev_err(bus->dev, "Master node %s not found\n", name);
return -EIO;
}
if (mipi_fwnode_property_read_bool(link,
"mipi-sdw-clock-stop-mode0-supported"))
prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE0);
if (mipi_fwnode_property_read_bool(link,
"mipi-sdw-clock-stop-mode1-supported"))
prop->clk_stop_modes |= BIT(SDW_CLK_STOP_MODE1);
fwnode_property_read_u32(link,
"mipi-sdw-max-clock-frequency",
&prop->max_clk_freq);
nval = fwnode_property_count_u32(link, "mipi-sdw-clock-frequencies-supported");
if (nval > 0) {
prop->num_clk_freq = nval;
prop->clk_freq = devm_kcalloc(bus->dev, prop->num_clk_freq,
sizeof(*prop->clk_freq),
GFP_KERNEL);
if (!prop->clk_freq) {
fwnode_handle_put(link);
return -ENOMEM;
}
ret = fwnode_property_read_u32_array(link,
"mipi-sdw-clock-frequencies-supported",
prop->clk_freq, prop->num_clk_freq);
if (ret < 0)
return ret;
}
/*
* Check the frequencies supported. If FW doesn't provide max
* freq, then populate here by checking values.
*/
if (!prop->max_clk_freq && prop->clk_freq) {
prop->max_clk_freq = prop->clk_freq[0];
for (i = 1; i < prop->num_clk_freq; i++) {
if (prop->clk_freq[i] > prop->max_clk_freq)
prop->max_clk_freq = prop->clk_freq[i];
}
}
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) {
fwnode_handle_put(link);
return -ENOMEM;
}
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",
&prop->default_frame_rate);
fwnode_property_read_u32(link, "mipi-sdw-default-frame-row-size",
&prop->default_row);
fwnode_property_read_u32(link, "mipi-sdw-default-frame-col-size",
&prop->default_col);
prop->dynamic_frame = 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);
static int sdw_slave_read_dp0(struct sdw_slave *slave,
struct fwnode_handle *port,
struct sdw_dp0_prop *dp0)
{
int nval;
int ret;
fwnode_property_read_u32(port, "mipi-sdw-port-max-wordlength",
&dp0->max_word);
fwnode_property_read_u32(port, "mipi-sdw-port-min-wordlength",
&dp0->min_word);
nval = fwnode_property_count_u32(port, "mipi-sdw-port-wordlength-configs");
if (nval > 0) {
dp0->num_words = nval;
dp0->words = devm_kcalloc(&slave->dev,
dp0->num_words, sizeof(*dp0->words),
GFP_KERNEL);
if (!dp0->words)
return -ENOMEM;
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 = mipi_fwnode_property_read_bool(port,
"mipi-sdw-bra-flow-controlled");
dp0->simple_ch_prep_sm = mipi_fwnode_property_read_bool(port,
"mipi-sdw-simplified-channel-prepare-sm");
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;
}
static int sdw_slave_read_dpn(struct sdw_slave *slave,
struct sdw_dpn_prop *dpn, int count, int ports,
char *type)
{
struct fwnode_handle *node;
u32 bit, i = 0;
unsigned long addr;
char name[40];
int nval;
int ret;
addr = ports;
/* valid ports are 1 to 14 so apply mask */
addr &= GENMASK(14, 1);
for_each_set_bit(bit, &addr, 32) {
snprintf(name, sizeof(name),
"mipi-sdw-dp-%d-%s-subproperties", bit, type);
dpn[i].num = bit;
node = device_get_named_child_node(&slave->dev, name);
if (!node) {
dev_err(&slave->dev, "%s dpN not found\n", name);
return -EIO;
}
fwnode_property_read_u32(node, "mipi-sdw-port-max-wordlength",
&dpn[i].max_word);
fwnode_property_read_u32(node, "mipi-sdw-port-min-wordlength",
&dpn[i].min_word);
nval = fwnode_property_count_u32(node, "mipi-sdw-port-wordlength-configs");
if (nval > 0) {
dpn[i].num_words = nval;
dpn[i].words = devm_kcalloc(&slave->dev,
dpn[i].num_words,
sizeof(*dpn[i].words),
GFP_KERNEL);
if (!dpn[i].words) {
fwnode_handle_put(node);
return -ENOMEM;
}
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",
&dpn[i].type);
fwnode_property_read_u32(node,
"mipi-sdw-max-grouping-supported",
&dpn[i].max_grouping);
dpn[i].simple_ch_prep_sm = mipi_fwnode_property_read_bool(node,
"mipi-sdw-simplified-channelprepare-sm");
fwnode_property_read_u32(node,
"mipi-sdw-port-channelprepare-timeout",
&dpn[i].ch_prep_timeout);
fwnode_property_read_u32(node,
"mipi-sdw-imp-def-dpn-interrupts-supported",
&dpn[i].imp_def_interrupts);
fwnode_property_read_u32(node, "mipi-sdw-min-channel-number",
&dpn[i].min_ch);
fwnode_property_read_u32(node, "mipi-sdw-max-channel-number",
&dpn[i].max_ch);
nval = fwnode_property_count_u32(node, "mipi-sdw-channel-number-list");
if (nval > 0) {
dpn[i].num_channels = nval;
dpn[i].channels = devm_kcalloc(&slave->dev,
dpn[i].num_channels,
sizeof(*dpn[i].channels),
GFP_KERNEL);
if (!dpn[i].channels) {
fwnode_handle_put(node);
return -ENOMEM;
}
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");
if (nval > 0) {
dpn[i].num_ch_combinations = nval;
dpn[i].ch_combinations = devm_kcalloc(&slave->dev,
dpn[i].num_ch_combinations,
sizeof(*dpn[i].ch_combinations),
GFP_KERNEL);
if (!dpn[i].ch_combinations) {
fwnode_handle_put(node);
return -ENOMEM;
}
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,
"mipi-sdw-modes-supported", &dpn[i].modes);
fwnode_property_read_u32(node, "mipi-sdw-max-async-buffer",
&dpn[i].max_async_buffer);
dpn[i].block_pack_mode = 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);
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++;
}
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
*/
int sdw_slave_read_prop(struct sdw_slave *slave)
{
struct sdw_slave_prop *prop = &slave->prop;
struct device *dev = &slave->dev;
struct fwnode_handle *port;
int nval;
int ret;
device_property_read_u32(dev, "mipi-sdw-sw-interface-revision",
&prop->mipi_revision);
prop->wake_capable = mipi_device_property_read_bool(dev,
"mipi-sdw-wake-up-unavailable");
prop->wake_capable = !prop->wake_capable;
prop->test_mode_capable = mipi_device_property_read_bool(dev,
"mipi-sdw-test-mode-supported");
prop->clk_stop_mode1 = false;
if (mipi_device_property_read_bool(dev,
"mipi-sdw-clock-stop-mode1-supported"))
prop->clk_stop_mode1 = true;
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);
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 = mipi_device_property_read_bool(dev,
"mipi-sdw-highPHY-capable");
prop->paging_support = mipi_device_property_read_bool(dev,
"mipi-sdw-paging-supported");
prop->bank_delay_support = mipi_device_property_read_bool(dev,
"mipi-sdw-bank-delay-supported");
device_property_read_u32(dev,
"mipi-sdw-port15-read-behavior", &prop->p15_behave);
device_property_read_u32(dev, "mipi-sdw-master-count",
&prop->master_count);
device_property_read_u32(dev, "mipi-sdw-source-port-list",
&prop->source_ports);
device_property_read_u32(dev, "mipi-sdw-sink-port-list",
&prop->sink_ports);
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");
} else {
prop->dp0_prop = devm_kzalloc(&slave->dev,
sizeof(*prop->dp0_prop),
GFP_KERNEL);
if (!prop->dp0_prop) {
fwnode_handle_put(port);
return -ENOMEM;
}
sdw_slave_read_dp0(slave, port, prop->dp0_prop);
fwnode_handle_put(port);
}
/*
* Based on each DPn port, get source and sink dpn properties.
* Also, some ports can operate as both source or sink.
*/
/* Allocate memory for set bits in port lists */
nval = hweight32(prop->source_ports);
prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->src_dpn_prop),
GFP_KERNEL);
if (!prop->src_dpn_prop)
return -ENOMEM;
/* Read dpn properties for source port(s) */
sdw_slave_read_dpn(slave, prop->src_dpn_prop, nval,
prop->source_ports, "source");
nval = hweight32(prop->sink_ports);
prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
sizeof(*prop->sink_dpn_prop),
GFP_KERNEL);
if (!prop->sink_dpn_prop)
return -ENOMEM;
/* Read dpn properties for sink port(s) */
sdw_slave_read_dpn(slave, prop->sink_dpn_prop, nval,
prop->sink_ports, "sink");
return sdw_slave_read_lane_mapping(slave);
}
EXPORT_SYMBOL(sdw_slave_read_prop);
File diff suppressed because it is too large Load Diff
+286
View File
@@ -0,0 +1,286 @@
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-17 Intel Corporation.
#include <linux/acpi.h>
#include <linux/of.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include <sound/sdca.h>
#include "bus.h"
#include "sysfs_local.h"
static void sdw_slave_release(struct device *dev)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
of_node_put(slave->dev.of_node);
mutex_destroy(&slave->sdw_dev_lock);
kfree(slave);
}
const struct device_type sdw_slave_type = {
.name = "sdw_slave",
.release = sdw_slave_release,
.uevent = sdw_slave_uevent,
};
EXPORT_SYMBOL_GPL(sdw_slave_type);
int sdw_slave_add(struct sdw_bus *bus,
struct sdw_slave_id *id, struct fwnode_handle *fwnode)
{
struct sdw_slave *slave;
int ret;
int i;
slave = kzalloc_obj(*slave);
if (!slave)
return -ENOMEM;
/* Initialize data structure */
memcpy(&slave->id, id, sizeof(*id));
slave->dev.parent = bus->dev;
slave->dev.fwnode = fwnode;
if (id->unique_id == SDW_IGNORED_UNIQUE_ID) {
/* name shall be sdw:ctrl:link:mfg:part:class */
dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x",
bus->controller_id, bus->link_id, id->mfg_id, id->part_id,
id->class_id);
} else {
/* name shall be sdw:ctrl:link:mfg:part:class:unique */
dev_set_name(&slave->dev, "sdw:%01x:%01x:%04x:%04x:%02x:%01x",
bus->controller_id, bus->link_id, id->mfg_id, id->part_id,
id->class_id, id->unique_id);
}
slave->dev.bus = &sdw_bus_type;
slave->dev.of_node = of_node_get(to_of_node(fwnode));
slave->dev.type = &sdw_slave_type;
slave->dev.groups = sdw_slave_status_attr_groups;
slave->bus = bus;
slave->status = SDW_SLAVE_UNATTACHED;
init_completion(&slave->enumeration_complete);
init_completion(&slave->initialization_complete);
slave->dev_num = 0;
slave->probed = false;
slave->first_interrupt_done = false;
mutex_init(&slave->sdw_dev_lock);
for (i = 0; i < SDW_MAX_PORTS; i++)
init_completion(&slave->port_ready[i]);
mutex_lock(&bus->bus_lock);
list_add_tail(&slave->node, &bus->slaves);
mutex_unlock(&bus->bus_lock);
/*
* The Soundwire driver probe may optionally register SDCA
* sub-devices, one per Function. This means the information
* on the SDCA revision and the number/type of Functions need
* to be extracted from platform firmware before the SoundWire
* driver probe, and as a consequence before the SoundWire
* device_register() below.
*/
sdca_lookup_interface_revision(slave);
sdca_lookup_functions(slave);
ret = device_register(&slave->dev);
if (ret) {
dev_err(bus->dev, "Failed to add slave: ret %d\n", ret);
/*
* On err, don't free but drop ref as this will be freed
* when release method is invoked.
*/
mutex_lock(&bus->bus_lock);
list_del(&slave->node);
mutex_unlock(&bus->bus_lock);
put_device(&slave->dev);
return ret;
}
sdw_slave_debugfs_init(slave);
return ret;
}
EXPORT_SYMBOL(sdw_slave_add);
#if IS_ENABLED(CONFIG_ACPI)
static bool find_slave(struct sdw_bus *bus,
struct acpi_device *adev,
struct sdw_slave_id *id)
{
unsigned int link_id;
u64 addr;
int ret;
if (acpi_bus_get_status(adev) || !acpi_dev_ready_for_enumeration(adev))
return false;
ret = acpi_get_local_u64_address(adev->handle, &addr);
if (ret < 0)
return false;
if (bus->ops->override_adr)
addr = bus->ops->override_adr(bus, addr);
if (!addr)
return false;
/* Extract link id from ADR, Bit 51 to 48 (included) */
link_id = SDW_DISCO_LINK_ID(addr);
/* Check for link_id match */
if (link_id != bus->link_id)
return false;
sdw_extract_slave_id(bus, addr, id);
return true;
}
struct sdw_acpi_child_walk_data {
struct sdw_bus *bus;
struct acpi_device *adev;
struct sdw_slave_id id;
bool ignore_unique_id;
};
static int sdw_acpi_check_duplicate(struct acpi_device *adev, void *data)
{
struct sdw_acpi_child_walk_data *cwd = data;
struct sdw_bus *bus = cwd->bus;
struct sdw_slave_id id;
if (adev == cwd->adev)
return 0;
if (!find_slave(bus, adev, &id))
return 0;
if (cwd->id.sdw_version != id.sdw_version || cwd->id.mfg_id != id.mfg_id ||
cwd->id.part_id != id.part_id || cwd->id.class_id != id.class_id)
return 0;
if (cwd->id.unique_id != id.unique_id) {
dev_dbg(bus->dev,
"Valid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n",
cwd->id.unique_id, id.unique_id, cwd->id.mfg_id,
cwd->id.part_id);
cwd->ignore_unique_id = false;
return 0;
}
dev_err(bus->dev,
"Invalid unique IDs 0x%x 0x%x for Slave mfg_id 0x%04x, part_id 0x%04x\n",
cwd->id.unique_id, id.unique_id, cwd->id.mfg_id, cwd->id.part_id);
return -ENODEV;
}
static int sdw_acpi_find_one(struct acpi_device *adev, void *data)
{
struct sdw_bus *bus = data;
struct sdw_acpi_child_walk_data cwd = {
.bus = bus,
.adev = adev,
.ignore_unique_id = true,
};
int ret;
if (!find_slave(bus, adev, &cwd.id))
return 0;
/* Brute-force O(N^2) search for duplicates. */
ret = acpi_dev_for_each_child(ACPI_COMPANION(bus->dev),
sdw_acpi_check_duplicate, &cwd);
if (ret)
return ret;
if (cwd.ignore_unique_id)
cwd.id.unique_id = SDW_IGNORED_UNIQUE_ID;
/* Ignore errors and continue. */
sdw_slave_add(bus, &cwd.id, acpi_fwnode_handle(adev));
return 0;
}
/*
* sdw_acpi_find_slaves() - Find Slave devices in Master ACPI node
* @bus: SDW bus instance
*
* Scans Master ACPI node for SDW child Slave devices and registers it.
*/
int sdw_acpi_find_slaves(struct sdw_bus *bus)
{
struct acpi_device *parent;
parent = ACPI_COMPANION(bus->dev);
if (!parent) {
dev_err(bus->dev, "Can't find parent for acpi bind\n");
return -ENODEV;
}
return acpi_dev_for_each_child(parent, sdw_acpi_find_one, bus);
}
#endif
/*
* sdw_of_find_slaves() - Find Slave devices in master device tree node
* @bus: SDW bus instance
*
* Scans Master DT node for SDW child Slave devices and registers it.
*/
int sdw_of_find_slaves(struct sdw_bus *bus)
{
struct device *dev = bus->dev;
struct device_node *node;
for_each_child_of_node(bus->dev->of_node, node) {
int link_id, ret, len;
unsigned int sdw_version;
const char *compat = NULL;
struct sdw_slave_id id;
const __be32 *addr;
ret = of_property_read_string(node, "compatible", &compat);
if (ret)
continue;
ret = sscanf(compat, "sdw%01x%04hx%04hx%02hhx", &sdw_version,
&id.mfg_id, &id.part_id, &id.class_id);
if (ret != 4) {
dev_err(dev, "Invalid compatible string found %s\n",
compat);
continue;
}
addr = of_get_property(node, "reg", &len);
if (!addr || (len < 2 * sizeof(u32))) {
dev_err(dev, "Invalid Link and Instance ID\n");
continue;
}
link_id = be32_to_cpup(addr++);
id.unique_id = be32_to_cpup(addr);
id.sdw_version = sdw_version;
/* Check for link_id match */
if (link_id != bus->link_id)
continue;
sdw_slave_add(bus, &id, of_fwnode_handle(node));
}
return 0;
}
struct device *of_sdw_find_device_by_node(struct device_node *np)
{
return bus_find_device_by_of_node(&sdw_bus_type, np);
}
EXPORT_SYMBOL_GPL(of_sdw_find_device_by_node);
MODULE_IMPORT_NS("SND_SOC_SDCA");
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright(c) 2015-2020 Intel Corporation. */
#ifndef __SDW_SYSFS_LOCAL_H
#define __SDW_SYSFS_LOCAL_H
/*
* SDW sysfs APIs -
*/
/* basic attributes to report status of Slave (attachment, dev_num) */
extern const struct attribute_group *sdw_slave_status_attr_groups[];
/* 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_dpn_init(struct sdw_slave *slave);
#endif /* __SDW_SYSFS_LOCAL_H */
+265
View File
@@ -0,0 +1,265 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2015-2020 Intel Corporation.
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "sysfs_local.h"
/*
* Slave sysfs
*/
/*
* The sysfs for Slave reflects the MIPI description as given
* in the MIPI DisCo spec.
* status and device_number come directly from the MIPI SoundWire
* 1.x specification.
*
* Base file is device
* |---- status
* |---- device_number
* |---- modalias
* |---- dev-properties
* |---- mipi_revision
* |---- wake_capable
* |---- test_mode_capable
* |---- clk_stop_mode1
* |---- simple_clk_stop_capable
* |---- clk_stop_timeout
* |---- ch_prep_timeout
* |---- reset_behave
* |---- high_PHY_capable
* |---- paging_support
* |---- bank_delay_support
* |---- p15_behave
* |---- master_count
* |---- source_ports
* |---- sink_ports
* |---- dp0
* |---- max_word
* |---- min_word
* |---- words
* |---- BRA_flow_controlled
* |---- simple_ch_prep_sm
* |---- imp_def_interrupts
* |---- dpN_<sink/src>
* |---- max_word
* |---- min_word
* |---- words
* |---- type
* |---- max_grouping
* |---- simple_ch_prep_sm
* |---- ch_prep_timeout
* |---- imp_def_interrupts
* |---- min_ch
* |---- max_ch
* |---- channels
* |---- ch_combinations
* |---- max_async_buffer
* |---- block_pack_mode
* |---- port_encoding
*
*/
#define sdw_slave_attr(field, format_string) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
return sprintf(buf, format_string, slave->prop.field); \
} \
static DEVICE_ATTR_RO(field)
sdw_slave_attr(mipi_revision, "0x%x\n");
sdw_slave_attr(wake_capable, "%d\n");
sdw_slave_attr(test_mode_capable, "%d\n");
sdw_slave_attr(clk_stop_mode1, "%d\n");
sdw_slave_attr(simple_clk_stop_capable, "%d\n");
sdw_slave_attr(clk_stop_timeout, "%d\n");
sdw_slave_attr(ch_prep_timeout, "%d\n");
sdw_slave_attr(reset_behave, "%d\n");
sdw_slave_attr(high_PHY_capable, "%d\n");
sdw_slave_attr(paging_support, "%d\n");
sdw_slave_attr(bank_delay_support, "%d\n");
sdw_slave_attr(p15_behave, "%d\n");
sdw_slave_attr(master_count, "%d\n");
sdw_slave_attr(source_ports, "0x%x\n");
sdw_slave_attr(sink_ports, "0x%x\n");
static ssize_t modalias_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
return sdw_slave_modalias(slave, buf, 256);
}
static DEVICE_ATTR_RO(modalias);
static struct attribute *slave_attrs[] = {
&dev_attr_modalias.attr,
NULL,
};
static const struct attribute_group slave_attr_group = {
.attrs = slave_attrs,
};
static struct attribute *slave_dev_attrs[] = {
&dev_attr_mipi_revision.attr,
&dev_attr_wake_capable.attr,
&dev_attr_test_mode_capable.attr,
&dev_attr_clk_stop_mode1.attr,
&dev_attr_simple_clk_stop_capable.attr,
&dev_attr_clk_stop_timeout.attr,
&dev_attr_ch_prep_timeout.attr,
&dev_attr_reset_behave.attr,
&dev_attr_high_PHY_capable.attr,
&dev_attr_paging_support.attr,
&dev_attr_bank_delay_support.attr,
&dev_attr_p15_behave.attr,
&dev_attr_master_count.attr,
&dev_attr_source_ports.attr,
&dev_attr_sink_ports.attr,
NULL,
};
static const struct attribute_group sdw_slave_dev_attr_group = {
.attrs = slave_dev_attrs,
.name = "dev-properties",
};
/*
* DP0 sysfs
*/
#define sdw_dp0_attr(field, format_string) \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
return sprintf(buf, format_string, slave->prop.dp0_prop->field);\
} \
static DEVICE_ATTR_RO(field)
sdw_dp0_attr(max_word, "%d\n");
sdw_dp0_attr(min_word, "%d\n");
sdw_dp0_attr(BRA_flow_controlled, "%d\n");
sdw_dp0_attr(simple_ch_prep_sm, "%d\n");
sdw_dp0_attr(imp_def_interrupts, "0x%x\n");
static ssize_t words_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
ssize_t size = 0;
int i;
for (i = 0; i < slave->prop.dp0_prop->num_words; i++)
size += sprintf(buf + size, "%d ",
slave->prop.dp0_prop->words[i]);
size += sprintf(buf + size, "\n");
return size;
}
static DEVICE_ATTR_RO(words);
static struct attribute *dp0_attrs[] = {
&dev_attr_max_word.attr,
&dev_attr_min_word.attr,
&dev_attr_words.attr,
&dev_attr_BRA_flow_controlled.attr,
&dev_attr_simple_ch_prep_sm.attr,
&dev_attr_imp_def_interrupts.attr,
NULL,
};
static umode_t dp0_attr_visible(struct kobject *kobj, struct attribute *attr,
int n)
{
struct sdw_slave *slave = dev_to_sdw_dev(kobj_to_dev(kobj));
if (slave->prop.dp0_prop)
return attr->mode;
return 0;
}
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)
return true;
return false;
}
DEFINE_SYSFS_GROUP_VISIBLE(dp0);
static const struct attribute_group dp0_group = {
.attrs = dp0_attrs,
.is_visible = SYSFS_GROUP_VISIBLE(dp0),
.name = "dp0",
};
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 highlight users to the fact that these status values
* are not expected.
*/
static const char *const slave_status[] = {
[SDW_SLAVE_UNATTACHED] = "UNATTACHED",
[SDW_SLAVE_ATTACHED] = "Attached",
[SDW_SLAVE_ALERT] = "Alert",
[SDW_SLAVE_RESERVED] = "RESERVED",
};
static ssize_t status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
return sprintf(buf, "%s\n", slave_status[slave->status]);
}
static DEVICE_ATTR_RO(status);
static ssize_t device_number_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct sdw_slave *slave = dev_to_sdw_dev(dev);
if (slave->status == SDW_SLAVE_UNATTACHED)
return sprintf(buf, "%s", "N/A");
else
return sprintf(buf, "%d", slave->dev_num);
}
static DEVICE_ATTR_RO(device_number);
static struct attribute *slave_status_attrs[] = {
&dev_attr_status.attr,
&dev_attr_device_number.attr,
NULL,
};
/*
* we don't use ATTRIBUTES_GROUP here since the group is used in a
* separate file and can't be handled as a static.
*/
static const struct attribute_group sdw_slave_status_attr_group = {
.attrs = slave_status_attrs,
};
const struct attribute_group *sdw_slave_status_attr_groups[] = {
&sdw_slave_status_attr_group,
NULL
};
@@ -0,0 +1,303 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright(c) 2015-2020 Intel Corporation.
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_type.h>
#include "bus.h"
#include "sysfs_local.h"
struct dpn_attribute {
struct device_attribute dev_attr;
int N;
int dir;
const char *format_string;
};
/*
* Since we can't use ARRAY_SIZE, hard-code number of dpN attributes.
* This needs to be updated when adding new attributes - an error will be
* flagged on a mismatch.
*/
#define SDW_DPN_ATTRIBUTES 15
#define sdw_dpn_attribute_alloc(field) \
static int field##_attribute_alloc(struct device *dev, \
struct attribute **res, \
int N, int dir, \
const char *format_string) \
{ \
struct dpn_attribute *dpn_attr; \
\
dpn_attr = devm_kzalloc(dev, sizeof(*dpn_attr), GFP_KERNEL); \
if (!dpn_attr) \
return -ENOMEM; \
dpn_attr->N = N; \
dpn_attr->dir = dir; \
sysfs_attr_init(&dpn_attr->dev_attr.attr); \
dpn_attr->format_string = format_string; \
dpn_attr->dev_attr.attr.name = __stringify(field); \
dpn_attr->dev_attr.attr.mode = 0444; \
dpn_attr->dev_attr.show = field##_show; \
\
*res = &dpn_attr->dev_attr.attr; \
\
return 0; \
}
#define sdw_dpn_attr(field) \
\
static ssize_t field##_dpn_show(struct sdw_slave *slave, \
int N, \
int dir, \
const char *format_string, \
char *buf) \
{ \
struct sdw_dpn_prop *dpn; \
unsigned long mask; \
int bit; \
int i; \
\
if (dir) { \
dpn = slave->prop.src_dpn_prop; \
mask = slave->prop.source_ports; \
} else { \
dpn = slave->prop.sink_dpn_prop; \
mask = slave->prop.sink_ports; \
} \
\
i = 0; \
for_each_set_bit(bit, &mask, 32) { \
if (bit == N) { \
return sprintf(buf, format_string, \
dpn[i].field); \
} \
i++; \
} \
return -EINVAL; \
} \
\
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
struct dpn_attribute *dpn_attr = \
container_of(attr, struct dpn_attribute, dev_attr); \
\
return field##_dpn_show(slave, \
dpn_attr->N, dpn_attr->dir, \
dpn_attr->format_string, \
buf); \
} \
sdw_dpn_attribute_alloc(field)
sdw_dpn_attr(imp_def_interrupts);
sdw_dpn_attr(max_word);
sdw_dpn_attr(min_word);
sdw_dpn_attr(type);
sdw_dpn_attr(max_grouping);
sdw_dpn_attr(simple_ch_prep_sm);
sdw_dpn_attr(ch_prep_timeout);
sdw_dpn_attr(max_ch);
sdw_dpn_attr(min_ch);
sdw_dpn_attr(max_async_buffer);
sdw_dpn_attr(block_pack_mode);
sdw_dpn_attr(port_encoding);
#define sdw_dpn_array_attr(field) \
\
static ssize_t field##_dpn_show(struct sdw_slave *slave, \
int N, \
int dir, \
const char *format_string, \
char *buf) \
{ \
struct sdw_dpn_prop *dpn; \
unsigned long mask; \
ssize_t size = 0; \
int bit; \
int i; \
int j; \
\
if (dir) { \
dpn = slave->prop.src_dpn_prop; \
mask = slave->prop.source_ports; \
} else { \
dpn = slave->prop.sink_dpn_prop; \
mask = slave->prop.sink_ports; \
} \
\
i = 0; \
for_each_set_bit(bit, &mask, 32) { \
if (bit == N) { \
for (j = 0; j < dpn[i].num_##field; j++) \
size += sprintf(buf + size, \
format_string, \
dpn[i].field[j]); \
size += sprintf(buf + size, "\n"); \
return size; \
} \
i++; \
} \
return -EINVAL; \
} \
static ssize_t field##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct sdw_slave *slave = dev_to_sdw_dev(dev); \
struct dpn_attribute *dpn_attr = \
container_of(attr, struct dpn_attribute, dev_attr); \
\
return field##_dpn_show(slave, \
dpn_attr->N, dpn_attr->dir, \
dpn_attr->format_string, \
buf); \
} \
sdw_dpn_attribute_alloc(field)
sdw_dpn_array_attr(words);
sdw_dpn_array_attr(ch_combinations);
sdw_dpn_array_attr(channels);
static int add_all_attributes(struct device *dev, int N, int dir)
{
struct attribute **dpn_attrs;
struct attribute_group *dpn_group;
int i = 0;
int ret;
/* allocate attributes, last one is NULL */
dpn_attrs = devm_kcalloc(dev, SDW_DPN_ATTRIBUTES + 1,
sizeof(struct attribute *),
GFP_KERNEL);
if (!dpn_attrs)
return -ENOMEM;
ret = max_word_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = min_word_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = words_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = type_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_grouping_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = simple_ch_prep_sm_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = ch_prep_timeout_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = imp_def_interrupts_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "0x%x\n");
if (ret < 0)
return ret;
ret = min_ch_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_ch_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = channels_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = ch_combinations_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = max_async_buffer_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = block_pack_mode_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
ret = port_encoding_attribute_alloc(dev, &dpn_attrs[i++],
N, dir, "%d\n");
if (ret < 0)
return ret;
/* paranoia check for editing mistakes */
if (i != SDW_DPN_ATTRIBUTES) {
dev_err(dev, "mismatch in attributes, allocated %d got %d\n",
SDW_DPN_ATTRIBUTES, i);
return -EINVAL;
}
dpn_group = devm_kzalloc(dev, sizeof(*dpn_group), GFP_KERNEL);
if (!dpn_group)
return -ENOMEM;
dpn_group->attrs = dpn_attrs;
dpn_group->name = devm_kasprintf(dev, GFP_KERNEL, "dp%d_%s",
N, dir ? "src" : "sink");
if (!dpn_group->name)
return -ENOMEM;
ret = devm_device_add_group(dev, dpn_group);
if (ret < 0)
return ret;
return 0;
}
int sdw_slave_sysfs_dpn_init(struct sdw_slave *slave)
{
unsigned long mask;
int ret;
int i;
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);
if (ret < 0)
return ret;
}
mask = slave->prop.sink_ports;
for_each_set_bit(i, &mask, 32) {
ret = add_all_attributes(&slave->dev, i, 0);
if (ret < 0)
return ret;
}
return 0;
}