Commit 176c2572 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

Merge tag 'soundwire-streaming' of...

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

Vinod writes:

soundwire streaming

This contains:
 - Support for SoundWire Streaming
 - Documentation updates for streaming
 - Cadence and Intel driver updates for streaming
 - ASoC API for programming soundwire stream
parents 720d690e c46302ec
========================
SoundWire Error Handling
========================
The SoundWire PHY was designed with care and errors on the bus are going to
be very unlikely, and if they happen it should be limited to single bit
errors. Examples of this design can be found in the synchronization
mechanism (sync loss after two errors) and short CRCs used for the Bulk
Register Access.
The errors can be detected with multiple mechanisms:
1. Bus clash or parity errors: This mechanism relies on low-level detectors
that are independent of the payload and usages, and they cover both control
and audio data. The current implementation only logs such errors.
Improvements could be invalidating an entire programming sequence and
restarting from a known position. In the case of such errors outside of a
control/command sequence, there is no concealment or recovery for audio
data enabled by the SoundWire protocol, the location of the error will also
impact its audibility (most-significant bits will be more impacted in PCM),
and after a number of such errors are detected the bus might be reset. Note
that bus clashes due to programming errors (two streams using the same bit
slots) or electrical issues during the transmit/receive transition cannot
be distinguished, although a recurring bus clash when audio is enabled is a
indication of a bus allocation issue. The interrupt mechanism can also help
identify Slaves which detected a Bus Clash or a Parity Error, but they may
not be responsible for the errors so resetting them individually is not a
viable recovery strategy.
2. Command status: Each command is associated with a status, which only
covers transmission of the data between devices. The ACK status indicates
that the command was received and will be executed by the end of the
current frame. A NAK indicates that the command was in error and will not
be applied. In case of a bad programming (command sent to non-existent
Slave or to a non-implemented register) or electrical issue, no response
signals the command was ignored. Some Master implementations allow for a
command to be retransmitted several times. If the retransmission fails,
backtracking and restarting the entire programming sequence might be a
solution. Alternatively some implementations might directly issue a bus
reset and re-enumerate all devices.
3. Timeouts: In a number of cases such as ChannelPrepare or
ClockStopPrepare, the bus driver is supposed to poll a register field until
it transitions to a NotFinished value of zero. The MIPI SoundWire spec 1.1
does not define timeouts but the MIPI SoundWire DisCo document adds
recommendation on timeouts. If such configurations do not complete, the
driver will return a -ETIMEOUT. Such timeouts are symptoms of a faulty
Slave device and are likely impossible to recover from.
Errors during global reconfiguration sequences are extremely difficult to
handle:
1. BankSwitch: An error during the last command issuing a BankSwitch is
difficult to backtrack from. Retransmitting the Bank Switch command may be
possible in a single segment setup, but this can lead to synchronization
problems when enabling multiple bus segments (a command with side effects
such as frame reconfiguration would be handled at different times). A global
hard-reset might be the best solution.
Note that SoundWire does not provide a mechanism to detect illegal values
written in valid registers. In a number of cases the standard even mentions
that the Slave might behave in implementation-defined ways. The bus
implementation does not provide a recovery mechanism for such errors, Slave
or Master driver implementers are responsible for writing valid values in
valid registers and implement additional range checking if needed.
......@@ -6,6 +6,9 @@ SoundWire Documentation
:maxdepth: 1
summary
stream
error_handling
locking
.. only:: subproject
......
=================
SoundWire Locking
=================
This document explains locking mechanism of the SoundWire Bus. Bus uses
following locks in order to avoid race conditions in Bus operations on
shared resources.
- Bus lock
- Message lock
Bus lock
========
SoundWire Bus lock is a mutex and is part of Bus data structure
(sdw_bus) which is used for every Bus instance. This lock is used to
serialize each of the following operations(s) within SoundWire Bus instance.
- Addition and removal of Slave(s), changing Slave status.
- Prepare, Enable, Disable and De-prepare stream operations.
- Access of Stream data structure.
Message lock
============
SoundWire message transfer lock. This mutex is part of
Bus data structure (sdw_bus). This lock is used to serialize the message
transfers (read/write) within a SoundWire Bus instance.
Below examples show how locks are acquired.
Example 1
---------
Message transfer.
1. For every message transfer
a. Acquire Message lock.
b. Transfer message (Read/Write) to Slave1 or broadcast message on
Bus in case of bank switch.
c. Release Message lock ::
+----------+ +---------+
| | | |
| Bus | | Master |
| | | Driver |
| | | |
+----+-----+ +----+----+
| |
| bus->ops->xfer_msg() |
<-------------------------------+ a. Acquire Message lock
| | b. Transfer message
| |
+-------------------------------> c. Release Message lock
| return success/error | d. Return success/error
| |
+ +
Example 2
---------
Prepare operation.
1. Acquire lock for Bus instance associated with Master 1.
2. For every message transfer in Prepare operation
a. Acquire Message lock.
b. Transfer message (Read/Write) to Slave1 or broadcast message on
Bus in case of bank switch.
c. Release Message lock.
3. Release lock for Bus instance associated with Master 1 ::
+----------+ +---------+
| | | |
| Bus | | Master |
| | | Driver |
| | | |
+----+-----+ +----+----+
| |
| sdw_prepare_stream() |
<-------------------------------+ 1. Acquire bus lock
| | 2. Perform stream prepare
| |
| |
| bus->ops->xfer_msg() |
<-------------------------------+ a. Acquire Message lock
| | b. Transfer message
| |
+-------------------------------> c. Release Message lock
| return success/error | d. Return success/error
| |
| |
| return success/error | 3. Release bus lock
+-------------------------------> 4. Return success/error
| |
+ +
=========================
Audio Stream in SoundWire
=========================
An audio stream is a logical or virtual connection created between
(1) System memory buffer(s) and Codec(s)
(2) DSP memory buffer(s) and Codec(s)
(3) FIFO(s) and Codec(s)
(4) Codec(s) and Codec(s)
which is typically driven by a DMA(s) channel through the data link. An
audio stream contains one or more channels of data. All channels within
stream must have same sample rate and same sample size.
Assume a stream with two channels (Left & Right) is opened using SoundWire
interface. Below are some ways a stream can be represented in SoundWire.
Stream Sample in memory (System memory, DSP memory or FIFOs) ::
-------------------------
| L | R | L | R | L | R |
-------------------------
Example 1: Stereo Stream with L and R channels is rendered from Master to
Slave. Both Master and Slave is using single port. ::
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| | | 1 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ +-----------------------> +---------------+
Example 2: Stereo Stream with L and R channels is captured from Slave to
Master. Both Master and Slave is using single port. ::
+---------------+ Clock Signal +---------------+
| Master +----------------------------------+ Slave |
| Interface | | Interface |
| | | 1 |
| | Data Signal | |
| L + R +----------------------------------+ L + R |
| (Data) | Data Direction | (Data) |
+---------------+ <-----------------------+ +---------------+
Example 3: Stereo Stream with L and R channels is rendered by Master. Each
of the L and R channel is received by two different Slaves. Master and both
Slaves are using single port. ::
+---------------+ Clock Signal +---------------+
| Master +---------+------------------------+ Slave |
| Interface | | | Interface |
| | | | 1 |
| | | Data Signal | |
| L + R +---+------------------------------+ L |
| (Data) | | | Data Direction | (Data) |
+---------------+ | | +-------------> +---------------+
| |
| |
| | +---------------+
| +----------------------> | Slave |
| | Interface |
| | 2 |
| | |
+----------------------------> | R |
| (Data) |
+---------------+
Example 4: Stereo Stream with L and R channel is rendered by two different
Ports of the Master and is received by only single Port of the Slave
interface. ::
+--------------------+
| |
| +--------------+ +----------------+
| | || | |
| | Data Port || L Channel | |
| | 1 |------------+ | |
| | L Channel || | +-----+----+ |
| | (Data) || | L + R Channel || Data | |
| Master +----------+ | +---+---------> || Port | |
| Interface | | || 1 | |
| +--------------+ | || | |
| | || | +----------+ |
| | Data Port |------------+ | |
| | 2 || R Channel | Slave |
| | R Channel || | Interface |
| | (Data) || | 1 |
| +--------------+ Clock Signal | L + R |
| +---------------------------> | (Data) |
+--------------------+ | |
+----------------+
SoundWire Stream Management flow
================================
Stream definitions
------------------
(1) Current stream: This is classified as the stream on which operation has
to be performed like prepare, enable, disable, de-prepare etc.
(2) Active stream: This is classified as the stream which is already active
on Bus other than current stream. There can be multiple active streams
on the Bus.
SoundWire Bus manages stream operations for each stream getting
rendered/captured on the SoundWire Bus. This section explains Bus operations
done for each of the stream allocated/released on Bus. Following are the
stream states maintained by the Bus for each of the audio stream.
SoundWire stream states
-----------------------
Below shows the SoundWire stream states and state transition diagram. ::
+-----------+ +------------+ +----------+ +----------+
| ALLOCATED +---->| CONFIGURED +---->| PREPARED +---->| ENABLED |
| STATE | | STATE | | STATE | | STATE |
+-----------+ +------------+ +----------+ +----+-----+
^
|
|
v
+----------+ +------------+ +----+-----+
| RELEASED |<----------+ DEPREPARED |<-------+ DISABLED |
| STATE | | STATE | | STATE |
+----------+ +------------+ +----------+
NOTE: State transition between prepare and deprepare is supported in Spec
but not in the software (subsystem)
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.
Stream State Operations
-----------------------
Below section explains the operations done by the Bus on Master(s) and
Slave(s) as part of stream state transitions.
SDW_STREAM_ALLOCATED
~~~~~~~~~~~~~~~~~~~~
Allocation state for stream. This is the entry state
of the stream. Operations performed before entering in this state:
(1) A stream runtime is allocated for the stream. This stream
runtime is used as a reference for all the operations performed
on the stream.
(2) The resources required for holding stream runtime information are
allocated and initialized. This holds all stream related information
such as stream type (PCM/PDM) and parameters, Master and Slave
interface associated with the stream, stream state etc.
After all above operations are successful, stream state is set to
``SDW_STREAM_ALLOCATED``.
Bus implements below API for allocate a stream which needs to be called once
per stream. From ASoC DPCM framework, this stream state maybe linked to
.startup() operation.
.. code-block:: c
int sdw_alloc_stream(char * stream_name);
SDW_STREAM_CONFIGURED
~~~~~~~~~~~~~~~~~~~~~
Configuration state of stream. Operations performed before entering in
this state:
(1) The resources allocated for stream information in SDW_STREAM_ALLOCATED
state are updated here. This includes stream parameters, Master(s)
and Slave(s) runtime information associated with current stream.
(2) All the Master(s) and Slave(s) associated with current stream provide
the port information to Bus which includes port numbers allocated by
Master(s) and Slave(s) for current stream and their channel mask.
After all above operations are successful, stream state is set to
``SDW_STREAM_CONFIGURED``.
Bus implements below APIs for CONFIG state which needs to be called by
the respective Master(s) and Slave(s) associated with stream. These APIs can
only be invoked once by respective Master(s) and Slave(s). From ASoC DPCM
framework, this stream state is linked to .hw_params() operation.
.. code-block:: c
int sdw_stream_add_master(struct sdw_bus * bus,
struct sdw_stream_config * stream_config,
struct sdw_ports_config * ports_config,
struct sdw_stream_runtime * stream);
int sdw_stream_add_slave(struct sdw_slave * slave,
struct sdw_stream_config * stream_config,
struct sdw_ports_config * ports_config,
struct sdw_stream_runtime * stream);
SDW_STREAM_PREPARED
~~~~~~~~~~~~~~~~~~~
Prepare state of stream. Operations performed before entering in this state:
(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
stream on the Bus.
(2) Transport and port parameters of all Master(s) and Slave(s) port(s) are
computed for the current as well as already active stream based on frame
shape and clock frequency computed in step 1.
(3) Computed Bus and transport parameters are programmed in Master(s) and
Slave(s) registers. The banked registers programming is done on the
alternate bank (bank currently unused). Port(s) are enabled for the
already active stream(s) on the alternate bank (bank currently unused).
This is done in order to not disrupt already active stream(s).
(4) Once all the values are programmed, Bus initiates switch to alternate
bank where all new values programmed gets into effect.
(5) Ports of Master(s) and Slave(s) for current stream are prepared by
programming PrepareCtrl register.
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.
.. code-block:: c
int sdw_prepare_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_ENABLED
~~~~~~~~~~~~~~~~~~
Enable state of stream. The data port(s) are enabled upon entering this state.
Operations performed before entering in this state:
(1) All the values computed in SDW_STREAM_PREPARED state are programmed
in alternate bank (bank currently unused). It includes programming of
already active stream(s) as well.
(2) All the Master(s) and Slave(s) port(s) for the current stream are
enabled on alternate bank (bank currently unused) by programming
ChannelEn register.
(3) Once all the values are programmed, Bus initiates switch to alternate
bank where all new values programmed gets into effect and port(s)
associated with current stream are enabled.
After all above operations are successful, stream state is set to
``SDW_STREAM_ENABLED``.
Bus implements below API for ENABLE state which needs to be called once per
stream. From ASoC DPCM framework, this stream state is linked to
.trigger() start operation.
.. code-block:: c
int sdw_enable_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_DISABLED
~~~~~~~~~~~~~~~~~~~
Disable state of stream. The data port(s) are disabled upon exiting this state.
Operations performed before entering in this state:
(1) All the Master(s) and Slave(s) port(s) for the current stream are
disabled on alternate bank (bank currently unused) by programming
ChannelEn register.
(2) All the current configuration of Bus and active stream(s) are programmed
into alternate bank (bank currently unused).
(3) Once all the values are programmed, Bus initiates switch to alternate
bank where all new values programmed gets into effect and port(s) associated
with current stream are disabled.
After all above operations are successful, stream state is set to
``SDW_STREAM_DISABLED``.
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.
.. code-block:: c
int sdw_disable_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_DEPREPARED
~~~~~~~~~~~~~~~~~~~~~
De-prepare state of stream. Operations performed before entering in this
state:
(1) All the port(s) of Master(s) and Slave(s) for current stream are
de-prepared by programming PrepareCtrl register.
(2) The payload bandwidth of current stream is reduced from the total
bandwidth requirement of bus and new parameters calculated and
applied by performing bank switch etc.
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.
.. code-block:: c
int sdw_deprepare_stream(struct sdw_stream_runtime * stream);
SDW_STREAM_RELEASED
~~~~~~~~~~~~~~~~~~~
Release state of stream. Operations performed before entering in this state:
(1) Release port resources for all Master(s) and Slave(s) port(s)
associated with current stream.
(2) Release Master(s) and Slave(s) runtime resources associated with
current stream.
(3) Release stream runtime resources associated with current stream.
After all above operations are successful, stream state is set to
``SDW_STREAM_RELEASED``.
Bus implements below APIs for RELEASE state which needs to be called by
all the Master(s) and Slave(s) associated with stream. From ASoC DPCM
framework, this stream state is linked to .hw_free() operation.
.. code-block:: c
int sdw_stream_remove_master(struct sdw_bus * bus,
struct sdw_stream_runtime * stream);
int sdw_stream_remove_slave(struct sdw_slave * slave,
struct sdw_stream_runtime * stream);
The .shutdown() ASoC DPCM operation calls below Bus API to release
stream assigned as part of ALLOCATED state.
In .shutdown() the data structure maintaining stream state are freed up.
.. code-block:: c
void sdw_release_stream(struct sdw_stream_runtime * stream);
Not Supported
=============
1. A single port with multiple channels supported cannot be used between two
streams or across stream. For example a port with 4 channels cannot be used
to handle 2 independent stereo streams even though it's possible in theory
in SoundWire.
......@@ -13115,7 +13115,7 @@ F: include/uapi/sound/
F: sound/
SOUND - COMPRESSED AUDIO
M: Vinod Koul <vinod.koul@intel.com>
M: Vinod Koul <vkoul@kernel.org>
L: alsa-devel@alsa-project.org (moderated for non-subscribers)
T: git git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git
S: Supported
......
......@@ -27,7 +27,7 @@ config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"
select SOUNDWIRE_CADENCE
select SOUNDWIRE_BUS
depends on X86 && ACPI
depends on X86 && ACPI && SND_SOC
---help---
SoundWire Intel Master driver.
If you have an Intel platform which has a SoundWire Master then
......
......@@ -3,7 +3,7 @@
#
#Bus Objs
soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o
soundwire-bus-objs := bus_type.o bus.o slave.o mipi_disco.o stream.o
obj-$(CONFIG_SOUNDWIRE_BUS) += soundwire-bus.o
#Cadence Objs
......
......@@ -17,6 +17,7 @@
*/
int sdw_add_bus_master(struct sdw_bus *bus)
{
struct sdw_master_prop *prop = NULL;
int ret;
if (!bus->dev) {
......@@ -32,6 +33,7 @@ int sdw_add_bus_master(struct sdw_bus *bus)
mutex_init(&bus->msg_lock);
mutex_init(&bus->bus_lock);
INIT_LIST_HEAD(&bus->slaves);
INIT_LIST_HEAD(&bus->m_rt_list);
if (bus->ops->read_prop) {
ret = bus->ops->read_prop(bus);
......@@ -77,6 +79,21 @@ int sdw_add_bus_master(struct sdw_bus *bus)
return ret;
}
/*
* Initialize clock values based on Master properties. The max
* frequency is read from max_freq property. Current assumption
* is that the bus will start at highest clock frequency when
* powered on.
*
* Default active bank will be 0 as out of reset the Slaves have
* to start with bank 0 (Table 40 of Spec)
*/
prop = &bus->prop;
bus->params.max_dr_freq = prop->max_freq * SDW_DOUBLE_RATE_FACTOR;
bus->params.curr_dr_freq = bus->params.max_dr_freq;
bus->params.curr_bank = SDW_BANK0;
bus->params.next_bank = SDW_BANK1;
return 0;
}
EXPORT_SYMBOL(sdw_add_bus_master);
......@@ -576,6 +593,32 @@ static void sdw_modify_slave_status(struct sdw_slave *slave,
mutex_unlock(&slave->bus->bus_lock);
}
int sdw_configure_dpn_intr(struct sdw_slave *slave,
int port, bool enable, int mask)
{
u32 addr;
int ret;
u8 val = 0;
addr = SDW_DPN_INTMASK(port);
/* Set/Clear port ready interrupt mask */
if (enable) {
val |= mask;
val |= SDW_DPN_INT_PORT_READY;
} else {
val &= ~(mask);
val &= ~SDW_DPN_INT_PORT_READY;
}
ret = sdw_update(slave, addr, (mask | SDW_DPN_INT_PORT_READY), val);
if (ret < 0)
dev_err(slave->bus->dev,
"SDW_DPN_INTMASK write failed:%d", val);
return ret;
}
static int sdw_initialize_slave(struct sdw_slave *slave)
{
struct sdw_slave_prop *prop = &slave->prop;
......
......@@ -45,6 +45,78 @@ struct sdw_msg {
bool page;
};
#define SDW_DOUBLE_RATE_FACTOR 2
extern int rows[SDW_FRAME_ROWS];
extern int cols[SDW_FRAME_COLS];
/**
* 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
*
* 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;
};
/**
* 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.
* @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 bus_node;
};
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,
struct sdw_defer *defer);
......
......@@ -13,6 +13,8 @@
#include <linux/mod_devicetable.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include "bus.h"
#include "cadence_master.h"
......@@ -396,7 +398,7 @@ static int cdns_prep_msg(struct sdw_cdns *cdns, struct sdw_msg *msg, int *cmd)
return 0;
}
static enum sdw_command_response
enum sdw_command_response
cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
......@@ -422,8 +424,9 @@ cdns_xfer_msg(struct sdw_bus *bus, struct sdw_msg *msg)
exit:
return ret;
}
EXPORT_SYMBOL(cdns_xfer_msg);
static enum sdw_command_response
enum sdw_command_response
cdns_xfer_msg_defer(struct sdw_bus *bus,
struct sdw_msg *msg, struct sdw_defer *defer)
{
......@@ -443,8 +446,9 @@ cdns_xfer_msg_defer(struct sdw_bus *bus,
return _cdns_xfer_msg(cdns, msg, cmd, 0, msg->len, true);
}
EXPORT_SYMBOL(cdns_xfer_msg_defer);
static enum sdw_command_response
enum sdw_command_response
cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
......@@ -456,6 +460,7 @@ cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num)
return cdns_program_scp_addr(cdns, &msg);
}
EXPORT_SYMBOL(cdns_reset_page_addr);
/*
* IRQ handling
......@@ -666,6 +671,120 @@ int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns)
}
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)
{
struct sdw_cdns_pdi *pdi;
int i;
if (!num)
return 0;
pdi = devm_kcalloc(cdns->dev, num, sizeof(*pdi), GFP_KERNEL);
if (!pdi)
return -ENOMEM;
for (i = 0; i < num; i++) {
pdi[i].num = i + pdi_offset;
pdi[i].assigned = false;
}
*stream = pdi;
return 0;
}
/**
* sdw_cdns_pdi_init() - PDI initialization routine
*
* @cdns: Cadence instance
* @config: Stream configurations
*/
int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
struct sdw_cdns_stream_config config)
{
struct sdw_cdns_streams *stream;
int offset, i, ret;
cdns->pcm.num_bd = config.pcm_bd;
cdns->pcm.num_in = config.pcm_in;
cdns->pcm.num_out = config.pcm_out;
cdns->pdm.num_bd = config.pdm_bd;
cdns->pdm.num_in = config.pdm_in;
cdns->pdm.num_out = config.pdm_out;
/* Allocate PDIs for PCMs */
stream = &cdns->pcm;
/* First two PDIs are reserved for bulk transfers */
stream->num_bd -= CDNS_PCM_PDI_OFFSET;
offset = CDNS_PCM_PDI_OFFSET;
ret = cdns_allocate_pdi(cdns, &stream->bd,
stream->num_bd, offset);
if (ret)
return ret;
offset += stream->num_bd;
ret = cdns_allocate_pdi(cdns, &stream->in,
stream->num_in, offset);
if (ret)
return ret;
offset += stream->num_in;
ret = cdns_allocate_pdi(cdns, &stream->out,
stream->num_out, offset);
if (ret)
return ret;
/* Update total number of PCM PDIs */
stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
cdns->num_ports = stream->num_pdi;
/* Allocate PDIs for PDMs */
stream = &cdns->pdm;
offset = CDNS_PDM_PDI_OFFSET;
ret = cdns_allocate_pdi(cdns, &stream->bd,
stream->num_bd, offset);
if (ret)
return ret;
offset += stream->num_bd;
ret = cdns_allocate_pdi(cdns, &stream->in,
stream->num_in, offset);
if (ret)
return ret;
offset += stream->num_in;
ret = cdns_allocate_pdi(cdns, &stream->out,
stream->num_out, offset);
if (ret)
return ret;
/* Update total number of PDM PDIs */
stream->num_pdi = stream->num_bd + stream->num_in + stream->num_out;
cdns->num_ports += stream->num_pdi;
cdns->ports = devm_kcalloc(cdns->dev, cdns->num_ports,
sizeof(*cdns->ports), GFP_KERNEL);
if (!cdns->ports) {
ret = -ENOMEM;
return ret;
}
for (i = 0; i < cdns->num_ports; i++) {
cdns->ports[i].assigned = false;
cdns->ports[i].num = i + 1; /* Port 0 reserved for bulk */
}
return 0;
}
EXPORT_SYMBOL(sdw_cdns_pdi_init);
/**
* sdw_cdns_init() - Cadence initialization
* @cdns: Cadence instance
......@@ -727,13 +846,133 @@ int sdw_cdns_init(struct sdw_cdns *cdns)
}
EXPORT_SYMBOL(sdw_cdns_init);
struct sdw_master_ops sdw_cdns_master_ops = {
.read_prop = sdw_master_read_prop,
.xfer_msg = cdns_xfer_msg,
.xfer_msg_defer = cdns_xfer_msg_defer,
.reset_page_addr = cdns_reset_page_addr,
int cdns_bus_conf(struct sdw_bus *bus, struct sdw_bus_params *params)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
int mcp_clkctrl_off, mcp_clkctrl;
int divider;
if (!params->curr_dr_freq) {
dev_err(cdns->dev, "NULL curr_dr_freq");
return -EINVAL;
}
divider = (params->max_dr_freq / params->curr_dr_freq) - 1;
if (params->next_bank)
mcp_clkctrl_off = CDNS_MCP_CLK_CTRL1;
else
mcp_clkctrl_off = CDNS_MCP_CLK_CTRL0;
mcp_clkctrl = cdns_readl(cdns, mcp_clkctrl_off);
mcp_clkctrl |= divider;
cdns_writel(cdns, mcp_clkctrl_off, mcp_clkctrl);
return 0;
}
EXPORT_SYMBOL(cdns_bus_conf);
static int cdns_port_params(struct sdw_bus *bus,
struct sdw_port_params *p_params, unsigned int bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
int dpn_config = 0, dpn_config_off;
if (bank)
dpn_config_off = CDNS_DPN_B1_CONFIG(p_params->num);
else
dpn_config_off = CDNS_DPN_B0_CONFIG(p_params->num);
dpn_config = cdns_readl(cdns, dpn_config_off);
dpn_config |= ((p_params->bps - 1) <<
SDW_REG_SHIFT(CDNS_DPN_CONFIG_WL));
dpn_config |= (p_params->flow_mode <<
SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_FLOW));
dpn_config |= (p_params->data_mode <<
SDW_REG_SHIFT(CDNS_DPN_CONFIG_PORT_DAT));
cdns_writel(cdns, dpn_config_off, dpn_config);
return 0;
}
static int cdns_transport_params(struct sdw_bus *bus,
struct sdw_transport_params *t_params,
enum sdw_reg_bank bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
int dpn_offsetctrl = 0, dpn_offsetctrl_off;
int dpn_config = 0, dpn_config_off;
int dpn_hctrl = 0, dpn_hctrl_off;
int num = t_params->port_num;
int dpn_samplectrl_off;
/*
* Note: Only full data port is supported on the Master side for
* both PCM and PDM ports.
*/
if (bank) {
dpn_config_off = CDNS_DPN_B1_CONFIG(num);
dpn_samplectrl_off = CDNS_DPN_B1_SAMPLE_CTRL(num);
dpn_hctrl_off = CDNS_DPN_B1_HCTRL(num);
dpn_offsetctrl_off = CDNS_DPN_B1_OFFSET_CTRL(num);
} else {
dpn_config_off = CDNS_DPN_B0_CONFIG(num);
dpn_samplectrl_off = CDNS_DPN_B0_SAMPLE_CTRL(num);
dpn_hctrl_off = CDNS_DPN_B0_HCTRL(num);
dpn_offsetctrl_off = CDNS_DPN_B0_OFFSET_CTRL(num);
}
dpn_config = cdns_readl(cdns, dpn_config_off);
dpn_config |= (t_params->blk_grp_ctrl <<
SDW_REG_SHIFT(CDNS_DPN_CONFIG_BGC));
dpn_config |= (t_params->blk_pkg_mode <<
SDW_REG_SHIFT(CDNS_DPN_CONFIG_BPM));
cdns_writel(cdns, dpn_config_off, dpn_config);
dpn_offsetctrl |= (t_params->offset1 <<
SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_1));
dpn_offsetctrl |= (t_params->offset2 <<
SDW_REG_SHIFT(CDNS_DPN_OFFSET_CTRL_2));
cdns_writel(cdns, dpn_offsetctrl_off, dpn_offsetctrl);
dpn_hctrl |= (t_params->hstart <<
SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTART));
dpn_hctrl |= (t_params->hstop << SDW_REG_SHIFT(CDNS_DPN_HCTRL_HSTOP));
dpn_hctrl |= (t_params->lane_ctrl <<
SDW_REG_SHIFT(CDNS_DPN_HCTRL_LCTRL));
cdns_writel(cdns, dpn_hctrl_off, dpn_hctrl);
cdns_writel(cdns, dpn_samplectrl_off, (t_params->sample_interval - 1));
return 0;
}
static int cdns_port_enable(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch, unsigned int bank)
{
struct sdw_cdns *cdns = bus_to_cdns(bus);
int dpn_chnen_off, ch_mask;
if (bank)
dpn_chnen_off = CDNS_DPN_B1_CH_EN(enable_ch->port_num);
else
dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->port_num);
ch_mask = enable_ch->ch_mask * enable_ch->enable;
cdns_writel(cdns, dpn_chnen_off, ch_mask);
return 0;
}
static const struct sdw_master_port_ops cdns_port_ops = {
.dpn_set_port_params = cdns_port_params,
.dpn_set_port_transport_params = cdns_transport_params,
.dpn_port_enable_ch = cdns_port_enable,
};
EXPORT_SYMBOL(sdw_cdns_master_ops);
/**
* sdw_cdns_probe() - Cadence probe routine
......@@ -742,10 +981,204 @@ EXPORT_SYMBOL(sdw_cdns_master_ops);
int sdw_cdns_probe(struct sdw_cdns *cdns)
{
init_completion(&cdns->tx_complete);
cdns->bus.port_ops = &cdns_port_ops;
return 0;
}
EXPORT_SYMBOL(sdw_cdns_probe);
int cdns_set_sdw_stream(struct snd_soc_dai *dai,
void *stream, bool pcm, int direction)
{
struct sdw_cdns *cdns = snd_soc_dai_get_drvdata(dai);
struct sdw_cdns_dma_data *dma;
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma)
return -ENOMEM;
if (pcm)
dma->stream_type = SDW_STREAM_PCM;
else
dma->stream_type = SDW_STREAM_PDM;
dma->bus = &cdns->bus;
dma->link_id = cdns->instance;
dma->stream = stream;
if (direction == SNDRV_PCM_STREAM_PLAYBACK)
dai->playback_dma_data = dma;
else
dai->capture_dma_data = dma;
return 0;
}
EXPORT_SYMBOL(cdns_set_sdw_stream);
/**
* cdns_find_pdi() - Find a free PDI
*
* @cdns: Cadence instance
* @num: Number of PDIs
* @pdi: PDI instances
*
* Find and return a free PDI for a given PDI array
*/
static struct sdw_cdns_pdi *cdns_find_pdi(struct sdw_cdns *cdns,
unsigned int num, struct sdw_cdns_pdi *pdi)
{
int i;
for (i = 0; i < num; i++) {
if (pdi[i].assigned == true)
continue;
pdi[i].assigned = true;
return &pdi[i];
}
return NULL;
}
/**
* sdw_cdns_config_stream: Configure a stream
*
* @cdns: Cadence instance
* @port: Cadence data port
* @ch: Channel count
* @dir: Data direction
* @pdi: PDI to be used
*/
void sdw_cdns_config_stream(struct sdw_cdns *cdns,
struct sdw_cdns_port *port,
u32 ch, u32 dir, struct sdw_cdns_pdi *pdi)
{
u32 offset, val = 0;
if (dir == SDW_DATA_DIR_RX)
val = CDNS_PORTCTRL_DIRN;
offset = CDNS_PORTCTRL + port->num * CDNS_PORT_OFFSET;
cdns_updatel(cdns, offset, CDNS_PORTCTRL_DIRN, val);
val = port->num;
val |= ((1 << ch) - 1) << SDW_REG_SHIFT(CDNS_PDI_CONFIG_CHANNEL);
cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
}
EXPORT_SYMBOL(sdw_cdns_config_stream);
/**
* cdns_get_num_pdi() - Get number of PDIs required
*
* @cdns: Cadence instance
* @pdi: PDI to be used
* @num: Number of PDIs
* @ch_count: Channel count
*/
static int cdns_get_num_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi *pdi,
unsigned int num, u32 ch_count)
{
int i, pdis = 0;
for (i = 0; i < num; i++) {
if (pdi[i].assigned == true)
continue;
if (pdi[i].ch_count < ch_count)
ch_count -= pdi[i].ch_count;
else
ch_count = 0;
pdis++;
if (!ch_count)
break;
}
if (ch_count)
return 0;
return pdis;
}
/**
* sdw_cdns_get_stream() - Get stream information
*
* @cdns: Cadence instance
* @stream: Stream to be allocated
* @ch: Channel count
* @dir: Data direction
*/
int sdw_cdns_get_stream(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
u32 ch, u32 dir)
{
int pdis = 0;
if (dir == SDW_DATA_DIR_RX)
pdis = cdns_get_num_pdi(cdns, stream->in, stream->num_in, ch);
else
pdis = cdns_get_num_pdi(cdns, stream->out, stream->num_out, ch);
/* check if we found PDI, else find in bi-directional */
if (!pdis)
pdis = cdns_get_num_pdi(cdns, stream->bd, stream->num_bd, ch);
return pdis;
}
EXPORT_SYMBOL(sdw_cdns_get_stream);
/**
* sdw_cdns_alloc_stream() - Allocate a stream
*
* @cdns: Cadence instance
* @stream: Stream to be allocated
* @port: Cadence data port
* @ch: Channel count
* @dir: Data direction
*/
int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
struct sdw_cdns_port *port, u32 ch, u32 dir)
{
struct sdw_cdns_pdi *pdi = NULL;
if (dir == SDW_DATA_DIR_RX)
pdi = cdns_find_pdi(cdns, stream->num_in, stream->in);
else
pdi = cdns_find_pdi(cdns, stream->num_out, stream->out);
/* check if we found a PDI, else find in bi-directional */
if (!pdi)
pdi = cdns_find_pdi(cdns, stream->num_bd, stream->bd);
if (!pdi)
return -EIO;
port->pdi = pdi;
pdi->l_ch_num = 0;
pdi->h_ch_num = ch - 1;
pdi->dir = dir;
pdi->ch_count = ch;
return 0;
}
EXPORT_SYMBOL(sdw_cdns_alloc_stream);
void sdw_cdns_shutdown(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)
return;
snd_soc_dai_set_dma_data(dai, substream, NULL);
kfree(dma);
}
EXPORT_SYMBOL(sdw_cdns_shutdown);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("Cadence Soundwire Library");
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-17 Intel Corporation.
#include <sound/soc.h>
#ifndef __SDW_CADENCE_H
#define __SDW_CADENCE_H
/**
* struct sdw_cdns_pdi: PDI (Physical Data Interface) instance
*
* @assigned: pdi assigned
* @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, PDM or PCM
*/
struct sdw_cdns_pdi {
bool assigned;
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_port: Cadence port structure
*
* @num: port number
* @assigned: port assigned
* @ch: channel count
* @direction: data port direction
* @pdi: pdi for this port
*/
struct sdw_cdns_port {
unsigned int num;
bool assigned;
unsigned int ch;
enum sdw_data_direction direction;
struct sdw_cdns_pdi *pdi;
};
/**
* 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
* @pdm_bd: number of bidirectional PDM streams supported
* @pdm_in: number of input PDM streams supported
* @pdm_out: number of output PDM streams supported
*/
struct sdw_cdns_stream_config {
unsigned int pcm_bd;
unsigned int pcm_in;
unsigned int pcm_out;
unsigned int pdm_bd;
unsigned int pdm_in;
unsigned int pdm_out;
};
/**
* struct sdw_cdns_dma_data: Cadence DMA data
*
* @name: SoundWire stream name
* @nr_ports: Number of ports
* @port: Ports
* @bus: Bus handle
* @stream_type: Stream type
* @link_id: Master link id
*/
struct sdw_cdns_dma_data {
char *name;
struct sdw_stream_runtime *stream;
int nr_ports;
struct sdw_cdns_port **port;
struct sdw_bus *bus;
enum sdw_stream_type stream_type;
int link_id;
};
/**
* struct sdw_cdns - Cadence driver context
* @dev: Linux device
......@@ -12,6 +119,10 @@
* @response_buf: SoundWire response buffer
* @tx_complete: Tx completion
* @defer: Defer pointer
* @ports: Data ports
* @num_ports: Total number of data ports
* @pcm: PCM streams
* @pdm: PDM streams
* @registers: Cadence registers
* @link_up: Link status
* @msg_count: Messages sent on bus
......@@ -25,6 +136,12 @@ struct sdw_cdns {
struct completion tx_complete;
struct sdw_defer *defer;
struct sdw_cdns_port *ports;
int num_ports;
struct sdw_cdns_streams pcm;
struct sdw_cdns_streams pdm;
void __iomem *registers;
bool link_up;
......@@ -42,7 +159,41 @@ 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);
int sdw_cdns_pdi_init(struct sdw_cdns *cdns,
struct sdw_cdns_stream_config config);
int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns);
int sdw_cdns_get_stream(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
u32 ch, u32 dir);
int sdw_cdns_alloc_stream(struct sdw_cdns *cdns,
struct sdw_cdns_streams *stream,
struct sdw_cdns_port *port, u32 ch, u32 dir);
void sdw_cdns_config_stream(struct sdw_cdns *cdns, struct sdw_cdns_port *port,
u32 ch, u32 dir, struct sdw_cdns_pdi *pdi);
void sdw_cdns_shutdown(struct snd_pcm_substream *substream,
struct snd_soc_dai *dai);
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);
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,
struct sdw_msg *msg, struct sdw_defer *defer);
enum sdw_command_response
cdns_reset_page_addr(struct sdw_bus *bus, unsigned int dev_num);
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, bool pcm, int direction);
#endif /* __SDW_CADENCE_H */
......@@ -9,6 +9,8 @@
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include <linux/soundwire/sdw_intel.h>
......@@ -85,6 +87,12 @@
#define SDW_ALH_STRMZCFG_DMAT GENMASK(7, 0)
#define SDW_ALH_STRMZCFG_CHN GENMASK(19, 16)
enum intel_pdi_type {
INTEL_PDI_IN = 0,
INTEL_PDI_OUT = 1,
INTEL_PDI_BD = 2,
};
struct sdw_intel {
struct sdw_cdns cdns;
int instance;
......@@ -234,6 +242,490 @@ static int intel_shim_init(struct sdw_intel *sdw)
return ret;
}
/*
* PDI routines
*/
static void intel_pdi_init(struct sdw_intel *sdw,
struct sdw_cdns_stream_config *config)
{
void __iomem *shim = sdw->res->shim;
unsigned int link_id = sdw->instance;
int pcm_cap, pdm_cap;
/* PCM Stream Capability */
pcm_cap = intel_readw(shim, SDW_SHIM_PCMSCAP(link_id));
config->pcm_bd = (pcm_cap & SDW_SHIM_PCMSCAP_BSS) >>
SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_BSS);
config->pcm_in = (pcm_cap & SDW_SHIM_PCMSCAP_ISS) >>
SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_ISS);
config->pcm_out = (pcm_cap & SDW_SHIM_PCMSCAP_OSS) >>
SDW_REG_SHIFT(SDW_SHIM_PCMSCAP_OSS);
/* PDM Stream Capability */
pdm_cap = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
config->pdm_bd = (pdm_cap & SDW_SHIM_PDMSCAP_BSS) >>
SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_BSS);
config->pdm_in = (pdm_cap & SDW_SHIM_PDMSCAP_ISS) >>
SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_ISS);
config->pdm_out = (pdm_cap & SDW_SHIM_PDMSCAP_OSS) >>
SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_OSS);
}
static int
intel_pdi_get_ch_cap(struct sdw_intel *sdw, unsigned int pdi_num, bool pcm)
{
void __iomem *shim = sdw->res->shim;
unsigned int link_id = sdw->instance;
int count;
if (pcm) {
count = intel_readw(shim, SDW_SHIM_PCMSYCHC(link_id, pdi_num));
} else {
count = intel_readw(shim, SDW_SHIM_PDMSCAP(link_id));
count = ((count & SDW_SHIM_PDMSCAP_CPSS) >>
SDW_REG_SHIFT(SDW_SHIM_PDMSCAP_CPSS));
}
/* zero based values for channel count in register */
count++;
return count;
}
static int intel_pdi_get_ch_update(struct sdw_intel *sdw,
struct sdw_cdns_pdi *pdi,
unsigned int num_pdi,
unsigned int *num_ch, bool pcm)
{
int i, ch_count = 0;
for (i = 0; i < num_pdi; i++) {
pdi->ch_count = intel_pdi_get_ch_cap(sdw, pdi->num, pcm);
ch_count += pdi->ch_count;
pdi++;
}
*num_ch = ch_count;
return 0;
}
static int intel_pdi_stream_ch_update(struct sdw_intel *sdw,
struct sdw_cdns_streams *stream, bool pcm)
{
intel_pdi_get_ch_update(sdw, stream->bd, stream->num_bd,
&stream->num_ch_bd, pcm);
intel_pdi_get_ch_update(sdw, stream->in, stream->num_in,
&stream->num_ch_in, pcm);
intel_pdi_get_ch_update(sdw, stream->out, stream->num_out,
&stream->num_ch_out, pcm);
return 0;
}
static int intel_pdi_ch_update(struct sdw_intel *sdw)
{
/* First update PCM streams followed by PDM streams */
intel_pdi_stream_ch_update(sdw, &sdw->cdns.pcm, true);
intel_pdi_stream_ch_update(sdw, &sdw->cdns.pdm, false);
return 0;
}
static void
intel_pdi_shim_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
void __iomem *shim = sdw->res->shim;
unsigned int link_id = sdw->instance;
int pdi_conf = 0;
pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
/*
* Program stream parameters to stream SHIM register
* This is applicable for PCM stream only.
*/
if (pdi->type != SDW_STREAM_PCM)
return;
if (pdi->dir == SDW_DATA_DIR_RX)
pdi_conf |= SDW_SHIM_PCMSYCM_DIR;
else
pdi_conf &= ~(SDW_SHIM_PCMSYCM_DIR);
pdi_conf |= (pdi->intel_alh_id <<
SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_STREAM));
pdi_conf |= (pdi->l_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_LCHN));
pdi_conf |= (pdi->h_ch_num << SDW_REG_SHIFT(SDW_SHIM_PCMSYCM_HCHN));
intel_writew(shim, SDW_SHIM_PCMSYCHM(link_id, pdi->num), pdi_conf);
}
static void
intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi)
{
void __iomem *alh = sdw->res->alh;
unsigned int link_id = sdw->instance;
unsigned int conf;
pdi->intel_alh_id = (link_id * 16) + pdi->num + 5;
/* Program Stream config ALH register */
conf = intel_readl(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id));
conf |= (SDW_ALH_STRMZCFG_DMAT_VAL <<
SDW_REG_SHIFT(SDW_ALH_STRMZCFG_DMAT));
conf |= ((pdi->ch_count - 1) <<
SDW_REG_SHIFT(SDW_ALH_STRMZCFG_CHN));
intel_writel(alh, SDW_ALH_STRMZCFG(pdi->intel_alh_id), conf);
}
static int intel_config_stream(struct sdw_intel *sdw,
struct snd_pcm_substream *substream,
struct snd_soc_dai *dai,
struct snd_pcm_hw_params *hw_params, int link_id)
{
if (sdw->res->ops && sdw->res->ops->config_stream)
return sdw->res->ops->config_stream(sdw->res->arg,
substream, dai, hw_params, link_id);
return -EIO;
}
/*
* DAI routines
*/
static struct sdw_cdns_port *intel_alloc_port(struct sdw_intel *sdw,
u32 ch, u32 dir, bool pcm)
{
struct sdw_cdns *cdns = &sdw->cdns;
struct sdw_cdns_port *port = NULL;
int i, ret = 0;
for (i = 0; i < cdns->num_ports; i++) {
if (cdns->ports[i].assigned == true)
continue;
port = &cdns->ports[i];
port->assigned = true;
port->direction = dir;
port->ch = ch;
break;
}
if (!port) {
dev_err(cdns->dev, "Unable to find a free port\n");
return NULL;
}
if (pcm) {
ret = sdw_cdns_alloc_stream(cdns, &cdns->pcm, port, ch, dir);
if (ret)
goto out;
intel_pdi_shim_configure(sdw, port->pdi);
sdw_cdns_config_stream(cdns, port, ch, dir, port->pdi);
intel_pdi_alh_configure(sdw, port->pdi);
} else {
ret = sdw_cdns_alloc_stream(cdns, &cdns->pdm, port, ch, dir);
}
out:
if (ret) {
port->assigned = false;
port = NULL;
}
return port;
}
static void intel_port_cleanup(struct sdw_cdns_dma_data *dma)
{
int i;
for (i = 0; i < dma->nr_ports; i++) {
if (dma->port[i]) {
dma->port[i]->pdi->assigned = false;
dma->port[i]->pdi = NULL;
dma->port[i]->assigned = false;
dma->port[i] = NULL;
}
}
}
static int intel_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
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;
struct sdw_stream_config sconfig;
struct sdw_port_config *pconfig;
int ret, i, ch, dir;
bool pcm = true;
dma = snd_soc_dai_get_dma_data(dai, substream);
if (!dma)
return -EIO;
ch = params_channels(params);
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
dir = SDW_DATA_DIR_RX;
else
dir = SDW_DATA_DIR_TX;
if (dma->stream_type == SDW_STREAM_PDM) {
/* TODO: Check whether PDM decimator is already in use */
dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pdm, ch, dir);
pcm = false;
} else {
dma->nr_ports = sdw_cdns_get_stream(cdns, &cdns->pcm, ch, dir);
}
if (!dma->nr_ports) {
dev_err(dai->dev, "ports/resources not available");
return -EINVAL;
}
dma->port = kcalloc(dma->nr_ports, sizeof(*dma->port), GFP_KERNEL);
if (!dma->port)
return -ENOMEM;
for (i = 0; i < dma->nr_ports; i++) {
dma->port[i] = intel_alloc_port(sdw, ch, dir, pcm);
if (!dma->port[i]) {
ret = -EINVAL;
goto port_error;
}
}
/* Inform DSP about PDI stream number */
for (i = 0; i < dma->nr_ports; i++) {
ret = intel_config_stream(sdw, substream, dai, params,
dma->port[i]->pdi->intel_alh_id);
if (ret)
goto port_error;
}
sconfig.direction = dir;
sconfig.ch_count = ch;
sconfig.frame_rate = params_rate(params);
sconfig.type = dma->stream_type;
if (dma->stream_type == SDW_STREAM_PDM) {
sconfig.frame_rate *= 50;
sconfig.bps = 1;
} else {
sconfig.bps = snd_pcm_format_width(params_format(params));
}
/* Port configuration */
pconfig = kcalloc(dma->nr_ports, sizeof(*pconfig), GFP_KERNEL);
if (!pconfig) {
ret = -ENOMEM;
goto port_error;
}
for (i = 0; i < dma->nr_ports; i++) {
pconfig[i].num = dma->port[i]->num;
pconfig[i].ch_mask = (1 << ch) - 1;
}
ret = sdw_stream_add_master(&cdns->bus, &sconfig,
pconfig, dma->nr_ports, dma->stream);
if (ret) {
dev_err(cdns->dev, "add master to stream failed:%d", ret);
goto stream_error;
}
kfree(pconfig);
return ret;
stream_error:
kfree(pconfig);
port_error:
intel_port_cleanup(dma);
kfree(dma->port);
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_cdns_dma_data *dma;
int ret;
dma = snd_soc_dai_get_dma_data(dai, substream);
if (!dma)
return -EIO;
ret = sdw_stream_remove_master(&cdns->bus, dma->stream);
if (ret < 0)
dev_err(dai->dev, "remove master from stream %s failed: %d",
dma->stream->name, ret);
intel_port_cleanup(dma);
kfree(dma->port);
return ret;
}
static int intel_pcm_set_sdw_stream(struct snd_soc_dai *dai,
void *stream, int direction)
{
return cdns_set_sdw_stream(dai, stream, true, direction);
}
static int intel_pdm_set_sdw_stream(struct snd_soc_dai *dai,
void *stream, int direction)
{
return cdns_set_sdw_stream(dai, stream, false, direction);
}
static struct snd_soc_dai_ops intel_pcm_dai_ops = {
.hw_params = intel_hw_params,
.hw_free = intel_hw_free,
.shutdown = sdw_cdns_shutdown,
.set_sdw_stream = intel_pcm_set_sdw_stream,
};
static struct snd_soc_dai_ops intel_pdm_dai_ops = {
.hw_params = intel_hw_params,
.hw_free = intel_hw_free,
.shutdown = sdw_cdns_shutdown,
.set_sdw_stream = intel_pdm_set_sdw_stream,
};
static const struct snd_soc_component_driver dai_component = {
.name = "soundwire",
};
static int intel_create_dai(struct sdw_cdns *cdns,
struct snd_soc_dai_driver *dais,
enum intel_pdi_type type,
u32 num, u32 off, u32 max_ch, bool pcm)
{
int i;
if (num == 0)
return 0;
/* TODO: Read supported rates/formats from hardware */
for (i = off; i < (off + num); i++) {
dais[i].name = kasprintf(GFP_KERNEL, "SDW%d Pin%d",
cdns->instance, i);
if (!dais[i].name)
return -ENOMEM;
if (type == INTEL_PDI_BD || type == INTEL_PDI_OUT) {
dais[i].playback.stream_name = kasprintf(GFP_KERNEL,
"SDW%d Tx%d",
cdns->instance, i);
if (!dais[i].playback.stream_name) {
kfree(dais[i].name);
return -ENOMEM;
}
dais[i].playback.channels_min = 1;
dais[i].playback.channels_max = max_ch;
dais[i].playback.rates = SNDRV_PCM_RATE_48000;
dais[i].playback.formats = SNDRV_PCM_FMTBIT_S16_LE;
}
if (type == INTEL_PDI_BD || type == INTEL_PDI_IN) {
dais[i].capture.stream_name = kasprintf(GFP_KERNEL,
"SDW%d Rx%d",
cdns->instance, i);
if (!dais[i].capture.stream_name) {
kfree(dais[i].name);
kfree(dais[i].playback.stream_name);
return -ENOMEM;
}
dais[i].playback.channels_min = 1;
dais[i].playback.channels_max = max_ch;
dais[i].capture.rates = SNDRV_PCM_RATE_48000;
dais[i].capture.formats = SNDRV_PCM_FMTBIT_S16_LE;
}
dais[i].id = SDW_DAI_ID_RANGE_START + i;
if (pcm)
dais[i].ops = &intel_pcm_dai_ops;
else
dais[i].ops = &intel_pdm_dai_ops;
}
return 0;
}
static int intel_register_dai(struct sdw_intel *sdw)
{
struct sdw_cdns *cdns = &sdw->cdns;
struct sdw_cdns_streams *stream;
struct snd_soc_dai_driver *dais;
int num_dai, ret, off = 0;
/* DAIs are created based on total number of PDIs supported */
num_dai = cdns->pcm.num_pdi + cdns->pdm.num_pdi;
dais = devm_kcalloc(cdns->dev, num_dai, sizeof(*dais), GFP_KERNEL);
if (!dais)
return -ENOMEM;
/* Create PCM DAIs */
stream = &cdns->pcm;
ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
stream->num_in, off, stream->num_ch_in, true);
if (ret)
return ret;
off += cdns->pcm.num_in;
ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT,
cdns->pcm.num_out, off, stream->num_ch_out, true);
if (ret)
return ret;
off += cdns->pcm.num_out;
ret = intel_create_dai(cdns, dais, INTEL_PDI_BD,
cdns->pcm.num_bd, off, stream->num_ch_bd, true);
if (ret)
return ret;
/* Create PDM DAIs */
stream = &cdns->pdm;
off += cdns->pcm.num_bd;
ret = intel_create_dai(cdns, dais, INTEL_PDI_IN,
cdns->pdm.num_in, off, stream->num_ch_in, false);
if (ret)
return ret;
off += cdns->pdm.num_in;
ret = intel_create_dai(cdns, dais, INTEL_PDI_OUT,
cdns->pdm.num_out, off, stream->num_ch_out, false);
if (ret)
return ret;
off += cdns->pdm.num_bd;
ret = intel_create_dai(cdns, dais, INTEL_PDI_BD,
cdns->pdm.num_bd, off, stream->num_ch_bd, false);
if (ret)
return ret;
return snd_soc_register_component(cdns->dev, &dai_component,
dais, num_dai);
}
static int intel_prop_read(struct sdw_bus *bus)
{
/* Initialize with default handler to read all DisCo properties */
......@@ -252,11 +744,20 @@ static int intel_prop_read(struct sdw_bus *bus)
return 0;
}
static struct sdw_master_ops sdw_intel_ops = {
.read_prop = sdw_master_read_prop,
.xfer_msg = cdns_xfer_msg,
.xfer_msg_defer = cdns_xfer_msg_defer,
.reset_page_addr = cdns_reset_page_addr,
.set_bus_conf = cdns_bus_conf,
};
/*
* probe and init
*/
static int intel_probe(struct platform_device *pdev)
{
struct sdw_cdns_stream_config config;
struct sdw_intel *sdw;
int ret;
......@@ -276,8 +777,11 @@ static int intel_probe(struct platform_device *pdev)
sdw_cdns_probe(&sdw->cdns);
/* Set property read ops */
sdw_cdns_master_ops.read_prop = intel_prop_read;
sdw->cdns.bus.ops = &sdw_cdns_master_ops;
sdw_intel_ops.read_prop = intel_prop_read;
sdw->cdns.bus.ops = &sdw_intel_ops;
sdw_intel_ops.read_prop = intel_prop_read;
sdw->cdns.bus.ops = &sdw_intel_ops;
platform_set_drvdata(pdev, sdw);
......@@ -296,9 +800,15 @@ static int intel_probe(struct platform_device *pdev)
goto err_init;
ret = sdw_cdns_enable_interrupt(&sdw->cdns);
/* Read the PDI config and initialize cadence PDI */
intel_pdi_init(sdw, &config);
ret = sdw_cdns_pdi_init(&sdw->cdns, config);
if (ret)
goto err_init;
intel_pdi_ch_update(sdw);
/* Acquire IRQ */
ret = request_threaded_irq(sdw->res->irq, sdw_cdns_irq,
sdw_cdns_thread, IRQF_SHARED, KBUILD_MODNAME,
......@@ -309,8 +819,18 @@ static int intel_probe(struct platform_device *pdev)
goto err_init;
}
/* Register DAIs */
ret = intel_register_dai(sdw);
if (ret) {
dev_err(sdw->cdns.dev, "DAI registration failed: %d", ret);
snd_soc_unregister_component(sdw->cdns.dev);
goto err_dai;
}
return 0;
err_dai:
free_irq(sdw->res->irq, sdw);
err_init:
sdw_delete_bus_master(&sdw->cdns.bus);
err_master_reg:
......@@ -324,6 +844,7 @@ static int intel_remove(struct platform_device *pdev)
sdw = platform_get_drvdata(pdev);
free_irq(sdw->res->irq, sdw);
snd_soc_unregister_component(sdw->cdns.dev);
sdw_delete_bus_master(&sdw->cdns.bus);
return 0;
......
......@@ -10,6 +10,8 @@
* @shim: Audio shim pointer
* @alh: ALH (Audio Link Hub) pointer
* @irq: Interrupt line
* @ops: Shim callback ops
* @arg: Shim callback ops argument
*
* This is set as pdata for each link instance.
*/
......@@ -18,6 +20,8 @@ struct sdw_intel_link_res {
void __iomem *shim;
void __iomem *alh;
int irq;
const struct sdw_intel_ops *ops;
void *arg;
};
#endif /* __SDW_INTEL_LOCAL_H */
......@@ -111,6 +111,9 @@ static struct sdw_intel_ctx
link->res.shim = res->mmio_base + SDW_SHIM_BASE;
link->res.alh = res->mmio_base + SDW_ALH_BASE;
link->res.ops = res->ops;
link->res.arg = res->arg;
memset(&pdevinfo, 0, sizeof(pdevinfo));
pdevinfo.parent = res->parent;
......
// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
// Copyright(c) 2015-18 Intel Corporation.
/*
* stream.c - SoundWire Bus stream operations.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>
#include <linux/soundwire/sdw_registers.h>
#include <linux/soundwire/sdw.h>
#include "bus.h"
/*
* Array of supported rows and columns as per MIPI SoundWire Specification 1.1
*
* The rows are arranged as per the array index value programmed
* in register. The index 15 has dummy value 0 in order to fill hole.
*/
int rows[SDW_FRAME_ROWS] = {48, 50, 60, 64, 75, 80, 125, 147,
96, 100, 120, 128, 150, 160, 250, 0,
192, 200, 240, 256, 72, 144, 90, 180};
int cols[SDW_FRAME_COLS] = {2, 4, 6, 8, 10, 12, 14, 16};
static int sdw_find_col_index(int col)
{
int i;
for (i = 0; i < SDW_FRAME_COLS; i++) {
if (cols[i] == col)
return i;
}
pr_warn("Requested column not found, selecting lowest column no: 2\n");
return 0;
}
static int sdw_find_row_index(int row)
{
int i;
for (i = 0; i < SDW_FRAME_ROWS; i++) {
if (rows[i] == row)
return i;
}
pr_warn("Requested row not found, selecting lowest row no: 48\n");
return 0;
}
static int _sdw_program_slave_port_params(struct sdw_bus *bus,
struct sdw_slave *slave,
struct sdw_transport_params *t_params,
enum sdw_dpn_type type)
{
u32 addr1, addr2, addr3, addr4;
int ret;
u16 wbuf;
if (bus->params.next_bank) {
addr1 = SDW_DPN_OFFSETCTRL2_B1(t_params->port_num);
addr2 = SDW_DPN_BLOCKCTRL3_B1(t_params->port_num);
addr3 = SDW_DPN_SAMPLECTRL2_B1(t_params->port_num);
addr4 = SDW_DPN_HCTRL_B1(t_params->port_num);
} else {
addr1 = SDW_DPN_OFFSETCTRL2_B0(t_params->port_num);
addr2 = SDW_DPN_BLOCKCTRL3_B0(t_params->port_num);
addr3 = SDW_DPN_SAMPLECTRL2_B0(t_params->port_num);
addr4 = SDW_DPN_HCTRL_B0(t_params->port_num);
}
/* Program DPN_OffsetCtrl2 registers */
ret = sdw_write(slave, addr1, t_params->offset2);
if (ret < 0) {
dev_err(bus->dev, "DPN_OffsetCtrl2 register write failed");
return ret;
}
/* Program DPN_BlockCtrl3 register */
ret = sdw_write(slave, addr2, t_params->blk_pkg_mode);
if (ret < 0) {
dev_err(bus->dev, "DPN_BlockCtrl3 register write failed");
return ret;
}
/*
* Data ports are FULL, SIMPLE and REDUCED. This function handles
* FULL and REDUCED only and and beyond this point only FULL is
* handled, so bail out if we are not FULL data port type
*/
if (type != SDW_DPN_FULL)
return ret;
/* Program DPN_SampleCtrl2 register */
wbuf = (t_params->sample_interval - 1);
wbuf &= SDW_DPN_SAMPLECTRL_HIGH;
wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
ret = sdw_write(slave, addr3, wbuf);
if (ret < 0) {
dev_err(bus->dev, "DPN_SampleCtrl2 register write failed");
return ret;
}
/* Program DPN_HCtrl register */
wbuf = t_params->hstart;
wbuf <<= SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART);
wbuf |= t_params->hstop;
ret = sdw_write(slave, addr4, wbuf);
if (ret < 0)
dev_err(bus->dev, "DPN_HCtrl register write failed");
return ret;
}
static int sdw_program_slave_port_params(struct sdw_bus *bus,
struct sdw_slave_runtime *s_rt,
struct sdw_port_runtime *p_rt)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
struct sdw_port_params *p_params = &p_rt->port_params;
struct sdw_slave_prop *slave_prop = &s_rt->slave->prop;
u32 addr1, addr2, addr3, addr4, addr5, addr6;
struct sdw_dpn_prop *dpn_prop;
int ret;
u8 wbuf;
dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
s_rt->direction,
t_params->port_num);
if (!dpn_prop)
return -EINVAL;
addr1 = SDW_DPN_PORTCTRL(t_params->port_num);
addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num);
if (bus->params.next_bank) {
addr3 = SDW_DPN_SAMPLECTRL1_B1(t_params->port_num);
addr4 = SDW_DPN_OFFSETCTRL1_B1(t_params->port_num);
addr5 = SDW_DPN_BLOCKCTRL2_B1(t_params->port_num);
addr6 = SDW_DPN_LANECTRL_B1(t_params->port_num);
} else {
addr3 = SDW_DPN_SAMPLECTRL1_B0(t_params->port_num);
addr4 = SDW_DPN_OFFSETCTRL1_B0(t_params->port_num);
addr5 = SDW_DPN_BLOCKCTRL2_B0(t_params->port_num);
addr6 = SDW_DPN_LANECTRL_B0(t_params->port_num);
}
/* Program DPN_PortCtrl register */
wbuf = p_params->data_mode << SDW_REG_SHIFT(SDW_DPN_PORTCTRL_DATAMODE);
wbuf |= p_params->flow_mode;
ret = sdw_update(s_rt->slave, addr1, 0xF, wbuf);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_PortCtrl register write failed for port %d",
t_params->port_num);
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",
t_params->port_num);
return ret;
}
/* Program DPN_SampleCtrl1 register */
wbuf = (t_params->sample_interval - 1) & SDW_DPN_SAMPLECTRL_LOW;
ret = sdw_write(s_rt->slave, addr3, wbuf);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_SampleCtrl1 register write failed for port %d",
t_params->port_num);
return ret;
}
/* Program DPN_OffsetCtrl1 registers */
ret = sdw_write(s_rt->slave, addr4, t_params->offset1);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_OffsetCtrl1 register write failed for port %d",
t_params->port_num);
return ret;
}
/* Program DPN_BlockCtrl2 register*/
if (t_params->blk_grp_ctrl_valid) {
ret = sdw_write(s_rt->slave, addr5, t_params->blk_grp_ctrl);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_BlockCtrl2 reg write failed for port %d",
t_params->port_num);
return ret;
}
}
/* program DPN_LaneCtrl register */
if (slave_prop->lane_control_support) {
ret = sdw_write(s_rt->slave, addr6, t_params->lane_ctrl);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"DPN_LaneCtrl register write failed for port %d",
t_params->port_num);
return ret;
}
}
if (dpn_prop->type != SDW_DPN_SIMPLE) {
ret = _sdw_program_slave_port_params(bus, s_rt->slave,
t_params, dpn_prop->type);
if (ret < 0)
dev_err(&s_rt->slave->dev,
"Transport reg write failed for port: %d",
t_params->port_num);
}
return ret;
}
static int sdw_program_master_port_params(struct sdw_bus *bus,
struct sdw_port_runtime *p_rt)
{
int ret;
/*
* we need to set transport and port parameters for the port.
* Transport parameters refers to the smaple interval, offsets and
* hstart/stop etc of the data. Port parameters refers to word
* length, flow mode etc of the port
*/
ret = bus->port_ops->dpn_set_port_transport_params(bus,
&p_rt->transport_params,
bus->params.next_bank);
if (ret < 0)
return ret;
return bus->port_ops->dpn_set_port_params(bus,
&p_rt->port_params,
bus->params.next_bank);
}
/**
* sdw_program_port_params() - Programs transport parameters of Master(s)
* and Slave(s)
*
* @m_rt: Master stream runtime
*/
static int sdw_program_port_params(struct sdw_master_runtime *m_rt)
{
struct sdw_slave_runtime *s_rt = NULL;
struct sdw_bus *bus = m_rt->bus;
struct sdw_port_runtime *p_rt;
int ret = 0;
/* Program transport & port parameters for Slave(s) */
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) {
ret = sdw_program_slave_port_params(bus, s_rt, p_rt);
if (ret < 0)
return ret;
}
}
/* Program transport & port parameters for Master(s) */
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
ret = sdw_program_master_port_params(bus, p_rt);
if (ret < 0)
return ret;
}
return 0;
}
/**
* sdw_enable_disable_slave_ports: Enable/disable slave data port
*
* @bus: bus instance
* @s_rt: slave runtime
* @p_rt: port runtime
* @en: enable or disable operation
*
* This function only sets the enable/disable bits in the relevant bank, the
* actual enable/disable is done with a bank switch
*/
static int sdw_enable_disable_slave_ports(struct sdw_bus *bus,
struct sdw_slave_runtime *s_rt,
struct sdw_port_runtime *p_rt, bool en)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
u32 addr;
int ret;
if (bus->params.next_bank)
addr = SDW_DPN_CHANNELEN_B1(p_rt->num);
else
addr = SDW_DPN_CHANNELEN_B0(p_rt->num);
/*
* Since bus doesn't support sharing a port across two streams,
* it is safe to reset this register
*/
if (en)
ret = sdw_update(s_rt->slave, addr, 0xFF, p_rt->ch_mask);
else
ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
if (ret < 0)
dev_err(&s_rt->slave->dev,
"Slave chn_en reg write failed:%d port:%d",
ret, t_params->port_num);
return ret;
}
static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
struct sdw_port_runtime *p_rt, bool en)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
struct sdw_bus *bus = m_rt->bus;
struct sdw_enable_ch enable_ch;
int ret = 0;
enable_ch.port_num = p_rt->num;
enable_ch.ch_mask = p_rt->ch_mask;
enable_ch.enable = en;
/* Perform Master port channel(s) enable/disable */
if (bus->port_ops->dpn_port_enable_ch) {
ret = bus->port_ops->dpn_port_enable_ch(bus,
&enable_ch, bus->params.next_bank);
if (ret < 0) {
dev_err(bus->dev,
"Master chn_en write failed:%d port:%d",
ret, t_params->port_num);
return ret;
}
} else {
dev_err(bus->dev,
"dpn_port_enable_ch not supported, %s failed\n",
en ? "enable" : "disable");
return -EINVAL;
}
return 0;
}
/**
* sdw_enable_disable_ports() - Enable/disable port(s) for Master and
* Slave(s)
*
* @m_rt: Master stream runtime
* @en: mode (enable/disable)
*/
static int sdw_enable_disable_ports(struct sdw_master_runtime *m_rt, bool en)
{
struct sdw_port_runtime *s_port, *m_port;
struct sdw_slave_runtime *s_rt = NULL;
int ret = 0;
/* Enable/Disable Slave port(s) */
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
list_for_each_entry(s_port, &s_rt->port_list, port_node) {
ret = sdw_enable_disable_slave_ports(m_rt->bus, s_rt,
s_port, en);
if (ret < 0)
return ret;
}
}
/* Enable/Disable Master port(s) */
list_for_each_entry(m_port, &m_rt->port_list, port_node) {
ret = sdw_enable_disable_master_ports(m_rt, m_port, en);
if (ret < 0)
return ret;
}
return 0;
}
static int sdw_do_port_prep(struct sdw_slave_runtime *s_rt,
struct sdw_prepare_ch prep_ch, enum sdw_port_prep_ops cmd)
{
const struct sdw_slave_ops *ops = s_rt->slave->ops;
int ret;
if (ops->port_prep) {
ret = ops->port_prep(s_rt->slave, &prep_ch, cmd);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"Slave Port Prep cmd %d failed: %d", cmd, ret);
return ret;
}
}
return 0;
}
static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
struct sdw_slave_runtime *s_rt,
struct sdw_port_runtime *p_rt, bool prep)
{
struct completion *port_ready = NULL;
struct sdw_dpn_prop *dpn_prop;
struct sdw_prepare_ch prep_ch;
unsigned int time_left;
bool intr = false;
int ret = 0, val;
u32 addr;
prep_ch.num = p_rt->num;
prep_ch.ch_mask = p_rt->ch_mask;
dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
s_rt->direction,
prep_ch.num);
if (!dpn_prop) {
dev_err(bus->dev,
"Slave Port:%d properties not found", prep_ch.num);
return -EINVAL;
}
prep_ch.prepare = prep;
prep_ch.bank = bus->params.next_bank;
if (dpn_prop->device_interrupts || !dpn_prop->simple_ch_prep_sm)
intr = true;
/*
* Enable interrupt before Port prepare.
* For Port de-prepare, it is assumed that port
* was prepared earlier
*/
if (prep && intr) {
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
dpn_prop->device_interrupts);
if (ret < 0)
return ret;
}
/* Inform slave about the impending port prepare */
sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_PRE_PREP);
/* Prepare Slave port implementing CP_SM */
if (!dpn_prop->simple_ch_prep_sm) {
addr = SDW_DPN_PREPARECTRL(p_rt->num);
if (prep)
ret = sdw_update(s_rt->slave, addr,
0xFF, p_rt->ch_mask);
else
ret = sdw_update(s_rt->slave, addr, 0xFF, 0x0);
if (ret < 0) {
dev_err(&s_rt->slave->dev,
"Slave prep_ctrl reg write failed");
return ret;
}
/* Wait for completion on port ready */
port_ready = &s_rt->slave->port_ready[prep_ch.num];
time_left = wait_for_completion_timeout(port_ready,
msecs_to_jiffies(dpn_prop->ch_prep_timeout));
val = sdw_read(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num));
val &= p_rt->ch_mask;
if (!time_left || val) {
dev_err(&s_rt->slave->dev,
"Chn prep failed for port:%d", prep_ch.num);
return -ETIMEDOUT;
}
}
/* Inform slaves about ports prepared */
sdw_do_port_prep(s_rt, prep_ch, SDW_OPS_PORT_POST_PREP);
/* Disable interrupt after Port de-prepare */
if (!prep && intr)
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
dpn_prop->device_interrupts);
return ret;
}
static int sdw_prep_deprep_master_ports(struct sdw_master_runtime *m_rt,
struct sdw_port_runtime *p_rt, bool prep)
{
struct sdw_transport_params *t_params = &p_rt->transport_params;
struct sdw_bus *bus = m_rt->bus;
const struct sdw_master_port_ops *ops = bus->port_ops;
struct sdw_prepare_ch prep_ch;
int ret = 0;
prep_ch.num = p_rt->num;
prep_ch.ch_mask = p_rt->ch_mask;
prep_ch.prepare = prep; /* Prepare/De-prepare */
prep_ch.bank = bus->params.next_bank;
/* Pre-prepare/Pre-deprepare port(s) */
if (ops->dpn_port_prep) {
ret = ops->dpn_port_prep(bus, &prep_ch);
if (ret < 0) {
dev_err(bus->dev, "Port prepare failed for port:%d",
t_params->port_num);
return ret;
}
}
return ret;
}
/**
* sdw_prep_deprep_ports() - Prepare/De-prepare port(s) for Master(s) and
* Slave(s)
*
* @m_rt: Master runtime handle
* @prep: Prepare or De-prepare
*/
static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep)
{
struct sdw_slave_runtime *s_rt = NULL;
struct sdw_port_runtime *p_rt;
int ret = 0;
/* Prepare/De-prepare Slave port(s) */
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) {
ret = sdw_prep_deprep_slave_ports(m_rt->bus, s_rt,
p_rt, prep);
if (ret < 0)
return ret;
}
}
/* Prepare/De-prepare Master port(s) */
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
ret = sdw_prep_deprep_master_ports(m_rt, p_rt, prep);
if (ret < 0)
return ret;
}
return ret;
}
/**
* sdw_notify_config() - Notify bus configuration
*
* @m_rt: Master runtime handle
*
* This function notifies the Master(s) and Slave(s) of the
* new bus configuration.
*/
static int sdw_notify_config(struct sdw_master_runtime *m_rt)
{
struct sdw_slave_runtime *s_rt;
struct sdw_bus *bus = m_rt->bus;
struct sdw_slave *slave;
int ret = 0;
if (bus->ops->set_bus_conf) {
ret = bus->ops->set_bus_conf(bus, &bus->params);
if (ret < 0)
return ret;
}
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
slave = s_rt->slave;
if (slave->ops->bus_config) {
ret = slave->ops->bus_config(slave, &bus->params);
if (ret < 0)
dev_err(bus->dev, "Notify Slave: %d failed",
slave->dev_num);
return ret;
}
}
return ret;
}
/**
* sdw_program_params() - Program transport and port parameters for Master(s)
* and Slave(s)
*
* @bus: SDW bus instance
*/
static int sdw_program_params(struct sdw_bus *bus)
{
struct sdw_master_runtime *m_rt = NULL;
int ret = 0;
list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
ret = sdw_program_port_params(m_rt);
if (ret < 0) {
dev_err(bus->dev,
"Program transport params failed: %d", ret);
return ret;
}
ret = sdw_notify_config(m_rt);
if (ret < 0) {
dev_err(bus->dev, "Notify bus config failed: %d", ret);
return ret;
}
/* Enable port(s) on alternate bank for all active streams */
if (m_rt->stream->state != SDW_STREAM_ENABLED)
continue;
ret = sdw_enable_disable_ports(m_rt, true);
if (ret < 0) {
dev_err(bus->dev, "Enable channel failed: %d", ret);
return ret;
}
}
return ret;
}
static int sdw_bank_switch(struct sdw_bus *bus)
{
int col_index, row_index;
struct sdw_msg *wr_msg;
u8 *wbuf = NULL;
int ret = 0;
u16 addr;
wr_msg = kzalloc(sizeof(*wr_msg), GFP_KERNEL);
if (!wr_msg)
return -ENOMEM;
wbuf = kzalloc(sizeof(*wbuf), GFP_KERNEL);
if (!wbuf) {
ret = -ENOMEM;
goto error_1;
}
/* Get row and column index to program register */
col_index = sdw_find_col_index(bus->params.col);
row_index = sdw_find_row_index(bus->params.row);
wbuf[0] = col_index | (row_index << 3);
if (bus->params.next_bank)
addr = SDW_SCP_FRAMECTRL_B1;
else
addr = SDW_SCP_FRAMECTRL_B0;
sdw_fill_msg(wr_msg, NULL, addr, 1, SDW_BROADCAST_DEV_NUM,
SDW_MSG_FLAG_WRITE, wbuf);
wr_msg->ssp_sync = true;
ret = sdw_transfer(bus, wr_msg);
if (ret < 0) {
dev_err(bus->dev, "Slave frame_ctrl reg write failed");
goto error;
}
kfree(wr_msg);
kfree(wbuf);
bus->defer_msg.msg = NULL;
bus->params.curr_bank = !bus->params.curr_bank;
bus->params.next_bank = !bus->params.next_bank;
return 0;
error:
kfree(wbuf);
error_1:
kfree(wr_msg);
return ret;
}
static int do_bank_switch(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
const struct sdw_master_ops *ops;
struct sdw_bus *bus = m_rt->bus;
int ret = 0;
ops = bus->ops;
/* Pre-bank switch */
if (ops->pre_bank_switch) {
ret = ops->pre_bank_switch(bus);
if (ret < 0) {
dev_err(bus->dev, "Pre bank switch op failed: %d", ret);
return ret;
}
}
/* Bank switch */
ret = sdw_bank_switch(bus);
if (ret < 0) {
dev_err(bus->dev, "Bank switch failed: %d", ret);
return ret;
}
/* Post-bank switch */
if (ops->post_bank_switch) {
ret = ops->post_bank_switch(bus);
if (ret < 0) {
dev_err(bus->dev,
"Post bank switch op failed: %d", ret);
}
}
return ret;
}
/**
* sdw_release_stream() - Free the assigned stream runtime
*
* @stream: SoundWire stream runtime
*
* sdw_release_stream should be called only once per stream
*/
void sdw_release_stream(struct sdw_stream_runtime *stream)
{
kfree(stream);
}
EXPORT_SYMBOL(sdw_release_stream);
/**
* sdw_alloc_stream() - Allocate and return stream runtime
*
* @stream_name: SoundWire stream name
*
* Allocates a SoundWire stream runtime instance.
* sdw_alloc_stream should be called only once per stream. Typically
* invoked from ALSA/ASoC machine/platform driver.
*/
struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name)
{
struct sdw_stream_runtime *stream;
stream = kzalloc(sizeof(*stream), GFP_KERNEL);
if (!stream)
return NULL;
stream->name = stream_name;
stream->state = SDW_STREAM_ALLOCATED;
return stream;
}
EXPORT_SYMBOL(sdw_alloc_stream);
/**
* sdw_alloc_master_rt() - Allocates and initialize Master runtime handle
*
* @bus: SDW bus instance
* @stream_config: Stream configuration
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held.
*/
static struct sdw_master_runtime
*sdw_alloc_master_rt(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt;
m_rt = stream->m_rt;
/*
* check if Master is already allocated (as a result of Slave adding
* it first), if so skip allocation and go to configure
*/
if (m_rt)
goto stream_config;
m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
if (!m_rt)
return NULL;
/* Initialization of Master runtime handle */
INIT_LIST_HEAD(&m_rt->port_list);
INIT_LIST_HEAD(&m_rt->slave_rt_list);
stream->m_rt = m_rt;
list_add_tail(&m_rt->bus_node, &bus->m_rt_list);
stream_config:
m_rt->ch_count = stream_config->ch_count;
m_rt->bus = bus;
m_rt->stream = stream;
m_rt->direction = stream_config->direction;
return m_rt;
}
/**
* sdw_alloc_slave_rt() - Allocate and initialize Slave runtime handle.
*
* @slave: Slave handle
* @stream_config: Stream configuration
* @stream: Stream runtime handle
*
* This function is to be called with bus_lock held.
*/
static struct sdw_slave_runtime
*sdw_alloc_slave_rt(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt = NULL;
s_rt = kzalloc(sizeof(*s_rt), GFP_KERNEL);
if (!s_rt)
return NULL;
INIT_LIST_HEAD(&s_rt->port_list);
s_rt->ch_count = stream_config->ch_count;
s_rt->direction = stream_config->direction;
s_rt->slave = slave;
return s_rt;
}
static void sdw_master_port_release(struct sdw_bus *bus,
struct sdw_master_runtime *m_rt)
{
struct sdw_port_runtime *p_rt, *_p_rt;
list_for_each_entry_safe(p_rt, _p_rt,
&m_rt->port_list, port_node) {
list_del(&p_rt->port_node);
kfree(p_rt);
}
}
static void sdw_slave_port_release(struct sdw_bus *bus,
struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
struct sdw_port_runtime *p_rt, *_p_rt;
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_slave_runtime *s_rt;
list_for_each_entry(s_rt, &m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave != slave)
continue;
list_for_each_entry_safe(p_rt, _p_rt,
&s_rt->port_list, port_node) {
list_del(&p_rt->port_node);
kfree(p_rt);
}
}
}
/**
* sdw_release_slave_stream() - Free Slave(s) runtime handle
*
* @slave: Slave handle.
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held.
*/
static void sdw_release_slave_stream(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt, *_s_rt;
struct sdw_master_runtime *m_rt = stream->m_rt;
/* Retrieve Slave runtime handle */
list_for_each_entry_safe(s_rt, _s_rt,
&m_rt->slave_rt_list, m_rt_node) {
if (s_rt->slave == slave) {
list_del(&s_rt->m_rt_node);
kfree(s_rt);
return;
}
}
}
/**
* sdw_release_master_stream() - Free Master runtime handle
*
* @stream: Stream runtime handle.
*
* This function is to be called with bus_lock held
* It frees the Master runtime handle and associated Slave(s) runtime
* handle. If this is called first then sdw_release_slave_stream() will have
* no effect as Slave(s) runtime handle would already be freed up.
*/
static void sdw_release_master_stream(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_slave_runtime *s_rt, *_s_rt;
list_for_each_entry_safe(s_rt, _s_rt,
&m_rt->slave_rt_list, m_rt_node)
sdw_stream_remove_slave(s_rt->slave, stream);
list_del(&m_rt->bus_node);
}
/**
* sdw_stream_remove_master() - Remove master from sdw_stream
*
* @bus: SDW Bus instance
* @stream: SoundWire stream
*
* This removes and frees port_rt and master_rt from a stream
*/
int sdw_stream_remove_master(struct sdw_bus *bus,
struct sdw_stream_runtime *stream)
{
mutex_lock(&bus->bus_lock);
sdw_release_master_stream(stream);
sdw_master_port_release(bus, stream->m_rt);
stream->state = SDW_STREAM_RELEASED;
kfree(stream->m_rt);
stream->m_rt = NULL;
mutex_unlock(&bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(sdw_stream_remove_master);
/**
* sdw_stream_remove_slave() - Remove slave from sdw_stream
*
* @slave: SDW Slave instance
* @stream: SoundWire stream
*
* This removes and frees port_rt and slave_rt from a stream
*/
int sdw_stream_remove_slave(struct sdw_slave *slave,
struct sdw_stream_runtime *stream)
{
mutex_lock(&slave->bus->bus_lock);
sdw_slave_port_release(slave->bus, slave, stream);
sdw_release_slave_stream(slave, stream);
mutex_unlock(&slave->bus->bus_lock);
return 0;
}
EXPORT_SYMBOL(sdw_stream_remove_slave);
/**
* sdw_config_stream() - Configure the allocated stream
*
* @dev: SDW device
* @stream: SoundWire stream
* @stream_config: Stream configuration for audio stream
* @is_slave: is API called from Slave or Master
*
* This function is to be called with bus_lock held.
*/
static int sdw_config_stream(struct device *dev,
struct sdw_stream_runtime *stream,
struct sdw_stream_config *stream_config, bool is_slave)
{
/*
* Update the stream rate, channel and bps based on data
* source. For more than one data source (multilink),
* match the rate, bps, stream type and increment number of channels.
*
* If rate/bps is zero, it means the values are not set, so skip
* comparison and allow the value to be set and stored in stream
*/
if (stream->params.rate &&
stream->params.rate != stream_config->frame_rate) {
dev_err(dev, "rate not matching, stream:%s", stream->name);
return -EINVAL;
}
if (stream->params.bps &&
stream->params.bps != stream_config->bps) {
dev_err(dev, "bps not matching, stream:%s", stream->name);
return -EINVAL;
}
stream->type = stream_config->type;
stream->params.rate = stream_config->frame_rate;
stream->params.bps = stream_config->bps;
/* TODO: Update this check during Device-device support */
if (is_slave)
stream->params.ch_count += stream_config->ch_count;
return 0;
}
static int sdw_is_valid_port_range(struct device *dev,
struct sdw_port_runtime *p_rt)
{
if (!SDW_VALID_PORT_RANGE(p_rt->num)) {
dev_err(dev,
"SoundWire: Invalid port number :%d", p_rt->num);
return -EINVAL;
}
return 0;
}
static struct sdw_port_runtime *sdw_port_alloc(struct device *dev,
struct sdw_port_config *port_config,
int port_index)
{
struct sdw_port_runtime *p_rt;
p_rt = kzalloc(sizeof(*p_rt), GFP_KERNEL);
if (!p_rt)
return NULL;
p_rt->ch_mask = port_config[port_index].ch_mask;
p_rt->num = port_config[port_index].num;
return p_rt;
}
static int sdw_master_port_config(struct sdw_bus *bus,
struct sdw_master_runtime *m_rt,
struct sdw_port_config *port_config,
unsigned int num_ports)
{
struct sdw_port_runtime *p_rt;
int i;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_ports; i++) {
p_rt = sdw_port_alloc(bus->dev, port_config, i);
if (!p_rt)
return -ENOMEM;
/*
* TODO: Check port capabilities for requested
* configuration (audio mode support)
*/
list_add_tail(&p_rt->port_node, &m_rt->port_list);
}
return 0;
}
static int sdw_slave_port_config(struct sdw_slave *slave,
struct sdw_slave_runtime *s_rt,
struct sdw_port_config *port_config,
unsigned int num_config)
{
struct sdw_port_runtime *p_rt;
int i, ret;
/* Iterate for number of ports to perform initialization */
for (i = 0; i < num_config; i++) {
p_rt = sdw_port_alloc(&slave->dev, port_config, i);
if (!p_rt)
return -ENOMEM;
/*
* TODO: Check valid port range as defined by DisCo/
* slave
*/
ret = sdw_is_valid_port_range(&slave->dev, p_rt);
if (ret < 0) {
kfree(p_rt);
return ret;
}
/*
* TODO: Check port capabilities for requested
* configuration (audio mode support)
*/
list_add_tail(&p_rt->port_node, &s_rt->port_list);
}
return 0;
}
/**
* sdw_stream_add_master() - Allocate and add master runtime to a stream
*
* @bus: SDW Bus instance
* @stream_config: Stream configuration for audio stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
* @stream: SoundWire stream
*/
int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = NULL;
int ret;
mutex_lock(&bus->bus_lock);
m_rt = sdw_alloc_master_rt(bus, stream_config, stream);
if (!m_rt) {
dev_err(bus->dev,
"Master runtime config failed for stream:%s",
stream->name);
ret = -ENOMEM;
goto error;
}
ret = sdw_config_stream(bus->dev, stream, stream_config, false);
if (ret)
goto stream_error;
ret = sdw_master_port_config(bus, m_rt, port_config, num_ports);
if (ret)
goto stream_error;
stream->state = SDW_STREAM_CONFIGURED;
stream_error:
sdw_release_master_stream(stream);
error:
mutex_unlock(&bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_stream_add_master);
/**
* sdw_stream_add_slave() - Allocate and add master/slave runtime to a stream
*
* @slave: SDW Slave instance
* @stream_config: Stream configuration for audio stream
* @stream: SoundWire stream
* @port_config: Port configuration for audio stream
* @num_ports: Number of ports
*/
int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream)
{
struct sdw_slave_runtime *s_rt;
struct sdw_master_runtime *m_rt;
int ret;
mutex_lock(&slave->bus->bus_lock);
/*
* If this API is invoked by Slave first then m_rt is not valid.
* So, allocate m_rt and add Slave to it.
*/
m_rt = sdw_alloc_master_rt(slave->bus, stream_config, stream);
if (!m_rt) {
dev_err(&slave->dev,
"alloc master runtime failed for stream:%s",
stream->name);
ret = -ENOMEM;
goto error;
}
s_rt = sdw_alloc_slave_rt(slave, stream_config, stream);
if (!s_rt) {
dev_err(&slave->dev,
"Slave runtime config failed for stream:%s",
stream->name);
ret = -ENOMEM;
goto stream_error;
}
ret = sdw_config_stream(&slave->dev, stream, stream_config, true);
if (ret)
goto stream_error;
list_add_tail(&s_rt->m_rt_node, &m_rt->slave_rt_list);
ret = sdw_slave_port_config(slave, s_rt, port_config, num_ports);
if (ret)
goto stream_error;
stream->state = SDW_STREAM_CONFIGURED;
goto error;
stream_error:
/*
* we hit error so cleanup the stream, release all Slave(s) and
* Master runtime
*/
sdw_release_master_stream(stream);
error:
mutex_unlock(&slave->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_stream_add_slave);
/**
* sdw_get_slave_dpn_prop() - Get Slave port capabilities
*
* @slave: Slave handle
* @direction: Data direction.
* @port_num: Port number
*/
struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
enum sdw_data_direction direction,
unsigned int port_num)
{
struct sdw_dpn_prop *dpn_prop;
u8 num_ports;
int i;
if (direction == SDW_DATA_DIR_TX) {
num_ports = hweight32(slave->prop.source_ports);
dpn_prop = slave->prop.src_dpn_prop;
} else {
num_ports = hweight32(slave->prop.sink_ports);
dpn_prop = slave->prop.sink_dpn_prop;
}
for (i = 0; i < num_ports; i++) {
dpn_prop = &dpn_prop[i];
if (dpn_prop->num == port_num)
return &dpn_prop[i];
}
return NULL;
}
static int _sdw_prepare_stream(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_bus *bus = m_rt->bus;
struct sdw_master_prop *prop = NULL;
struct sdw_bus_params params;
int ret;
prop = &bus->prop;
memcpy(&params, &bus->params, sizeof(params));
/* TODO: Support Asynchronous mode */
if ((prop->max_freq % stream->params.rate) != 0) {
dev_err(bus->dev, "Async mode not supported");
return -EINVAL;
}
/* Increment cumulative bus bandwidth */
/* TODO: Update this during Device-Device support */
bus->params.bandwidth += m_rt->stream->params.rate *
m_rt->ch_count * m_rt->stream->params.bps;
/* Program params */
ret = sdw_program_params(bus);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d", ret);
goto restore_params;
}
ret = do_bank_switch(stream);
if (ret < 0) {
dev_err(bus->dev, "Bank switch failed: %d", ret);
goto restore_params;
}
/* Prepare port(s) on the new clock configuration */
ret = sdw_prep_deprep_ports(m_rt, true);
if (ret < 0) {
dev_err(bus->dev, "Prepare port(s) failed ret = %d",
ret);
return ret;
}
stream->state = SDW_STREAM_PREPARED;
return ret;
restore_params:
memcpy(&bus->params, &params, sizeof(params));
return ret;
}
/**
* sdw_prepare_stream() - Prepare SoundWire stream
*
* @stream: Soundwire stream
*
* Documentation/soundwire/stream.txt explains this API in detail
*/
int sdw_prepare_stream(struct sdw_stream_runtime *stream)
{
int ret = 0;
if (!stream) {
pr_err("SoundWire: Handle not found for stream");
return -EINVAL;
}
mutex_lock(&stream->m_rt->bus->bus_lock);
ret = _sdw_prepare_stream(stream);
if (ret < 0)
pr_err("Prepare for stream:%s failed: %d", stream->name, ret);
mutex_unlock(&stream->m_rt->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_prepare_stream);
static int _sdw_enable_stream(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_bus *bus = m_rt->bus;
int ret;
/* Program params */
ret = sdw_program_params(bus);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d", ret);
return ret;
}
/* Enable port(s) */
ret = sdw_enable_disable_ports(m_rt, true);
if (ret < 0) {
dev_err(bus->dev, "Enable port(s) failed ret: %d", ret);
return ret;
}
ret = do_bank_switch(stream);
if (ret < 0) {
dev_err(bus->dev, "Bank switch failed: %d", ret);
return ret;
}
stream->state = SDW_STREAM_ENABLED;
return 0;
}
/**
* sdw_enable_stream() - Enable SoundWire stream
*
* @stream: Soundwire stream
*
* Documentation/soundwire/stream.txt explains this API in detail
*/
int sdw_enable_stream(struct sdw_stream_runtime *stream)
{
int ret = 0;
if (!stream) {
pr_err("SoundWire: Handle not found for stream");
return -EINVAL;
}
mutex_lock(&stream->m_rt->bus->bus_lock);
ret = _sdw_enable_stream(stream);
if (ret < 0)
pr_err("Enable for stream:%s failed: %d", stream->name, ret);
mutex_unlock(&stream->m_rt->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_enable_stream);
static int _sdw_disable_stream(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_bus *bus = m_rt->bus;
int ret;
/* Disable port(s) */
ret = sdw_enable_disable_ports(m_rt, false);
if (ret < 0) {
dev_err(bus->dev, "Disable port(s) failed: %d", ret);
return ret;
}
stream->state = SDW_STREAM_DISABLED;
/* Program params */
ret = sdw_program_params(bus);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d", ret);
return ret;
}
return do_bank_switch(stream);
}
/**
* sdw_disable_stream() - Disable SoundWire stream
*
* @stream: Soundwire stream
*
* Documentation/soundwire/stream.txt explains this API in detail
*/
int sdw_disable_stream(struct sdw_stream_runtime *stream)
{
int ret = 0;
if (!stream) {
pr_err("SoundWire: Handle not found for stream");
return -EINVAL;
}
mutex_lock(&stream->m_rt->bus->bus_lock);
ret = _sdw_disable_stream(stream);
if (ret < 0)
pr_err("Disable for stream:%s failed: %d", stream->name, ret);
mutex_unlock(&stream->m_rt->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_disable_stream);
static int _sdw_deprepare_stream(struct sdw_stream_runtime *stream)
{
struct sdw_master_runtime *m_rt = stream->m_rt;
struct sdw_bus *bus = m_rt->bus;
int ret = 0;
/* De-prepare port(s) */
ret = sdw_prep_deprep_ports(m_rt, false);
if (ret < 0) {
dev_err(bus->dev, "De-prepare port(s) failed: %d", ret);
return ret;
}
stream->state = SDW_STREAM_DEPREPARED;
/* TODO: Update this during Device-Device support */
bus->params.bandwidth -= m_rt->stream->params.rate *
m_rt->ch_count * m_rt->stream->params.bps;
/* Program params */
ret = sdw_program_params(bus);
if (ret < 0) {
dev_err(bus->dev, "Program params failed: %d", ret);
return ret;
}
return do_bank_switch(stream);
}
/**
* sdw_deprepare_stream() - Deprepare SoundWire stream
*
* @stream: Soundwire stream
*
* Documentation/soundwire/stream.txt explains this API in detail
*/
int sdw_deprepare_stream(struct sdw_stream_runtime *stream)
{
int ret = 0;
if (!stream) {
pr_err("SoundWire: Handle not found for stream");
return -EINVAL;
}
mutex_lock(&stream->m_rt->bus->bus_lock);
ret = _sdw_deprepare_stream(stream);
if (ret < 0)
pr_err("De-prepare for stream:%d failed: %d", ret, ret);
mutex_unlock(&stream->m_rt->bus->bus_lock);
return ret;
}
EXPORT_SYMBOL(sdw_deprepare_stream);
......@@ -23,9 +23,24 @@ struct sdw_slave;
#define SDW_MASTER_DEV_NUM 14
#define SDW_NUM_DEV_ID_REGISTERS 6
/* frame shape defines */
/*
* Note: The maximum row define in SoundWire spec 1.1 is 23. In order to
* fill hole with 0, one more dummy entry is added
*/
#define SDW_FRAME_ROWS 24
#define SDW_FRAME_COLS 8
#define SDW_FRAME_ROW_COLS (SDW_FRAME_ROWS * SDW_FRAME_COLS)
#define SDW_FRAME_CTRL_BITS 48
#define SDW_MAX_DEVICES 11
#define SDW_VALID_PORT_RANGE(n) (n <= 14 && n >= 1)
#define SDW_DAI_ID_RANGE_START 100
#define SDW_DAI_ID_RANGE_END 200
/**
* enum sdw_slave_status - Slave status
* @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus.
......@@ -61,6 +76,30 @@ enum sdw_command_response {
SDW_CMD_FAIL_OTHER = 4,
};
/**
* enum sdw_stream_type: data stream type
*
* @SDW_STREAM_PCM: PCM data stream
* @SDW_STREAM_PDM: PDM data stream
*
* spec doesn't define this, but is used in implementation
*/
enum sdw_stream_type {
SDW_STREAM_PCM = 0,
SDW_STREAM_PDM = 1,
};
/**
* enum sdw_data_direction: Data direction
*
* @SDW_DATA_DIR_RX: Data into Port
* @SDW_DATA_DIR_TX: Data out of Port
*/
enum sdw_data_direction {
SDW_DATA_DIR_RX = 0,
SDW_DATA_DIR_TX = 1,
};
/*
* SDW properties, defined in MIPI DisCo spec v1.0
*/
......@@ -341,11 +380,92 @@ struct sdw_slave_intr_status {
};
/**
* struct sdw_slave_ops - Slave driver callback ops
* sdw_reg_bank - SoundWire register banks
* @SDW_BANK0: Soundwire register bank 0
* @SDW_BANK1: Soundwire register bank 1
*/
enum sdw_reg_bank {
SDW_BANK0,
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
*
* @num: Port number
* @ch_mask: Active channel mask
* @prepare: Prepare (true) /de-prepare (false) channel
* @bank: Register bank, which bank Slave/Master driver should program for
* implementation defined registers. This is always updated to next_bank
* value read from bus params.
*
*/
struct sdw_prepare_ch {
unsigned int num;
unsigned int ch_mask;
bool prepare;
unsigned int bank;
};
/**
* enum sdw_port_prep_ops: Prepare operations for Data Port
*
* @SDW_OPS_PORT_PRE_PREP: Pre prepare operation for the Port
* @SDW_OPS_PORT_PREP: Prepare operation for the Port
* @SDW_OPS_PORT_POST_PREP: Post prepare operation for the Port
*/
enum sdw_port_prep_ops {
SDW_OPS_PORT_PRE_PREP = 0,
SDW_OPS_PORT_PREP = 1,
SDW_OPS_PORT_POST_PREP = 2,
};
/**
* struct sdw_bus_params: Structure holding bus configuration
*
* @curr_bank: Current bank in use (BANK0/BANK1)
* @next_bank: Next bank to use (BANK0/BANK1). next_bank will always be
* set to !curr_bank
* @max_dr_freq: Maximum double rate clock frequency supported, in Hz
* @curr_dr_freq: Current double rate clock frequency, in Hz
* @bandwidth: Current bandwidth
* @col: Active columns
* @row: Active rows
*/
struct sdw_bus_params {
enum sdw_reg_bank curr_bank;
enum sdw_reg_bank next_bank;
unsigned int max_dr_freq;
unsigned int curr_dr_freq;
unsigned int bandwidth;
unsigned int col;
unsigned int row;
};
/**
* struct sdw_slave_ops: Slave driver callback ops
*
* @read_prop: Read Slave properties
* @interrupt_callback: Device interrupt notification (invoked in thread
* context)
* @update_status: Update Slave status
* @bus_config: Update the bus config for Slave
* @port_prep: Prepare the port with parameters
*/
struct sdw_slave_ops {
int (*read_prop)(struct sdw_slave *sdw);
......@@ -353,6 +473,11 @@ struct sdw_slave_ops {
struct sdw_slave_intr_status *status);
int (*update_status)(struct sdw_slave *slave,
enum sdw_slave_status status);
int (*bus_config)(struct sdw_slave *slave,
struct sdw_bus_params *params);
int (*port_prep)(struct sdw_slave *slave,
struct sdw_prepare_ch *prepare_ch,
enum sdw_port_prep_ops pre_ops);
};
/**
......@@ -406,6 +531,93 @@ int sdw_handle_slave_status(struct sdw_bus *bus,
* SDW master structures and APIs
*/
/**
* struct sdw_port_params: Data Port parameters
*
* @num: Port number
* @bps: Word length of the Port
* @flow_mode: Port Data flow mode
* @data_mode: Test modes or normal mode
*
* This is used to program the Data Port based on Data Port stream
* parameters.
*/
struct sdw_port_params {
unsigned int num;
unsigned int bps;
unsigned int flow_mode;
unsigned int data_mode;
};
/**
* struct sdw_transport_params: Data Port Transport Parameters
*
* @blk_grp_ctrl_valid: Port implements block group control
* @num: Port number
* @blk_grp_ctrl: Block group control value
* @sample_interval: Sample interval
* @offset1: Blockoffset of the payload data
* @offset2: Blockoffset of the payload data
* @hstart: Horizontal start of the payload data
* @hstop: Horizontal stop of the payload data
* @blk_pkg_mode: Block per channel or block per port
* @lane_ctrl: Data lane Port uses for Data transfer. Currently only single
* data lane is supported in bus
*
* This is used to program the Data Port based on Data Port transport
* parameters. All these parameters are banked and can be modified
* during a bank switch without any artifacts in audio stream.
*/
struct sdw_transport_params {
bool blk_grp_ctrl_valid;
unsigned int port_num;
unsigned int blk_grp_ctrl;
unsigned int sample_interval;
unsigned int offset1;
unsigned int offset2;
unsigned int hstart;
unsigned int hstop;
unsigned int blk_pkg_mode;
unsigned int lane_ctrl;
};
/**
* struct sdw_enable_ch: Enable/disable Data Port channel
*
* @num: Port number
* @ch_mask: Active channel mask
* @enable: Enable (true) /disable (false) channel
*/
struct sdw_enable_ch {
unsigned int port_num;
unsigned int ch_mask;
bool enable;
};
/**
* struct sdw_master_port_ops: Callback functions from bus to Master
* driver to set Master Data ports.
*
* @dpn_set_port_params: Set the Port parameters for the Master Port.
* Mandatory callback
* @dpn_set_port_transport_params: Set transport parameters for the Master
* Port. Mandatory callback
* @dpn_port_prep: Port prepare operations for the Master Data Port.
* @dpn_port_enable_ch: Enable the channels of Master Port.
*/
struct sdw_master_port_ops {
int (*dpn_set_port_params)(struct sdw_bus *bus,
struct sdw_port_params *port_params,
unsigned int bank);
int (*dpn_set_port_transport_params)(struct sdw_bus *bus,
struct sdw_transport_params *transport_params,
enum sdw_reg_bank bank);
int (*dpn_port_prep)(struct sdw_bus *bus,
struct sdw_prepare_ch *prepare_ch);
int (*dpn_port_enable_ch)(struct sdw_bus *bus,
struct sdw_enable_ch *enable_ch, unsigned int bank);
};
struct sdw_msg;
/**
......@@ -426,6 +638,9 @@ struct sdw_defer {
* @xfer_msg: Transfer message callback
* @xfer_msg_defer: Defer version of transfer message callback
* @reset_page_addr: Reset the SCP page address registers
* @set_bus_conf: Set the bus configuration
* @pre_bank_switch: Callback for pre bank switch
* @post_bank_switch: Callback for post bank switch
*/
struct sdw_master_ops {
int (*read_prop)(struct sdw_bus *bus);
......@@ -437,6 +652,11 @@ struct sdw_master_ops {
struct sdw_defer *defer);
enum sdw_command_response (*reset_page_addr)
(struct sdw_bus *bus, unsigned int dev_num);
int (*set_bus_conf)(struct sdw_bus *bus,
struct sdw_bus_params *params);
int (*pre_bank_switch)(struct sdw_bus *bus);
int (*post_bank_switch)(struct sdw_bus *bus);
};
/**
......@@ -449,9 +669,15 @@ struct sdw_master_ops {
* @bus_lock: bus lock
* @msg_lock: message lock
* @ops: Master callback ops
* @port_ops: Master port callback ops
* @params: Current bus parameters
* @prop: Master properties
* @m_rt_list: List of Master instance of all stream(s) running on Bus. This
* is used to compute and program bus bandwidth, clock, frame shape,
* transport and port parameters
* @defer_msg: Defer message
* @clk_stop_timeout: Clock stop timeout computed
* @bank_switch_timeout: Bank switch timeout computed
*/
struct sdw_bus {
struct device *dev;
......@@ -461,14 +687,118 @@ struct sdw_bus {
struct mutex bus_lock;
struct mutex msg_lock;
const struct sdw_master_ops *ops;
const struct sdw_master_port_ops *port_ops;
struct sdw_bus_params params;
struct sdw_master_prop prop;
struct list_head m_rt_list;
struct sdw_defer defer_msg;
unsigned int clk_stop_timeout;
u32 bank_switch_timeout;
};
int sdw_add_bus_master(struct sdw_bus *bus);
void sdw_delete_bus_master(struct sdw_bus *bus);
/**
* sdw_port_config: Master or Slave Port configuration
*
* @num: Port number
* @ch_mask: channels mask for port
*/
struct sdw_port_config {
unsigned int num;
unsigned int ch_mask;
};
/**
* sdw_stream_config: Master or Slave stream configuration
*
* @frame_rate: Audio frame rate of the stream, in Hz
* @ch_count: Channel count of the stream
* @bps: Number of bits per audio sample
* @direction: Data direction
* @type: Stream type PCM or PDM
*/
struct sdw_stream_config {
unsigned int frame_rate;
unsigned int ch_count;
unsigned int bps;
enum sdw_data_direction direction;
enum sdw_stream_type type;
};
/**
* sdw_stream_state: Stream states
*
* @SDW_STREAM_ALLOCATED: New stream allocated.
* @SDW_STREAM_CONFIGURED: Stream configured
* @SDW_STREAM_PREPARED: Stream prepared
* @SDW_STREAM_ENABLED: Stream enabled
* @SDW_STREAM_DISABLED: Stream disabled
* @SDW_STREAM_DEPREPARED: Stream de-prepared
* @SDW_STREAM_RELEASED: Stream released
*/
enum sdw_stream_state {
SDW_STREAM_ALLOCATED = 0,
SDW_STREAM_CONFIGURED = 1,
SDW_STREAM_PREPARED = 2,
SDW_STREAM_ENABLED = 3,
SDW_STREAM_DISABLED = 4,
SDW_STREAM_DEPREPARED = 5,
SDW_STREAM_RELEASED = 6,
};
/**
* sdw_stream_params: Stream parameters
*
* @rate: Sampling frequency, in Hz
* @ch_count: Number of channels
* @bps: bits per channel sample
*/
struct sdw_stream_params {
unsigned int rate;
unsigned int ch_count;
unsigned int bps;
};
/**
* sdw_stream_runtime: Runtime stream parameters
*
* @name: SoundWire stream name
* @params: Stream parameters
* @state: Current state of the stream
* @type: Stream type PCM or PDM
* @m_rt: Master runtime
*/
struct sdw_stream_runtime {
char *name;
struct sdw_stream_params params;
enum sdw_stream_state state;
enum sdw_stream_type type;
struct sdw_master_runtime *m_rt;
};
struct sdw_stream_runtime *sdw_alloc_stream(char *stream_name);
void sdw_release_stream(struct sdw_stream_runtime *stream);
int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream);
int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_port_config *port_config,
unsigned int num_ports,
struct sdw_stream_runtime *stream);
int sdw_stream_remove_master(struct sdw_bus *bus,
struct sdw_stream_runtime *stream);
int sdw_stream_remove_slave(struct sdw_slave *slave,
struct sdw_stream_runtime *stream);
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);
/* messaging and data APIs */
int sdw_read(struct sdw_slave *slave, u32 addr);
......
......@@ -4,18 +4,32 @@
#ifndef __SDW_INTEL_H
#define __SDW_INTEL_H
/**
* struct sdw_intel_ops: Intel audio driver callback ops
*
* @config_stream: configure the stream with the hw_params
*/
struct sdw_intel_ops {
int (*config_stream)(void *arg, void *substream,
void *dai, void *hw_params, int stream_num);
};
/**
* struct sdw_intel_res - Soundwire Intel resource structure
* @mmio_base: mmio base of SoundWire registers
* @irq: interrupt number
* @handle: ACPI parent handle
* @parent: parent device
* @ops: callback ops
* @arg: callback arg
*/
struct sdw_intel_res {
void __iomem *mmio_base;
int irq;
acpi_handle handle;
struct device *parent;
const struct sdw_intel_ops *ops;
void *arg;
};
void *sdw_intel_init(acpi_handle *parent_handle, struct sdw_intel_res *res);
......
......@@ -170,6 +170,8 @@ struct snd_soc_dai_ops {
unsigned int rx_num, unsigned int *rx_slot);
int (*set_tristate)(struct snd_soc_dai *dai, int tristate);
int (*set_sdw_stream)(struct snd_soc_dai *dai,
void *stream, int direction);
/*
* DAI digital mute - optional.
* Called by soc-core to minimise any pops.
......@@ -358,4 +360,25 @@ static inline void *snd_soc_dai_get_drvdata(struct snd_soc_dai *dai)
return dev_get_drvdata(dai->dev);
}
/**
* snd_soc_dai_set_sdw_stream() - Configures a DAI for SDW stream operation
* @dai: DAI
* @stream: STREAM
* @direction: Stream direction(Playback/Capture)
* SoundWire subsystem doesn't have a notion of direction and we reuse
* the ASoC stream direction to configure sink/source ports.
* Playback maps to source ports and Capture for sink ports.
*
* This should be invoked with NULL to clear the stream set previously.
* Returns 0 on success, a negative error code otherwise.
*/
static inline int snd_soc_dai_set_sdw_stream(struct snd_soc_dai *dai,
void *stream, int direction)
{
if (dai->driver->ops->set_sdw_stream)
return dai->driver->ops->set_sdw_stream(dai, stream, direction);
else
return -ENOTSUPP;
}
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment