1
0
Fork 0

soundwire updates for v5.7-rc1

This contains updates to stream and pm handling in the core as well as
 updates to Intel drivers for hw sequencing and multi-link.
 
 Details:
 Core:
   - Updates to stream handling for state machine checks
   - Changes to handle potential races for probe/enumeration and init of the bus
   - Add no pm version of read and writes
   - Support for multiple Slave on same link
   - Add read_only_wordlength for simple/reduced ports
 
 Intel:
   - Updates to cadence lib to handle hw sequencing
   - Support for audio dai calls in intel driver
   - Multi link support for cadence lib
 
 Qualcomm:
   - Support for get_sdw_stream()
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE+vs47OPLdNbVcHzyfBQHDyUjg0cFAl54ppEACgkQfBQHDyUj
 g0edww/+Pm5Z5OeJgzf6Ekx6wIMqyOvJoEaYreloKck9Cbr0TEKHkLASKcMqWdUj
 jl+gPyZcT7piDGFuj5HP4Ld/6PLmfgiNTUNbLXTftLrJZa2NrjYp3RuKon2Zug+z
 2Y6fiV1nOTtp5oqGunsEPP4LxEDVEsj3pGa2TumCkgd0MrAPDKApgp/icrQ2f1xl
 UmXicDkLRvIPV29VaCsaIki6+Te9JjA7r5TRpSEK7NSzdiq2/+lu1cHypn3Py38a
 eaLKGZxN/hnSDIK/7PHSCmzbd2e0MDSGeRrFFeLQ5J3rUwz/Mg1UtEG+KRq+YusN
 qpkoKwnbDpZ+2TSpJvd33xmO7saJTI4/tbo8WVxtZBnSSs2Im4jlLR5rtX5OczLw
 OE2XYWOFtVu0vtgjbLsTT6Y/AlJSQ7h4mR3DfapZQ01hPGRIp9UIA+A0Sum9hX4e
 R1V9yPp41QX1TbaOIgN6IkyKt/3DYOKJ3LhsD25pPo0Dhqwdyvnl2o7yCMGfa5+1
 ISIBJ5MAF3dAFRuDXs6H2oqWX5ZiSUflxO54wMYDpj7pQImXGmgIJVMpd8D3yZ81
 jmd3btgo/uM746s9UwayA7+oGbrsFAVxqp+YS8IkDOZlqa+Qfy6/+KYRkS/vccBg
 L+/QLDcPW+E5Qeul1VNpLCxv/EYybTI59Tt8+UwkY/udYVN1kew=
 =UPKg
 -----END PGP SIGNATURE-----

Merge tag 'soundwire-5.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire into char-misc-next

Vinod writes:

soundwire updates for v5.7-rc1

This contains updates to stream and pm handling in the core as well as
updates to Intel drivers for hw sequencing and multi-link.

Details:
Core:
  - Updates to stream handling for state machine checks
  - Changes to handle potential races for probe/enumeration and init of the bus
  - Add no pm version of read and writes
  - Support for multiple Slave on same link
  - Add read_only_wordlength for simple/reduced ports

Intel:
  - Updates to cadence lib to handle hw sequencing
  - Support for audio dai calls in intel driver
  - Multi link support for cadence lib

Qualcomm:
  - Support for get_sdw_stream()

* tag 'soundwire-5.7-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (43 commits)
  soundwire: qcom: add support for get_sdw_stream()
  soundwire: stream: Add read_only_wordlength flag to port properties
  soundwire: cadence: clear FIFO to avoid pop noise issue on playback start
  soundwire: cadence: multi-link support
  soundwire: cadence: commit changes in the exit_reset() sequence
  soundwire: cadence: remove automatic command retries
  soundwire: cadence: remove PREQ_DELAY assignment
  soundwire: cadence: enable NORMAL operation in cdns_init()
  soundwire: cadence: reorder MCP_CONFIG settings
  soundwire: cadence: make SSP interval programmable
  soundwire: cadence: move clock/SSP related inits to dedicated function
  soundwire: cadence: merge routines to clear/set bits
  soundwire: cadence: mask Slave interrupt before stopping clock
  soundwire: cadence: fix a io timeout issue in S3 test
  soundwire: cadence: add clock_stop/restart routines
  soundwire: cadence: handle error cases with CONFIG_UPDATE
  soundwire: cadence: add interface to check clock status
  soundwire: cadence: simplifiy cdns_init()
  soundwire: cadence: s/update_config/config_update
  soundwire: stream: use sdw_write instead of update
  ...
5.9.x+fslc
Greg Kroah-Hartman 2020-03-23 13:19:00 +01:00
commit 33e12f6e45
11 changed files with 1134 additions and 161 deletions

View File

@ -156,22 +156,27 @@ Below shows the SoundWire stream states and state transition diagram. ::
+-----------+ +------------+ +----------+ +----------+
| ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED |
| STATE | | STATE | | STATE | | STATE |
+-----------+ +------------+ +----------+ +----+-----+
^
|
|
v
+----------+ +------------+ +----+-----+
+-----------+ +------------+ +---+--+---+ +----+-----+
^ ^ ^
| | |
__| |___________ |
| | |
v | v
+----------+ +-----+------+ +-+--+-----+
| RELEASED |<----------+ DEPREPARED |<-------+ DISABLED |
| STATE | | STATE | | STATE |
+----------+ +------------+ +----------+
NOTE: State transition between prepare and deprepare is supported in Spec
but not in the software (subsystem)
NOTE: State transitions between ``SDW_STREAM_ENABLED`` and
``SDW_STREAM_DISABLED`` are only relevant when then INFO_PAUSE flag is
supported at the ALSA/ASoC level. Likewise the transition between
``SDW_DISABLED_STATE`` and ``SDW_PREPARED_STATE`` depends on the
INFO_RESUME flag.
NOTE2: Stream state transition checks need to be handled by caller
framework, for example ALSA/ASoC. No checks for stream transition exist in
SoundWire subsystem.
NOTE2: The framework implements basic state transition checks, but
does not e.g. check if a transition from DISABLED to ENABLED is valid
on a specific platform. Such tests need to be added at the ALSA/ASoC
level.
Stream State Operations
-----------------------
@ -246,6 +251,9 @@ SDW_STREAM_PREPARED
Prepare state of stream. Operations performed before entering in this state:
(0) Steps 1 and 2 are omitted in the case of a resume operation,
where the bus bandwidth is known.
(1) Bus parameters such as bandwidth, frame shape, clock frequency,
are computed based on current stream as well as already active
stream(s) on Bus. Re-computation is required to accommodate current
@ -270,9 +278,11 @@ Prepare state of stream. Operations performed before entering in this state:
After all above operations are successful, stream state is set to
``SDW_STREAM_PREPARED``.
Bus implements below API for PREPARE state which needs to be called once per
stream. From ASoC DPCM framework, this stream state is linked to
.prepare() operation.
Bus implements below API for PREPARE state which needs to be called
once per stream. From ASoC DPCM framework, this stream state is linked
to .prepare() operation. Since the .trigger() operations may not
follow the .prepare(), a direct transition from
``SDW_STREAM_PREPARED`` to ``SDW_STREAM_DEPREPARED`` is allowed.
.. code-block:: c
@ -332,6 +342,14 @@ Bus implements below API for DISABLED state which needs to be called once
per stream. From ASoC DPCM framework, this stream state is linked to
.trigger() stop operation.
When the INFO_PAUSE flag is supported, a direct transition to
``SDW_STREAM_ENABLED`` is allowed.
For resume operations where ASoC will use the .prepare() callback, the
stream can transition from ``SDW_STREAM_DISABLED`` to
``SDW_STREAM_PREPARED``, with all required settings restored but
without updating the bandwidth and bit allocation.
.. code-block:: c
int sdw_disable_stream(struct sdw_stream_runtime * stream);
@ -353,9 +371,18 @@ state:
After all above operations are successful, stream state is set to
``SDW_STREAM_DEPREPARED``.
Bus implements below API for DEPREPARED state which needs to be called once
per stream. From ASoC DPCM framework, this stream state is linked to
.trigger() stop operation.
Bus implements below API for DEPREPARED state which needs to be called
once per stream. ALSA/ASoC do not have a concept of 'deprepare', and
the mapping from this stream state to ALSA/ASoC operation may be
implementation specific.
When the INFO_PAUSE flag is supported, the stream state is linked to
the .hw_free() operation - the stream is not deprepared on a
TRIGGER_STOP.
Other implementations may transition to the ``SDW_STREAM_DEPREPARED``
state on TRIGGER_STOP, should they require a transition through the
``SDW_STREAM_PREPARED`` state.
.. code-block:: c

