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 -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJa98rIAAoJEHwUBw8lI4NHrU4P/2h6FLtl1RETg28dcUiGUXj4 ALgMej7IbkRXaqEClfA6sU9qe9NCi1UrWD4bEnwetjgMki8/WeJP1pSgoRwZT/YA 38lle3KoGaptBRf2uZe9hfiKKyvp3oI9XPNHEsk6Qqw0qSSUr8t6k5FAWBiupkiV s8R/dYDW/qSEmoWODodFFuJFYr1Xok5L95vt4dyCg4FXglr8Sym8Sk6DmjzcqEy/ w4YsWKvg62RbOZ4hbuZcwY3NunIvvMAx0+A3xweZaLobpCwJ0KB3ApYZeOybF5px ODDRNuh76scgz+UMBr796vfvzPAztbUUwcCmXVJrZkWC98OCSOUXfQSZOKnIRmFL AZICxsSPOmip++O3C221bKeI65ldXiPHBFzzmzAHOI5IyMfZREu3bbE69x+cwTUi 1UKrQb0uQRPCQJtaR9hMc4CadRA/SwhOIk42G5aNJ0R4CRpVpVhixuXUHv9+Huaj AHSYr3BKCiAW5FshcDcfl9PUvmgULgO66Nk5YURSEdaY39E+1NElHSeKxbbSYRqc SNr+gAUKP9Pqfpa8ToOUvb7yg4X72Tw4U9tUbj0i18vb7robkJD4BwlIikUpyBK/ w8Ew/6WqatmHvX8GpQPRIuE3IbbGKYJquQjTF9tRwzuJBzWFr3JYRU3dZ8g1q+I7 o9amLP9RcMVwD+qYFKtA =KZwI -----END PGP SIGNATURE----- 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
This commit is contained in:
Коммит
176c2572cd
|
@ -0,0 +1,65 @@
|
|||
========================
|
||||
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
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
=================
|
||||
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
|
||||
| |
|
||||
+ +
|
|
@ -0,0 +1,372 @@
|
|||
=========================
|
||||
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");
|
||||
|
|
|
@ -1,9 +1,116 @@
|
|||
// 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;
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче