Skip to content
30 changes: 9 additions & 21 deletions drivers/soundwire/cadence_master.c
Original file line number Diff line number Diff line change
Expand Up @@ -1236,7 +1236,7 @@ EXPORT_SYMBOL(sdw_cdns_enable_interrupt);

static int cdns_allocate_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi **stream,
u32 num, u32 pdi_offset)
u32 num)
{
struct sdw_cdns_pdi *pdi;
int i;
Expand All @@ -1249,7 +1249,7 @@ static int cdns_allocate_pdi(struct sdw_cdns *cdns,
return -ENOMEM;

for (i = 0; i < num; i++) {
pdi[i].num = i + pdi_offset;
pdi[i].num = i;
}

*stream = pdi;
Expand All @@ -1266,7 +1266,6 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
struct sdw_cdns_stream_config config)
{
struct sdw_cdns_streams *stream;
int offset;
int ret;

cdns->pcm.num_bd = config.pcm_bd;
Expand All @@ -1277,24 +1276,15 @@ int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
stream = &cdns->pcm;

/* we allocate PDI0 and PDI1 which are used for Bulk */
offset = 0;

ret = cdns_allocate_pdi(cdns, &stream->bd,
stream->num_bd, offset);
ret = cdns_allocate_pdi(cdns, &stream->bd, stream->num_bd);
if (ret)
return ret;

offset += stream->num_bd;

ret = cdns_allocate_pdi(cdns, &stream->in,
stream->num_in, offset);
ret = cdns_allocate_pdi(cdns, &stream->in, stream->num_in);
if (ret)
return ret;

offset += stream->num_in;

ret = cdns_allocate_pdi(cdns, &stream->out,
stream->num_out, offset);
ret = cdns_allocate_pdi(cdns, &stream->out, stream->num_out);
if (ret)
return ret;

Expand Down Expand Up @@ -1802,7 +1792,6 @@ EXPORT_SYMBOL(cdns_set_sdw_stream);
* cdns_find_pdi() - Find a free PDI
*
* @cdns: Cadence instance
* @offset: Starting offset
* @num: Number of PDIs
* @pdi: PDI instances
* @dai_id: DAI id
Expand All @@ -1811,14 +1800,13 @@ EXPORT_SYMBOL(cdns_set_sdw_stream);
* expected to match, return NULL otherwise.
*/
static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns,
unsigned int offset,
unsigned int num,
struct sdw_cdns_pdi *pdi,
int dai_id)
{
int i;

for (i = offset; i < offset + num; i++)
for (i = 0; i < num; i++)
if (pdi[i].num == dai_id)
return &pdi[i];

Expand Down Expand Up @@ -1872,15 +1860,15 @@ struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi *pdi = NULL;

if (dir == SDW_DATA_DIR_RX)
pdi = cdns_find_pdi(cdns, 0, stream->num_in, stream->in,
pdi = cdns_find_pdi(cdns, stream->num_in, stream->in,
dai_id);
else
pdi = cdns_find_pdi(cdns, 0, stream->num_out, stream->out,
pdi = cdns_find_pdi(cdns, stream->num_out, stream->out,
dai_id);

/* check if we found a PDI, else find in bi-directional */
if (!pdi)
pdi = cdns_find_pdi(cdns, 2, stream->num_bd, stream->bd,
pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd,
dai_id);

if (pdi) {
Expand Down
150 changes: 150 additions & 0 deletions drivers/soundwire/debugfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/firmware.h>
#include <linux/mod_devicetable.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
Expand Down Expand Up @@ -137,6 +138,145 @@ static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
}
DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg);

#define MAX_CMD_BYTES 256

static int cmd;
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", value ? "read" : "write");
cmd = value;

return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(set_command_fops, NULL,
set_command, "%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 cmd_go(void *data, u64 value)
{
struct sdw_slave *slave = data;
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;
}

/* Userspace changed the hardware state behind the kernel's back */
add_taint(TAINT_USER, LOCKDEP_STILL_OK);

dev_dbg(&slave->dev, "starting command\n");

if (cmd == 0) {
const struct firmware *fw;

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: unexpected size %zd, desired %zd\n",
firmware_file, fw->size, num_bytes);
release_firmware(fw);
goto out;
}

ret = sdw_nwrite_no_pm(slave, start_addr, num_bytes, fw->data);
release_firmware(fw);
} else {
ret = sdw_nread_no_pm(slave, start_addr, num_bytes, read_buffer);
}

dev_dbg(&slave->dev, "command completed %d\n", ret);

out:
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;
Expand All @@ -151,6 +291,16 @@ void sdw_slave_debugfs_init(struct sdw_slave *slave)

debugfs_create_file("registers", 0400, d, slave, &sdw_slave_reg_fops);

/* interface to send arbitrary commands */
debugfs_create_file("command", 0200, d, slave, &set_command_fops);
debugfs_create_file("start_address", 0200, d, slave, &set_start_address_fops);
debugfs_create_file("num_bytes", 0200, d, slave, &set_num_bytes_fops);
debugfs_create_file("go", 0200, d, slave, &cmd_go_fops);

debugfs_create_file("read_buffer", 0400, d, slave, &read_buffer_fops);
firmware_file = NULL;
debugfs_create_str("firmware_file", 0200, d, &firmware_file);

slave->debugfs = d;
}

Expand Down
5 changes: 5 additions & 0 deletions drivers/soundwire/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,8 @@ static struct sdw_master_runtime
m_rt->bus = bus;
m_rt->stream = stream;

bus->stream_refcount++;

return m_rt;
}