View File

@ -2,6 +2,7 @@
// Copyright(c) 2015-17 Intel Corporation.
#include <linux/acpi.h>
#include <linux/delay.h>
#include <linux/mod_devicetable.h>
#include <linux/pm_runtime.h>
#include <linux/soundwire/sdw_registers.h>
@ -113,6 +114,8 @@ static int sdw_delete_slave(struct device *dev, void *data)
struct sdw_slave *slave = dev_to_sdw_dev(dev);
struct sdw_bus *bus = slave->bus;
pm_runtime_disable(dev);
sdw_slave_debugfs_exit(slave);
mutex_lock(&bus->bus_lock);
@ -317,14 +320,15 @@ int sdw_fill_msg(struct sdw_msg *msg, struct sdw_slave *slave,
return 0;
}
/**
* sdw_nread() - Read "n" contiguous SDW Slave registers
* @slave: SDW Slave
* @addr: Register address
* @count: length
* @val: Buffer for values to be read
/*
* Read/Write IO functions.
* no_pm versions can only be called by the bus, e.g. while enumerating or
* handling suspend-resume sequences.
* all clients need to use the pm versions
*/
int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
static int
sdw_nread_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
{
struct sdw_msg msg;
int ret;
@ -334,11 +338,94 @@ int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
if (ret < 0)
return ret;
ret = pm_runtime_get_sync(slave->bus->dev);
return sdw_transfer(slave->bus, &msg);
}
static int
sdw_nwrite_no_pm(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
{
struct sdw_msg msg;
int ret;
ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_WRITE, val);
if (ret < 0)
return ret;
ret = sdw_transfer(slave->bus, &msg);
return sdw_transfer(slave->bus, &msg);
}
static int sdw_write_no_pm(struct sdw_slave *slave, u32 addr, u8 value)
{
return sdw_nwrite_no_pm(slave, addr, 1, &value);
}
static int
sdw_bread_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr)
{
struct sdw_msg msg;
u8 buf;
int ret;
ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num,
SDW_MSG_FLAG_READ, &buf);
if (ret)
return ret;
ret = sdw_transfer(bus, &msg);
if (ret < 0)
return ret;
else
return buf;
}
static int
sdw_bwrite_no_pm(struct sdw_bus *bus, u16 dev_num, u32 addr, u8 value)
{
struct sdw_msg msg;
int ret;
ret = sdw_fill_msg(&msg, NULL, addr, 1, dev_num,
SDW_MSG_FLAG_WRITE, &value);
if (ret)
return ret;
return sdw_transfer(bus, &msg);
}
static int
sdw_read_no_pm(struct sdw_slave *slave, u32 addr)
{
u8 buf;
int ret;
ret = sdw_nread_no_pm(slave, addr, 1, &buf);
if (ret < 0)
return ret;
else
return buf;
}
/**
* sdw_nread() - Read "n" contiguous SDW Slave registers
* @slave: SDW Slave
* @addr: Register address
* @count: length
* @val: Buffer for values to be read
*/
int sdw_nread(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
{
int ret;
ret = pm_runtime_get_sync(slave->bus->dev);
if (ret < 0 && ret != -EACCES) {
pm_runtime_put_noidle(slave->bus->dev);
return ret;
}
ret = sdw_nread_no_pm(slave, addr, count, val);
pm_runtime_mark_last_busy(slave->bus->dev);
pm_runtime_put(slave->bus->dev);
return ret;
@ -354,19 +441,17 @@ EXPORT_SYMBOL(sdw_nread);
*/
int sdw_nwrite(struct sdw_slave *slave, u32 addr, size_t count, u8 *val)
{
struct sdw_msg msg;
int ret;
ret = sdw_fill_msg(&msg, slave, addr, count,
slave->dev_num, SDW_MSG_FLAG_WRITE, val);
if (ret < 0)
return ret;
ret = pm_runtime_get_sync(slave->bus->dev);
if (ret < 0)
if (ret < 0 && ret != -EACCES) {
pm_runtime_put_noidle(slave->bus->dev);
return ret;
}
ret = sdw_transfer(slave->bus, &msg);
ret = sdw_nwrite_no_pm(slave, addr, count, val);
pm_runtime_mark_last_busy(slave->bus->dev);
pm_runtime_put(slave->bus->dev);
return ret;
@ -486,7 +571,7 @@ static int sdw_assign_device_num(struct sdw_slave *slave)
dev_num = slave->dev_num;
slave->dev_num = 0;
ret = sdw_write(slave, SDW_SCP_DEVNUMBER, dev_num);
ret = sdw_write_no_pm(slave, SDW_SCP_DEVNUMBER, dev_num);
if (ret < 0) {
dev_err(&slave->dev, "Program device_num %d failed: %d\n",
dev_num, ret);
@ -504,22 +589,11 @@ void sdw_extract_slave_id(struct sdw_bus *bus,
{
dev_dbg(bus->dev, "SDW Slave Addr: %llx\n", addr);
/*
* Spec definition
* Register Bit Contents
* DevId_0 [7:4] 47:44 sdw_version
* DevId_0 [3:0] 43:40 unique_id
* DevId_1 39:32 mfg_id [15:8]
* DevId_2 31:24 mfg_id [7:0]
* DevId_3 23:16 part_id [15:8]
* DevId_4 15:08 part_id [7:0]
* DevId_5 07:00 class_id
*/
id->sdw_version = (addr >> 44) & GENMASK(3, 0);
id->unique_id = (addr >> 40) & GENMASK(3, 0);
id->mfg_id = (addr >> 24) & GENMASK(15, 0);
id->part_id = (addr >> 8) & GENMASK(15, 0);
id->class_id = addr & GENMASK(7, 0);
id->sdw_version = SDW_VERSION(addr);
id->unique_id = SDW_UNIQUE_ID(addr);
id->mfg_id = SDW_MFG_ID(addr);
id->part_id = SDW_PART_ID(addr);
id->class_id = SDW_CLASS_ID(addr);
dev_dbg(bus->dev,
"SDW Slave class_id %x, part_id %x, mfg_id %x, unique_id %x, version %x\n",
@ -610,10 +684,320 @@ static void sdw_modify_slave_status(struct sdw_slave *slave,
enum sdw_slave_status status)
{
mutex_lock(&slave->bus->bus_lock);
dev_vdbg(&slave->dev,
"%s: changing status slave %d status %d new status %d\n",
__func__, slave->dev_num, slave->status, status);
if (status == SDW_SLAVE_UNATTACHED) {
dev_dbg(&slave->dev,
"%s: initializing completion for Slave %d\n",
__func__, slave->dev_num);
init_completion(&slave->enumeration_complete);
init_completion(&slave->initialization_complete);
} else if ((status == SDW_SLAVE_ATTACHED) &&
(slave->status == SDW_SLAVE_UNATTACHED)) {
dev_dbg(&slave->dev,
"%s: signaling completion for Slave %d\n",
__func__, slave->dev_num);
complete(&slave->enumeration_complete);
}
slave->status = status;
mutex_unlock(&slave->bus->bus_lock);
}
static enum sdw_clk_stop_mode sdw_get_clk_stop_mode(struct sdw_slave *slave)
{
enum sdw_clk_stop_mode mode;
/*
* Query for clock stop mode if Slave implements
* ops->get_clk_stop_mode, else read from property.
*/
if (slave->ops && slave->ops->get_clk_stop_mode) {
mode = slave->ops->get_clk_stop_mode(slave);
} else {
if (slave->prop.clk_stop_mode1)
mode = SDW_CLK_STOP_MODE1;
else
mode = SDW_CLK_STOP_MODE0;
}
return mode;
}
static int sdw_slave_clk_stop_callback(struct sdw_slave *slave,
enum sdw_clk_stop_mode mode,
enum sdw_clk_stop_type type)
{
int ret;
if (slave->ops && slave->ops->clk_stop) {
ret = slave->ops->clk_stop(slave, mode, type);
if (ret < 0) {
dev_err(&slave->dev,
"Clk Stop type =%d failed: %d\n", type, ret);
return ret;
}
}
return 0;
}
static int sdw_slave_clk_stop_prepare(struct sdw_slave *slave,
enum sdw_clk_stop_mode mode,
bool prepare)
{
bool wake_en;
u32 val = 0;
int ret;
wake_en = slave->prop.wake_capable;
if (prepare) {
val = SDW_SCP_SYSTEMCTRL_CLK_STP_PREP;
if (mode == SDW_CLK_STOP_MODE1)
val |= SDW_SCP_SYSTEMCTRL_CLK_STP_MODE1;
if (wake_en)
val |= SDW_SCP_SYSTEMCTRL_WAKE_UP_EN;
} else {
val = sdw_read_no_pm(slave, SDW_SCP_SYSTEMCTRL);
val &= ~(SDW_SCP_SYSTEMCTRL_CLK_STP_PREP);
}
ret = sdw_write_no_pm(slave, SDW_SCP_SYSTEMCTRL, val);
if (ret != 0)
dev_err(&slave->dev,
"Clock Stop prepare failed for slave: %d", ret);
return ret;
}
static int sdw_bus_wait_for_clk_prep_deprep(struct sdw_bus *bus, u16 dev_num)
{
int retry = bus->clk_stop_timeout;
int val;
do {
val = sdw_bread_no_pm(bus, dev_num, SDW_SCP_STAT) &
SDW_SCP_STAT_CLK_STP_NF;
if (!val) {
dev_info(bus->dev, "clock stop prep/de-prep done slave:%d",
dev_num);
return 0;
}
usleep_range(1000, 1500);
retry--;
} while (retry);
dev_err(bus->dev, "clock stop prep/de-prep failed slave:%d",
dev_num);
return -ETIMEDOUT;
}
/**
* sdw_bus_prep_clk_stop: prepare Slave(s) for clock stop
*
* @bus: SDW bus instance
*
* Query Slave for clock stop mode and prepare for that mode.
*/
int sdw_bus_prep_clk_stop(struct sdw_bus *bus)
{
enum sdw_clk_stop_mode slave_mode;
bool simple_clk_stop = true;
struct sdw_slave *slave;
bool is_slave = false;
int ret = 0;
/*
* In order to save on transition time, prepare
* each Slave and then wait for all Slave(s) to be
* prepared for clock stop.
*/
list_for_each_entry(slave, &bus->slaves, node) {
if (!slave->dev_num)
continue;
/* Identify if Slave(s) are available on Bus */
is_slave = true;
if (slave->status != SDW_SLAVE_ATTACHED &&
slave->status != SDW_SLAVE_ALERT)
continue;
slave_mode = sdw_get_clk_stop_mode(slave);
slave->curr_clk_stop_mode = slave_mode;
ret = sdw_slave_clk_stop_callback(slave, slave_mode,
SDW_CLK_PRE_PREPARE);
if (ret < 0) {
dev_err(&slave->dev,
"pre-prepare failed:%d", ret);
return ret;
}
ret = sdw_slave_clk_stop_prepare(slave,
slave_mode, true);
if (ret < 0) {
dev_err(&slave->dev,
"pre-prepare failed:%d", ret);
return ret;
}
if (slave_mode == SDW_CLK_STOP_MODE1)
simple_clk_stop = false;
}
if (is_slave && !simple_clk_stop) {
ret = sdw_bus_wait_for_clk_prep_deprep(bus,
SDW_BROADCAST_DEV_NUM);
if (ret < 0)
return ret;
}
/* Inform slaves that prep is done */
list_for_each_entry(slave, &bus->slaves, node) {
if (!slave->dev_num)
continue;
if (slave->status != SDW_SLAVE_ATTACHED &&
slave->status != SDW_SLAVE_ALERT)
continue;
slave_mode = slave->curr_clk_stop_mode;
if (slave_mode == SDW_CLK_STOP_MODE1) {
ret = sdw_slave_clk_stop_callback(slave,
slave_mode,
SDW_CLK_POST_PREPARE);
if (ret < 0) {
dev_err(&slave->dev,
"post-prepare failed:%d", ret);
}
}
}
return ret;
}
EXPORT_SYMBOL(sdw_bus_prep_clk_stop);
/**
* sdw_bus_clk_stop: stop bus clock
*
* @bus: SDW bus instance
*
* After preparing the Slaves for clock stop, stop the clock by broadcasting
* write to SCP_CTRL register.
*/
int sdw_bus_clk_stop(struct sdw_bus *bus)
{
int ret;
/*
* broadcast clock stop now, attached Slaves will ACK this,
* unattached will ignore
*/
ret = sdw_bwrite_no_pm(bus, SDW_BROADCAST_DEV_NUM,
SDW_SCP_CTRL, SDW_SCP_CTRL_CLK_STP_NOW);
if (ret < 0) {
if (ret == -ENODATA)
dev_dbg(bus->dev,
"ClockStopNow Broadcast msg ignored %d", ret);
else
dev_err(bus->dev,
"ClockStopNow Broadcast msg failed %d", ret);
return ret;
}
return 0;
}
EXPORT_SYMBOL(sdw_bus_clk_stop);
/**
* sdw_bus_exit_clk_stop: Exit clock stop mode
*
* @bus: SDW bus instance
*
* This De-prepares the Slaves by exiting Clock Stop Mode 0. For the Slaves
* exiting Clock Stop Mode 1, they will be de-prepared after they enumerate
* back.
*/
int sdw_bus_exit_clk_stop(struct sdw_bus *bus)
{
enum sdw_clk_stop_mode mode;
bool simple_clk_stop = true;
struct sdw_slave *slave;
bool is_slave = false;
int ret;
/*
* In order to save on transition time, de-prepare
* each Slave and then wait for all Slave(s) to be
* de-prepared after clock resume.
*/
list_for_each_entry(slave, &bus->slaves, node) {
if (!slave->dev_num)
continue;
/* Identify if Slave(s) are available on Bus */
is_slave = true;
if (slave->status != SDW_SLAVE_ATTACHED &&
slave->status != SDW_SLAVE_ALERT)
continue;
mode = slave->curr_clk_stop_mode;
if (mode == SDW_CLK_STOP_MODE1) {
simple_clk_stop = false;
continue;
}
ret = sdw_slave_clk_stop_callback(slave, mode,
SDW_CLK_PRE_DEPREPARE);
if (ret < 0)
dev_warn(&slave->dev,
"clk stop deprep failed:%d", ret);
ret = sdw_slave_clk_stop_prepare(slave, mode,
false);
if (ret < 0)
dev_warn(&slave->dev,
"clk stop deprep failed:%d", ret);
}
if (is_slave && !simple_clk_stop)
sdw_bus_wait_for_clk_prep_deprep(bus, SDW_BROADCAST_DEV_NUM);
list_for_each_entry(slave, &bus->slaves, node) {
if (!slave->dev_num)
continue;
if (slave->status != SDW_SLAVE_ATTACHED &&
slave->status != SDW_SLAVE_ALERT)
continue;
mode = slave->curr_clk_stop_mode;
sdw_slave_clk_stop_callback(slave, mode,
SDW_CLK_POST_DEPREPARE);
}
return 0;
}
EXPORT_SYMBOL(sdw_bus_exit_clk_stop);
int sdw_configure_dpn_intr(struct sdw_slave *slave,
int port, bool enable, int mask)
{
@ -672,13 +1056,10 @@ static int sdw_initialize_slave(struct sdw_slave *slave)
val |= SDW_DP0_INT_PORT_READY | SDW_DP0_INT_BRA_FAILURE;
ret = sdw_update(slave, SDW_DP0_INTMASK, val, val);
if (ret < 0) {
if (ret < 0)
dev_err(slave->bus->dev,
"SDW_DP0_INTMASK read failed:%d\n", ret);
return val;
}
return 0;
return ret;
}
static int sdw_handle_dp0_interrupt(struct sdw_slave *slave, u8 *slave_status)
@ -831,12 +1212,19 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
sdw_modify_slave_status(slave, SDW_SLAVE_ALERT);
ret = pm_runtime_get_sync(&slave->dev);
if (ret < 0 && ret != -EACCES) {
dev_err(&slave->dev, "Failed to resume device: %d\n", ret);
pm_runtime_put_noidle(slave->bus->dev);
return ret;
}
/* Read Instat 1, Instat 2 and Instat 3 registers */
ret = sdw_read(slave, SDW_SCP_INT1);
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 read failed:%d\n", ret);
return ret;
goto io_err;
}
buf = ret;
@ -844,7 +1232,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT2/3 read failed:%d\n", ret);
return ret;
goto io_err;
}
do {
@ -924,7 +1312,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 write failed:%d\n", ret);
return ret;
goto io_err;
}
/*
@ -935,7 +1323,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT1 read failed:%d\n", ret);
return ret;
goto io_err;
}
_buf = ret;
@ -943,7 +1331,7 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
if (ret < 0) {
dev_err(slave->bus->dev,
"SDW_SCP_INT2/3 read failed:%d\n", ret);
return ret;
goto io_err;
}
/* Make sure no interrupts are pending */
@ -964,16 +1352,39 @@ static int sdw_handle_slave_alerts(struct sdw_slave *slave)
if (count == SDW_READ_INTR_CLEAR_RETRY)
dev_warn(slave->bus->dev, "Reached MAX_RETRY on alert read\n");
io_err:
pm_runtime_mark_last_busy(&slave->dev);
pm_runtime_put_autosuspend(&slave->dev);
return ret;
}
static int sdw_update_slave_status(struct sdw_slave *slave,
enum sdw_slave_status status)
{
if (slave->ops && slave->ops->update_status)
return slave->ops->update_status(slave, status);
unsigned long time;
return 0;
if (!slave->probed) {
/*
* the slave status update is typically handled in an
* interrupt thread, which can race with the driver
* probe, e.g. when a module needs to be loaded.
*
* make sure the probe is complete before updating
* status.
*/
time = wait_for_completion_timeout(&slave->probe_complete,
msecs_to_jiffies(DEFAULT_PROBE_TIMEOUT));
if (!time) {
dev_err(&slave->dev, "Probe not complete, timed out\n");
return -ETIMEDOUT;
}
}
if (!slave->ops || !slave->ops->update_status)
return 0;
return slave->ops->update_status(slave, status);
}
/**
@ -986,6 +1397,7 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
{
enum sdw_slave_status prev_status;
struct sdw_slave *slave;
bool attached_initializing;
int i, ret = 0;
/* first check if any Slaves fell off the bus */
@ -1031,6 +1443,8 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
if (!slave)
continue;
attached_initializing = false;
switch (status[i]) {
case SDW_SLAVE_UNATTACHED:
if (slave->status == SDW_SLAVE_UNATTACHED)
@ -1057,6 +1471,8 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
if (prev_status == SDW_SLAVE_ALERT)
break;
attached_initializing = true;
ret = sdw_initialize_slave(slave);
if (ret)
dev_err(bus->dev,
@ -1075,8 +1491,37 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
if (ret)
dev_err(slave->bus->dev,
"Update Slave status failed:%d\n", ret);
if (attached_initializing)
complete(&slave->initialization_complete);
}
return ret;
}
EXPORT_SYMBOL(sdw_handle_slave_status);
void sdw_clear_slave_status(struct sdw_bus *bus, u32 request)
{
struct sdw_slave *slave;
int i;
/* Check all non-zero devices */
for (i = 1; i <= SDW_MAX_DEVICES; i++) {
mutex_lock(&bus->bus_lock);
if (test_bit(i, bus->assigned) == false) {
mutex_unlock(&bus->bus_lock);
continue;
}
mutex_unlock(&bus->bus_lock);
slave = sdw_get_slave(bus, i);
if (!slave)
continue;
if (slave->status != SDW_SLAVE_UNATTACHED)
sdw_modify_slave_status(slave, SDW_SLAVE_UNATTACHED);
/* keep track of request, used in pm_runtime resume */
slave->unattach_request = request;
}
}
EXPORT_SYMBOL(sdw_clear_slave_status);

View File

@ -5,6 +5,7 @@
#define __SDW_BUS_H
#define DEFAULT_BANK_SWITCH_TIMEOUT 3000
#define DEFAULT_PROBE_TIMEOUT 2000
#if IS_ENABLED(CONFIG_ACPI)
int sdw_acpi_find_slaves(struct sdw_bus *bus);
@ -164,4 +165,12 @@ sdw_update(struct sdw_slave *slave, u32 addr, u8 mask, u8 val)
return sdw_write(slave, addr, tmp);
}
/*
* 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);
#endif /* __SDW_BUS_H */

View File

@ -110,6 +110,11 @@ static int sdw_drv_probe(struct device *dev)
slave->bus->clk_stop_timeout = max_t(u32, slave->bus->clk_stop_timeout,
slave->prop.clk_stop_timeout);
slave->probed = true;
complete(&slave->probe_complete);
dev_dbg(dev, "probe complete\n");
return 0;
}

View File

@ -183,7 +183,6 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
#define CDNS_PDI_CONFIG_PORT GENMASK(4, 0)
/* Driver defaults */
#define CDNS_DEFAULT_SSP_INTERVAL 0x18
#define CDNS_TX_TIMEOUT 2000
#define CDNS_SCP_RX_FIFOLEVEL 0x2
@ -211,34 +210,45 @@ static inline void cdns_updatel(struct sdw_cdns *cdns,
cdns_writel(cdns, offset, tmp);
}
static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
static int cdns_set_wait(struct sdw_cdns *cdns, int offset, u32 mask, u32 value)
{
int timeout = 10;
u32 reg_read;
writel(value, cdns->registers + offset);
/* Wait for bit to be self cleared */
/* Wait for bit to be set */
do {
reg_read = readl(cdns->registers + offset);
if ((reg_read & value) == 0)
if ((reg_read & mask) == value)
return 0;
timeout--;
udelay(50);
usleep_range(50, 100);
} while (timeout != 0);
return -EAGAIN;
return -ETIMEDOUT;
}
static int cdns_clear_bit(struct sdw_cdns *cdns, int offset, u32 value)
{
writel(value, cdns->registers + offset);
/* Wait for bit to be self cleared */
return cdns_set_wait(cdns, offset, value, 0);
}
/*
* all changes to the MCP_CONFIG, MCP_CONTROL, MCP_CMDCTRL and MCP_PHYCTRL
* need to be confirmed with a write to MCP_CONFIG_UPDATE
*/
static int cdns_update_config(struct sdw_cdns *cdns)
static int cdns_config_update(struct sdw_cdns *cdns)
{
int ret;
if (sdw_cdns_is_clock_stop(cdns)) {
dev_err(cdns->dev, "Cannot program MCP_CONFIG_UPDATE in ClockStopMode\n");
return -EINVAL;
}
ret = cdns_clear_bit(cdns, CDNS_MCP_CONFIG_UPDATE,
CDNS_MCP_CONFIG_UPDATE_BIT);
if (ret < 0)
@ -832,16 +842,35 @@ int sdw_cdns_exit_reset(struct sdw_cdns *cdns)
CDNS_MCP_CONTROL_HW_RST,
CDNS_MCP_CONTROL_HW_RST);
/* enable bus operations with clock and data */
cdns_updatel(cdns, CDNS_MCP_CONFIG,
CDNS_MCP_CONFIG_OP,
CDNS_MCP_CONFIG_OP_NORMAL);
/* commit changes */
return cdns_update_config(cdns);
cdns_updatel(cdns, CDNS_MCP_CONFIG_UPDATE,
CDNS_MCP_CONFIG_UPDATE_BIT,
CDNS_MCP_CONFIG_UPDATE_BIT);
/* don't wait here */
return 0;
}
EXPORT_SYMBOL(sdw_cdns_exit_reset);
/**
* sdw_cdns_enable_slave_interrupt() - Enable SDW slave interrupts
* @cdns: Cadence instance
* @state: boolean for true/false
*/
static void cdns_enable_slave_interrupts(struct sdw_cdns *cdns, bool state)
{
u32 mask;
mask = cdns_readl(cdns, CDNS_MCP_INTMASK);
if (state)
mask |= CDNS_MCP_INT_SLAVE_MASK;
else
mask &= ~CDNS_MCP_INT_SLAVE_MASK;
cdns_writel(cdns, CDNS_MCP_INTMASK, mask);
}
/**
* sdw_cdns_enable_interrupt() - Enable SDW interrupts
* @cdns: Cadence instance
@ -1014,26 +1043,13 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
return val;
}
/**
* sdw_cdns_init() - Cadence initialization
* @cdns: Cadence instance
*/
int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
{
struct sdw_bus *bus = &cdns->bus;
struct sdw_master_prop *prop = &bus->prop;
u32 val;
u32 ssp_interval;
int divider;
int ret;
if (clock_stop_exit) {
ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_CLK_STOP_CLR);
if (ret < 0) {
dev_err(cdns->dev, "Couldn't exit from clock stop\n");
return ret;
}
}
/* Set clock divider */
divider = (prop->mclk_freq / prop->max_clk_freq) - 1;
@ -1052,8 +1068,23 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
cdns_writel(cdns, CDNS_MCP_FRAME_SHAPE_INIT, val);
/* Set SSP interval to default value */
cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, CDNS_DEFAULT_SSP_INTERVAL);
cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, CDNS_DEFAULT_SSP_INTERVAL);
ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ;
cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval);
cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval);
}
/**
* sdw_cdns_init() - Cadence initialization
* @cdns: Cadence instance
*/
int sdw_cdns_init(struct sdw_cdns *cdns)
{
u32 val;
cdns_init_clock_ctrl(cdns);
/* reset msg_count to default value of FIFOLEVEL */
cdns->msg_count = cdns_readl(cdns, CDNS_MCP_FIFOLEVEL);
/* flush command FIFOs */
cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_RST,
@ -1066,25 +1097,31 @@ int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit)
/* Configure mcp config */
val = cdns_readl(cdns, CDNS_MCP_CONFIG);
/* Set Max cmd retry to 15 */
val |= CDNS_MCP_CONFIG_MCMD_RETRY;
/* Set frame delay between PREQ and ping frame to 15 frames */
val |= 0xF << SDW_REG_SHIFT(CDNS_MCP_CONFIG_MPREQ_DELAY);
/* Disable auto bus release */
val &= ~CDNS_MCP_CONFIG_BUS_REL;
/* Disable sniffer mode */
val &= ~CDNS_MCP_CONFIG_SNIFFER;
/* enable bus operations with clock and data */
val &= ~CDNS_MCP_CONFIG_OP;
val |= CDNS_MCP_CONFIG_OP_NORMAL;
/* Set cmd mode for Tx and Rx cmds */
val &= ~CDNS_MCP_CONFIG_CMD;
/* Disable sniffer mode */
val &= ~CDNS_MCP_CONFIG_SNIFFER;
/* Disable auto bus release */
val &= ~CDNS_MCP_CONFIG_BUS_REL;
if (cdns->bus.multi_link)
/* Set Multi-master mode to take gsync into account */
val |= CDNS_MCP_CONFIG_MMASTER;
/* leave frame delay to hardware default of 0x1F */
/* leave command retry to hardware default of 0 */
cdns_writel(cdns, CDNS_MCP_CONFIG, val);
/* commit changes */
return cdns_update_config(cdns);
/* changes will be committed later */
return 0;
}
EXPORT_SYMBOL(sdw_cdns_init);
@ -1217,6 +1254,166 @@ static const struct sdw_master_port_ops cdns_port_ops = {
.dpn_port_enable_ch = cdns_port_enable,
};
/**
* sdw_cdns_is_clock_stop: Check clock status
*
* @cdns: Cadence instance
*/
bool sdw_cdns_is_clock_stop(struct sdw_cdns *cdns)
{
return !!(cdns_readl(cdns, CDNS_MCP_STAT) & CDNS_MCP_STAT_CLK_STOP);
}
EXPORT_SYMBOL(sdw_cdns_is_clock_stop);
/**
* sdw_cdns_clock_stop: Cadence clock stop configuration routine
*
* @cdns: Cadence instance
* @block_wake: prevent wakes if required by the platform
*/
int sdw_cdns_clock_stop(struct sdw_cdns *cdns, bool block_wake)
{
bool slave_present = false;
struct sdw_slave *slave;
int ret;
/* Check suspend status */
if (sdw_cdns_is_clock_stop(cdns)) {
dev_dbg(cdns->dev, "Clock is already stopped\n");
return 0;
}
/*
* Before entering clock stop we mask the Slave
* interrupts. This helps avoid having to deal with e.g. a
* Slave becoming UNATTACHED while the clock is being stopped
*/
cdns_enable_slave_interrupts(cdns, false);
/*
* For specific platforms, it is required to be able to put
* master into a state in which it ignores wake-up trials
* in clock stop state
*/
if (block_wake)
cdns_updatel(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_BLOCK_WAKEUP,
CDNS_MCP_CONTROL_BLOCK_WAKEUP);
list_for_each_entry(slave, &cdns->bus.slaves, node) {
if (slave->status == SDW_SLAVE_ATTACHED ||
slave->status == SDW_SLAVE_ALERT) {
slave_present = true;
break;
}
}
/*
* This CMD_ACCEPT should be used when there are no devices
* attached on the link when entering clock stop mode. If this is
* not set and there is a broadcast write then the command ignored
* will be treated as a failure
*/
if (!slave_present)
cdns_updatel(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_CMD_ACCEPT,
CDNS_MCP_CONTROL_CMD_ACCEPT);
else
cdns_updatel(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_CMD_ACCEPT, 0);
/* commit changes */
ret = cdns_config_update(cdns);
if (ret < 0) {
dev_err(cdns->dev, "%s: config_update failed\n", __func__);
return ret;
}
/* Prepare slaves for clock stop */
ret = sdw_bus_prep_clk_stop(&cdns->bus);
if (ret < 0) {
dev_err(cdns->dev, "prepare clock stop failed %d", ret);
return ret;
}
/*
* Enter clock stop mode and only report errors if there are
* Slave devices present (ALERT or ATTACHED)
*/
ret = sdw_bus_clk_stop(&cdns->bus);
if (ret < 0 && slave_present && ret != -ENODATA) {
dev_err(cdns->dev, "bus clock stop failed %d", ret);
return ret;
}
ret = cdns_set_wait(cdns, CDNS_MCP_STAT,
CDNS_MCP_STAT_CLK_STOP,
CDNS_MCP_STAT_CLK_STOP);
if (ret < 0)
dev_err(cdns->dev, "Clock stop failed %d\n", ret);
return ret;
}
EXPORT_SYMBOL(sdw_cdns_clock_stop);
/**
* sdw_cdns_clock_restart: Cadence PM clock restart configuration routine
*
* @cdns: Cadence instance
* @bus_reset: context may be lost while in low power modes and the bus
* may require a Severe Reset and re-enumeration after a wake.
*/
int sdw_cdns_clock_restart(struct sdw_cdns *cdns, bool bus_reset)
{
int ret;
/* unmask Slave interrupts that were masked when stopping the clock */
cdns_enable_slave_interrupts(cdns, true);
ret = cdns_clear_bit(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_CLK_STOP_CLR);
if (ret < 0) {
dev_err(cdns->dev, "Couldn't exit from clock stop\n");
return ret;
}
ret = cdns_set_wait(cdns, CDNS_MCP_STAT, CDNS_MCP_STAT_CLK_STOP, 0);
if (ret < 0) {
dev_err(cdns->dev, "clock stop exit failed %d\n", ret);
return ret;
}
cdns_updatel(cdns, CDNS_MCP_CONTROL,
CDNS_MCP_CONTROL_BLOCK_WAKEUP, 0);
/*
* clear CMD_ACCEPT so that the command ignored
* will be treated as a failure during a broadcast write
*/
cdns_updatel(cdns, CDNS_MCP_CONTROL, CDNS_MCP_CONTROL_CMD_ACCEPT, 0);
if (!bus_reset) {
/* enable bus operations with clock and data */
cdns_updatel(cdns, CDNS_MCP_CONFIG,
CDNS_MCP_CONFIG_OP,
CDNS_MCP_CONFIG_OP_NORMAL);
ret = cdns_config_update(cdns);
if (ret < 0) {
dev_err(cdns->dev, "%s: config_update failed\n", __func__);
return ret;
}
ret = sdw_bus_exit_clk_stop(&cdns->bus);
if (ret < 0)
dev_err(cdns->dev, "bus failed to exit clock stop %d\n", ret);
}
return ret;
}
EXPORT_SYMBOL(sdw_cdns_clock_restart);
/**
* sdw_cdns_probe() - Cadence probe routine
* @cdns: Cadence instance
@ -1306,6 +1503,7 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns,
cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
val = pdi->num;
val |= CDNS_PDI_CONFIG_SOFT_RESET;
val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
}

View File

@ -5,6 +5,9 @@
#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)
/**
* struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
*
@ -138,30 +141,26 @@ extern struct sdw_master_ops sdw_cdns_master_ops;
irqreturn_t sdw_cdns_irq(int irq, void *dev_id);
irqreturn_t sdw_cdns_thread(int irq, void *dev_id);
int sdw_cdns_init(struct sdw_cdns *cdns, bool clock_stop_exit);
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
int sdw_cdns_get_stream(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
u32 ch, u32 dir);
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);
int sdw_cdns_pcm_set_stream(struct snd_soc_dai *dai,
void *stream, int direction);
int sdw_cdns_pdm_set_stream(struct snd_soc_dai *dai,
void *stream, int direction);
enum sdw_command_response
cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);

View File

@ -103,7 +103,7 @@ enum intel_pdi_type {
struct sdw_intel {
struct sdw_cdns cdns;
int instance;
struct sdw_intel_link_res *res;
struct sdw_intel_link_res *link_res;
#ifdef CONFIG_DEBUG_FS
struct dentry *debugfs;
#endif
@ -193,8 +193,8 @@ static ssize_t intel_sprintf(void __iomem *mem, bool l,
static int intel_reg_show(struct seq_file *s_file, void *data)
{
struct sdw_intel *sdw = s_file->private;
void __iomem *s = sdw->res->shim;
void __iomem *a = sdw->res->alh;
void __iomem *s = sdw->link_res->shim;
void __iomem *a = sdw->link_res->alh;
char *buf;
ssize_t ret;
int i, j;
@ -289,7 +289,7 @@ static void intel_debugfs_exit(struct sdw_intel *sdw) {}
static int intel_link_power_up(struct sdw_intel *sdw)
{
unsigned int link_id = sdw->instance;
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
int spa_mask, cpa_mask;
int link_control, ret;
@ -309,7 +309,7 @@ static int intel_link_power_up(struct sdw_intel *sdw)
static int intel_shim_init(struct sdw_intel *sdw)
{
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int sync_reg, ret;
u16 ioctl = 0, act = 0;
@ -370,7 +370,7 @@ static int intel_shim_init(struct sdw_intel *sdw)
static void intel_pdi_init(struct sdw_intel *sdw,
struct sdw_cdns_stream_config *config)
{
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int pcm_cap, pdm_cap;
@ -404,7 +404,7 @@ static void intel_pdi_init(struct sdw_intel *sdw,
static int
intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm)
{
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int count;
@ -476,7 +476,7 @@ static int intel_pdi_ch_update(struct sdw_intel *sdw)
static void
intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
unsigned int link_id = sdw->instance;
int pdi_conf = 0;
@ -508,7 +508,7 @@ intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
static void
intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
void __iomem *alh = sdw->res->alh;
void __iomem *alh = sdw->link_res->alh;
unsigned int link_id = sdw->instance;
unsigned int conf;
@ -535,7 +535,7 @@ static int intel_params_stream(struct sdw_intel *sdw,
struct snd_pcm_hw_params *hw_params,
int link_id, int alh_stream_id)
{
struct sdw_intel_link_res *res = sdw->res;
struct sdw_intel_link_res *res = sdw->link_res;
struct sdw_intel_stream_params_data params_data;
params_data.substream = substream;
@ -550,6 +550,25 @@ static int intel_params_stream(struct sdw_intel *sdw,
return -EIO;
}
static int intel_free_stream(struct sdw_intel *sdw,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
int link_id)
{
struct sdw_intel_link_res *res = sdw->link_res;
struct sdw_intel_stream_free_data free_data;
free_data.substream = substream;
free_data.dai = dai;
free_data.link_id = link_id;
if (res->ops && res->ops->free_stream && res->dev)
return res->ops->free_stream(res->dev,
&free_data);
return 0;
}
/*
* bank switch routines
*/
@ -558,7 +577,7 @@ static int intel_pre_bank_switch(struct sdw_bus *bus)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
struct sdw_intel *sdw = cdns_to_intel(cdns);
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
int sync_reg;
/* Write to register only for multi-link */
@ -577,7 +596,7 @@ static int intel_post_bank_switch(struct sdw_bus *bus)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
struct sdw_intel *sdw = cdns_to_intel(cdns);
void __iomem *shim = sdw->res->shim;
void __iomem *shim = sdw->link_res->shim;
int sync_reg, ret;
/* Write to register only for multi-link */
@ -617,6 +636,68 @@ static int intel_post_bank_switch(struct sdw_bus *bus)
* DAI routines
*/
static int sdw_stream_setup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct sdw_stream_runtime *sdw_stream = NULL;
char *name;
int i, ret;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
name = kasprintf(GFP_KERNEL, "%s-Playback", dai->name);
else
name = kasprintf(GFP_KERNEL, "%s-Capture", dai->name);
if (!name)
return -ENOMEM;
sdw_stream = sdw_alloc_stream(name);
if (!sdw_stream) {
dev_err(dai->dev, "alloc stream failed for DAI %s", dai->name);
ret = -ENOMEM;
goto error;
}
/* Set stream pointer on CPU DAI */
ret = snd_soc_dai_set_sdw_stream(dai, sdw_stream, substream->stream);
if (ret < 0) {
dev_err(dai->dev, "failed to set stream pointer on cpu dai %s",
dai->name);
goto release_stream;
}
/* Set stream pointer on all CODEC DAIs */
for (i = 0; i < rtd->num_codecs; i++) {
ret = snd_soc_dai_set_sdw_stream(rtd->codec_dais[i], sdw_stream,
substream->stream);
if (ret < 0) {
dev_err(dai->dev, "failed to set stream pointer on codec dai %s",
rtd->codec_dais[i]->name);
goto release_stream;
}
}
return 0;
release_stream:
sdw_release_stream(sdw_stream);
error:
kfree(name);
return ret;
}
static int intel_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
/*
* TODO: add pm_runtime support here, the startup callback
* will make sure the IP is 'active'
*/
return sdw_stream_setup(substream, dai);
}
static int intel_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
@ -699,10 +780,63 @@ error:
return ret;
}
static int intel_prepare(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
struct sdw_cdns_dma_data *dma;
dma = snd_soc_dai_get_dma_data(dai, substream);
if (!dma) {
dev_err(dai->dev, "failed to get dma data in %s",
__func__);
return -EIO;
}
return sdw_prepare_stream(dma->stream);
}
static int intel_trigger(struct snd_pcm_substream *substream, int cmd,
struct snd_soc_dai *dai)
{
struct sdw_cdns_dma_data *dma;
int ret;
dma = snd_soc_dai_get_dma_data(dai, substream);
if (!dma) {
dev_err(dai->dev, "failed to get dma data in %s", __func__);
return -EIO;
}
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
case SNDRV_PCM_TRIGGER_RESUME:
ret = sdw_enable_stream(dma->stream);
break;
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_STOP:
ret = sdw_disable_stream(dma->stream);
break;
default:
ret = -EINVAL;
break;
}
if (ret)
dev_err(dai->dev,
"%s trigger %d failed: %d",
__func__, cmd, ret);
return ret;
}
static int
intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
struct sdw_intel *sdw = cdns_to_intel(cdns);
struct sdw_cdns_dma_data *dma;
int ret;
@ -710,12 +844,29 @@ intel_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
if (!dma)
return -EIO;
ret = sdw_deprepare_stream(dma->stream);
if (ret) {
dev_err(dai->dev, "sdw_deprepare_stream: failed %d", ret);
return ret;
}
ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
if (ret < 0)
if (ret < 0) {
dev_err(dai->dev, "remove master from stream %s failed: %d\n",
dma->stream->name, ret);
return ret;
}
return ret;
ret = intel_free_stream(sdw, substream, dai, sdw->instance);
if (ret < 0) {
dev_err(dai->dev, "intel_free_stream: failed %d", ret);
return ret;
}
kfree(dma->stream->name);
sdw_release_stream(dma->stream);
return 0;
}
static void intel_shutdown(struct snd_pcm_substream *substream,
@ -744,14 +895,20 @@ static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai,
}
static const struct snd_soc_dai_ops intel_pcm_dai_ops = {
.startup = intel_startup,
.hw_params = intel_hw_params,
.prepare = intel_prepare,
.trigger = intel_trigger,
.hw_free = intel_hw_free,
.shutdown = intel_shutdown,
.set_sdw_stream = intel_pcm_set_sdw_stream,
};
static const struct snd_soc_dai_ops intel_pdm_dai_ops = {
.startup = intel_startup,
.hw_params = intel_hw_params,
.prepare = intel_prepare,
.trigger = intel_trigger,
.hw_free = intel_hw_free,
.shutdown = intel_shutdown,
.set_sdw_stream = intel_pdm_set_sdw_stream,
@ -920,7 +1077,7 @@ static int intel_init(struct sdw_intel *sdw)
intel_link_power_up(sdw);
intel_shim_init(sdw);
return sdw_cdns_init(&sdw->cdns, false);
return sdw_cdns_init(&sdw->cdns);
}
/*
@ -937,9 +1094,9 @@ static int intel_probe(struct platform_device *pdev)
return -ENOMEM;
sdw->instance = pdev->id;
sdw->res = dev_get_platdata(&pdev->dev);
sdw->link_res = dev_get_platdata(&pdev->dev);
sdw->cdns.dev = &pdev->dev;
sdw->cdns.registers = sdw->res->registers;
sdw->cdns.registers = sdw->link_res->registers;
sdw->cdns.instance = sdw->instance;
sdw->cdns.msg_count = 0;
sdw->cdns.bus.dev = &pdev->dev;
@ -979,11 +1136,12 @@ static int intel_probe(struct platform_device *pdev)
intel_pdi_ch_update(sdw);
/* Acquire IRQ */
ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq, sdw_cdns_thread,
ret = request_threaded_irq(sdw->link_res->irq,
sdw_cdns_irq, sdw_cdns_thread,
IRQF_SHARED, KBUILD_MODNAME, &sdw->cdns);
if (ret < 0) {
dev_err(sdw->cdns.dev, "unable to grab IRQ %d, disabling device\n",
sdw->res->irq);
sdw->link_res->irq);
goto err_init;
}
@ -1013,7 +1171,7 @@ static int intel_probe(struct platform_device *pdev)
err_interrupt:
sdw_cdns_enable_interrupt(&sdw->cdns, false);
free_irq(sdw->res->irq, sdw);
free_irq(sdw->link_res->irq, sdw);
err_init:
sdw_delete_bus_master(&sdw->cdns.bus);
return ret;
@ -1028,7 +1186,7 @@ static int intel_remove(struct platform_device *pdev)
if (!sdw->cdns.bus.prop.hw_disabled) {
intel_debugfs_exit(sdw);
sdw_cdns_enable_interrupt(&sdw->cdns, false);
free_irq(sdw->res->irq, sdw);
free_irq(sdw->link_res->irq, sdw);
snd_soc_unregister_component(sdw->cdns.dev);
}
sdw_delete_bus_master(&sdw->cdns.bus);

View File

@ -588,6 +588,13 @@ static int qcom_swrm_set_sdw_stream(struct snd_soc_dai *dai,
return 0;
}
static void *qcom_swrm_get_sdw_stream(struct snd_soc_dai *dai, int direction)
{
struct qcom_swrm_ctrl *ctrl = dev_get_drvdata(dai->dev);
return ctrl->sruntime[dai->id];
}
static int qcom_swrm_startup(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai)
{
@ -631,6 +638,7 @@ static const struct snd_soc_dai_ops qcom_swrm_pdm_dai_ops = {
.startup = qcom_swrm_startup,
.shutdown = qcom_swrm_shutdown,
.set_sdw_stream = qcom_swrm_set_sdw_stream,
.get_sdw_stream = qcom_swrm_get_sdw_stream,
};
static const struct snd_soc_component_driver qcom_swrm_dai_component = {

View File

@ -46,7 +46,11 @@ static int sdw_slave_add(struct sdw_bus *bus,
slave->dev.of_node = of_node_get(to_of_node(fwnode));
slave->bus = bus;
slave->status = SDW_SLAVE_UNATTACHED;
init_completion(&slave->enumeration_complete);
init_completion(&slave->initialization_complete);
slave->dev_num = 0;
init_completion(&slave->probe_complete);
slave->probed = false;
mutex_lock(&bus->bus_lock);
list_add_tail(&slave->node, &bus->slaves);

View File

@ -167,13 +167,15 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
return ret;
}
/* Program DPN_BlockCtrl1 register */
ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1));
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_BlockCtrl1 register write failed for port %d\n",
t_params->port_num);
return ret;
if (!dpn_prop->read_only_wordlength) {
/* Program DPN_BlockCtrl1 register */
ret = sdw_write(s_rt->slave, addr2, (p_params->bps - 1));
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_BlockCtrl1 register write failed for port %d\n",
t_params->port_num);
return ret;
}
}
/* Program DPN_SampleCtrl1 register */
@ -313,9 +315,9 @@ static int sdw_enable_disable_slave_ports(struct sdw_bus *bus,
* it is safe to reset this register
*/
if (en)
ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask);
ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask);
else
ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
ret = sdw_write(s_rt->slave, addr, 0x0);
if (ret < 0)
dev_err(&s_rt->slave->dev,
@ -464,10 +466,9 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
addr = SDW_DPN_PREPARECTRL(p_rt->num);
if (prep)
ret = sdw_update(s_rt->slave, addr,
0xFF, p_rt->ch_mask);
ret = sdw_write(s_rt->slave, addr, p_rt->ch_mask);
else
ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
ret = sdw_write(s_rt->slave, addr, 0x0);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
@ -587,10 +588,11 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt)
if (slave->ops->bus_config) {
ret = slave->ops->bus_config(slave, &bus->params);
if (ret < 0)
if (ret < 0) {
dev_err(bus->dev, "Notify Slave: %d failed\n",
slave->dev_num);
return ret;
return ret;
}
}
}
@ -602,13 +604,25 @@ static int sdw_notify_config(struct sdw_master_runtime *m_rt)
* and Slave(s)
*
* @bus: SDW bus instance
* @prepare: true if sdw_program_params() is called by _prepare.
*/
static int sdw_program_params(struct sdw_bus *bus)
static int sdw_program_params(struct sdw_bus *bus, bool prepare)
{
struct sdw_master_runtime *m_rt;
int ret = 0;
list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
/*
* this loop walks through all master runtimes for a
* bus, but the ports can only be configured while
* explicitly preparing a stream or handling an
* already-prepared stream otherwise.
*/
if (!prepare &&
m_rt->stream->state == SDW_STREAM_CONFIGURED)
continue;
ret = sdw_program_port_params(m_rt);
if (ret < 0) {
dev_err(bus->dev,
@ -1460,7 +1474,8 @@ static void sdw_release_bus_lock(struct sdw_stream_runtime *stream)
}
}
static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
static int _sdw_prepare_stream(struct sdw_stream_runtime *stream,
bool update_params)
{
struct sdw_master_runtime *m_rt;
struct sdw_bus *bus = NULL;
@ -1480,6 +1495,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
return -EINVAL;
}
if (!update_params)
goto program_params;
/* Increment cumulative bus bandwidth */
/* TODO: Update this during Device-Device support */
bus->params.bandwidth += m_rt->stream->params.rate *
@ -1495,8 +1513,9 @@ static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
}
}
program_params:
/* Program params */
ret = sdw_program_params(bus);
ret = sdw_program_params(bus, true);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d\n", ret);
goto restore_params;
@ -1544,7 +1563,8 @@ restore_params:
*/
int sdw_prepare_stream(struct sdw_stream_runtime *stream)
{
int ret = 0;
bool update_params = true;
int ret;
if (!stream) {
pr_err("SoundWire: Handle not found for stream\n");
@ -1553,8 +1573,32 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
ret = _sdw_prepare_stream(stream);
if (stream->state == SDW_STREAM_PREPARED) {
ret = 0;
goto state_err;
}
if (stream->state != SDW_STREAM_CONFIGURED &&
stream->state != SDW_STREAM_DEPREPARED &&
stream->state != SDW_STREAM_DISABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
__func__, stream->name, stream->state);
ret = -EINVAL;
goto state_err;
}
/*
* when the stream is DISABLED, this means sdw_prepare_stream()
* is called as a result of an underflow or a resume operation.
* In this case, the bus parameters shall not be recomputed, but
* still need to be re-applied
*/
if (stream->state == SDW_STREAM_DISABLED)
update_params = false;
ret = _sdw_prepare_stream(stream, update_params);
state_err:
sdw_release_bus_lock(stream);
return ret;
}
@ -1571,7 +1615,7 @@ static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
bus = m_rt->bus;
/* Program params */
ret = sdw_program_params(bus);
ret = sdw_program_params(bus, false);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d\n", ret);
return ret;
@ -1619,8 +1663,17 @@ int sdw_enable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
if (stream->state != SDW_STREAM_PREPARED &&
stream->state != SDW_STREAM_DISABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
__func__, stream->name, stream->state);
ret = -EINVAL;
goto state_err;
}
ret = _sdw_enable_stream(stream);
state_err:
sdw_release_bus_lock(stream);
return ret;
}
@ -1647,7 +1700,7 @@ static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
struct sdw_bus *bus = m_rt->bus;
/* Program params */
ret = sdw_program_params(bus);
ret = sdw_program_params(bus, false);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d\n", ret);
return ret;
@ -1693,8 +1746,16 @@ int sdw_disable_stream(struct sdw_stream_runtime *stream)
sdw_acquire_bus_lock(stream);
if (stream->state != SDW_STREAM_ENABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
__func__, stream->name, stream->state);
ret = -EINVAL;
goto state_err;
}
ret = _sdw_disable_stream(stream);
state_err:
sdw_release_bus_lock(stream);
return ret;
}
@ -1721,7 +1782,7 @@ static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
m_rt->ch_count * m_rt->stream->params.bps;
/* Program params */
ret = sdw_program_params(bus);
ret = sdw_program_params(bus, false);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d\n", ret);
return ret;
@ -1749,8 +1810,18 @@ int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
}
sdw_acquire_bus_lock(stream);
if (stream->state != SDW_STREAM_PREPARED &&
stream->state != SDW_STREAM_DISABLED) {
pr_err("%s: %s: inconsistent state state %d\n",
__func__, stream->name, stream->state);
ret = -EINVAL;
goto state_err;
}
ret = _sdw_deprepare_stream(stream);
state_err:
sdw_release_bus_lock(stream);
return ret;
}