Expand Down Expand Up @@ -1216,6 +1218,7 @@ static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt, *_s_rt;
struct sdw_bus *bus = m_rt->bus;

list_for_each_entry_safe(s_rt, _s_rt, &m_rt->slave_rt_list, m_rt_node) {
sdw_slave_port_free(s_rt->slave, stream);
Expand All @@ -1225,6 +1228,8 @@ static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
list_del(&m_rt->stream_node);
list_del(&m_rt->bus_node);
kfree(m_rt);

bus->stream_refcount--;
}

/**
Expand Down
21 changes: 5 additions & 16 deletions include/linux/soundwire/sdw.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,8 @@ enum sdw_clk_stop_mode {
* @BRA_flow_controlled: Slave implementation results in an OK_NotReady
* response
* @simple_ch_prep_sm: If channel prepare sequence is required
* @imp_def_interrupts: If set, each bit corresponds to support for
* @ch_prep_timeout: Port-specific timeout value, in milliseconds
* @imp_def_interrupts: If set, each bit corresponds to support for
* implementation-defined interrupts
*
* The wordlengths are specified by Spec as max, min AND number of
Expand All @@ -249,6 +250,7 @@ struct sdw_dp0_prop {
u32 *words;
bool BRA_flow_controlled;
bool simple_ch_prep_sm;
u32 ch_prep_timeout;
bool imp_def_interrupts;
};

Expand Down Expand Up @@ -542,21 +544,6 @@ enum sdw_reg_bank {
SDW_BANK1,
};

/**
* struct sdw_bus_conf: Bus configuration
*
* @clk_freq: Clock frequency, in Hz
* @num_rows: Number of rows in frame
* @num_cols: Number of columns in frame
* @bank: Next register bank
*/
struct sdw_bus_conf {
unsigned int clk_freq;
unsigned int num_rows;
unsigned int num_cols;
unsigned int bank;
};

/**
* struct sdw_prepare_ch: Prepare/De-prepare Data Port channel
*
Expand Down Expand Up @@ -915,6 +902,7 @@ struct sdw_master_ops {
* meaningful if multi_link is set. If set to 1, hardware-based
* synchronization will be used even if a stream only uses a single
* SoundWire segment.
* @stream_refcount: number of streams currently using this bus
*/
struct sdw_bus {
struct device *dev;
Expand Down Expand Up @@ -944,6 +932,7 @@ struct sdw_bus {
u32 bank_switch_timeout;
bool multi_link;
int hw_sync_min_links;
int stream_refcount;
Copy link

@RanderWang RanderWang Dec 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

refcount is native supported by Linux kernel. How about to use it ? It can help to decrease multi-thread risk

refcount_inc, refcount_set, refcount_dec_and_test.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point @RanderWang, I initially planned to use the refcount helpers, but there's a protection:

sdw_master_rt_alloc() and sdw_master_rt_free() need to be called with bus_lock held, it's clearly written in the documentation and it's used this way.

I didn't see the need for additional protection.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a comment in the commit message to make this clear.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, get it

};

int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
Expand Down
2 changes: 1 addition & 1 deletion include/linux/soundwire/sdw_registers.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

#define SDW_REG_NO_PAGE 0x00008000
#define SDW_REG_OPTIONAL_PAGE 0x00010000
#define SDW_REG_MAX 0x80000000
#define SDW_REG_MAX 0x48000000

#define SDW_DPN_SIZE 0x100
#define SDW_BANK1_OFFSET 0x10
Expand Down
2 changes: 1 addition & 1 deletion include/sound/hda-mlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,4 @@ hdac_bus_eml_enable_offload(struct hdac_bus *bus, bool alt, int elid, bool enabl
{
return 0;
}
#endif /* CONFIG_SND_SOC_SOF_HDA */
#endif /* CONFIG_SND_SOC_SOF_HDA_MLINK */
1 change: 1 addition & 0 deletions sound/soc/sof/intel/hda-stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -1080,3 +1080,4 @@ snd_pcm_uframes_t hda_dsp_stream_get_position(struct hdac_stream *hstream,

return pos;
}
EXPORT_SYMBOL_NS(hda_dsp_stream_get_position, SND_SOC_SOF_INTEL_HDA_COMMON);