View File

@ -79,6 +79,21 @@ enum sdw_slave_status {
SDW_SLAVE_RESERVED = 3,
};
/**
* enum sdw_clk_stop_type: clock stop operations
*
* @SDW_CLK_PRE_PREPARE: pre clock stop prepare
* @SDW_CLK_POST_PREPARE: post clock stop prepare
* @SDW_CLK_PRE_DEPREPARE: pre clock stop de-prepare
* @SDW_CLK_POST_DEPREPARE: post clock stop de-prepare
*/
enum sdw_clk_stop_type {
SDW_CLK_PRE_PREPARE = 0,
SDW_CLK_POST_PREPARE,
SDW_CLK_PRE_DEPREPARE,
SDW_CLK_POST_DEPREPARE,
};
/**
* enum sdw_command_response - Command response as defined by SDW spec
* @SDW_CMD_OK: cmd was successful
@ -284,6 +299,7 @@ struct sdw_dpn_audio_mode {
* @max_async_buffer: Number of samples that this port can buffer in
* asynchronous modes
* @block_pack_mode: Type of block port mode supported
* @read_only_wordlength: Read Only wordlength field in DPN_BlockCtrl1 register
* @port_encoding: Payload Channel Sample encoding schemes supported
* @audio_modes: Audio modes supported
*/
@ -307,6 +323,7 @@ struct sdw_dpn_prop {
u32 modes;
u32 max_async_buffer;
bool block_pack_mode;
bool read_only_wordlength;
u32 port_encoding;
struct sdw_dpn_audio_mode *audio_modes;
};
@ -424,6 +441,29 @@ struct sdw_slave_id {
__u8 sdw_version:4;
};
/*
* Helper macros to extract the MIPI-defined IDs
*
* Spec definition
* Register Bit Contents
* DevId_0 [7:4] 47:44 sdw_version
* DevId_0 [3:0] 43:40 unique_id
* DevId_1 39:32 mfg_id [15:8]
* DevId_2 31:24 mfg_id [7:0]
* DevId_3 23:16 part_id [15:8]
* DevId_4 15:08 part_id [7:0]
* DevId_5 07:00 class_id
*
* The MIPI DisCo for SoundWire defines in addition the link_id as bits 51:48
*/
#define SDW_DISCO_LINK_ID(adr) (((adr) >> 48) & GENMASK(3, 0))
#define SDW_VERSION(adr) (((adr) >> 44) & GENMASK(3, 0))
#define SDW_UNIQUE_ID(adr) (((adr) >> 40) & GENMASK(3, 0))
#define SDW_MFG_ID(adr) (((adr) >> 24) & GENMASK(15, 0))
#define SDW_PART_ID(adr) (((adr) >> 8) & GENMASK(15, 0))
#define SDW_CLASS_ID(adr) ((adr) & GENMASK(7, 0))
/**
* struct sdw_slave_intr_status - Slave interrupt status
* @control_port: control port status
@ -533,6 +573,11 @@ struct sdw_slave_ops {
int (*port_prep)(struct sdw_slave *slave,
struct sdw_prepare_ch *prepare_ch,
enum sdw_port_prep_ops pre_ops);
int (*get_clk_stop_mode)(struct sdw_slave *slave);
int (*clk_stop)(struct sdw_slave *slave,
enum sdw_clk_stop_mode mode,
enum sdw_clk_stop_type type);
};
/**
@ -575,6 +620,7 @@ struct sdw_slave {
#endif
struct list_head node;
struct completion *port_ready;
enum sdw_clk_stop_mode curr_clk_stop_mode;
u16 dev_num;
u16 dev_num_sticky;
bool probed;
@ -892,6 +938,9 @@ int sdw_prepare_stream(struct sdw_stream_runtime *stream);
int sdw_enable_stream(struct sdw_stream_runtime *stream);
int sdw_disable_stream(struct sdw_stream_runtime *stream);
int sdw_deprepare_stream(struct sdw_stream_runtime *stream);
int sdw_bus_prep_clk_stop(struct sdw_bus *bus);
int sdw_bus_clk_stop(struct sdw_bus *bus);
int sdw_bus_exit_clk_stop(struct sdw_bus *bus);
/* messaging and data APIs */