[alsa-devel] [PATCH v2 00/13] soundwire: Add stream support
This series adds support in SoundWire subsystem for: - Documentation for stream support - stream management - data port management - DAI ops in cadence and Intel drivers - ASoC API to propagate SDW stream
Updates in v2: - Make ASoC API inlined - Make stream states as states and not action - Update the direction enum - Fix some typos and comment updates
Sanyog Kale (7): soundwire: Add more documentation soundwire: Add support for SoundWire stream management soundwire: Add support for port management soundwire: Add Master and Slave port programming soundwire: Add helpers for ports operations soundwire: Add bank switch routine soundwire: Add stream configuration APIs
Shreyas NC (2): ASoC: Add SoundWire stream programming interface soundwire: Remove cdns_master_ops
Vinod Koul (4): soundwire: cdns: Add port routines soundwire: cdns: Add stream routines soundwire: intel: Add stream initialization soundwire: intel: Add audio DAI ops
.../driver-api/soundwire/error_handling.rst | 65 + Documentation/driver-api/soundwire/index.rst | 3 + Documentation/driver-api/soundwire/locking.rst | 106 ++ Documentation/driver-api/soundwire/stream.rst | 368 +++++ drivers/soundwire/Kconfig | 2 +- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 43 + drivers/soundwire/bus.h | 72 + drivers/soundwire/cadence_master.c | 449 +++++- drivers/soundwire/cadence_master.h | 151 ++ drivers/soundwire/intel.c | 528 ++++++- drivers/soundwire/intel.h | 4 + drivers/soundwire/intel_init.c | 3 + drivers/soundwire/stream.c | 1549 ++++++++++++++++++++ include/linux/soundwire/sdw.h | 332 ++++- include/linux/soundwire/sdw_intel.h | 14 + include/sound/soc-dai.h | 21 + 17 files changed, 3698 insertions(+), 14 deletions(-) create mode 100644 Documentation/driver-api/soundwire/error_handling.rst create mode 100644 Documentation/driver-api/soundwire/locking.rst create mode 100644 Documentation/driver-api/soundwire/stream.rst create mode 100644 drivers/soundwire/stream.c
From: Sanyog Kale sanyog.r.kale@intel.com
This adds documentation for error handling, locking and streams.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- .../driver-api/soundwire/error_handling.rst | 65 ++++ Documentation/driver-api/soundwire/index.rst | 3 + Documentation/driver-api/soundwire/locking.rst | 106 ++++++ Documentation/driver-api/soundwire/stream.rst | 368 +++++++++++++++++++++ 4 files changed, 542 insertions(+) create mode 100644 Documentation/driver-api/soundwire/error_handling.rst create mode 100644 Documentation/driver-api/soundwire/locking.rst create mode 100644 Documentation/driver-api/soundwire/stream.rst
diff --git a/Documentation/driver-api/soundwire/error_handling.rst b/Documentation/driver-api/soundwire/error_handling.rst new file mode 100644 index 000000000000..aa3a0a23a066 --- /dev/null +++ b/Documentation/driver-api/soundwire/error_handling.rst @@ -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. diff --git a/Documentation/driver-api/soundwire/index.rst b/Documentation/driver-api/soundwire/index.rst index 647e94654752..6db026028f27 100644 --- a/Documentation/driver-api/soundwire/index.rst +++ b/Documentation/driver-api/soundwire/index.rst @@ -6,6 +6,9 @@ SoundWire Documentation :maxdepth: 1
summary + stream + error_handling + locking
.. only:: subproject
diff --git a/Documentation/driver-api/soundwire/locking.rst b/Documentation/driver-api/soundwire/locking.rst new file mode 100644 index 000000000000..253f73555255 --- /dev/null +++ b/Documentation/driver-api/soundwire/locking.rst @@ -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 + | | + + + diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst new file mode 100644 index 000000000000..83830e1dd138 --- /dev/null +++ b/Documentation/driver-api/soundwire/stream.rst @@ -0,0 +1,368 @@ +========================= +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) + +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.
On 4/5/18 11:48 AM, Vinod Koul wrote:
From: Sanyog Kale sanyog.r.kale@intel.com
This adds documentation for error handling, locking and streams.
Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
.../driver-api/soundwire/error_handling.rst | 65 ++++ Documentation/driver-api/soundwire/index.rst | 3 + Documentation/driver-api/soundwire/locking.rst | 106 ++++++ Documentation/driver-api/soundwire/stream.rst | 368 +++++++++++++++++++++ 4 files changed, 542 insertions(+) create mode 100644 Documentation/driver-api/soundwire/error_handling.rst create mode 100644 Documentation/driver-api/soundwire/locking.rst create mode 100644 Documentation/driver-api/soundwire/stream.rst
diff --git a/Documentation/driver-api/soundwire/error_handling.rst b/Documentation/driver-api/soundwire/error_handling.rst new file mode 100644 index 000000000000..aa3a0a23a066 --- /dev/null +++ b/Documentation/driver-api/soundwire/error_handling.rst @@ -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. diff --git a/Documentation/driver-api/soundwire/index.rst b/Documentation/driver-api/soundwire/index.rst index 647e94654752..6db026028f27 100644 --- a/Documentation/driver-api/soundwire/index.rst +++ b/Documentation/driver-api/soundwire/index.rst @@ -6,6 +6,9 @@ SoundWire Documentation :maxdepth: 1
summary
stream
error_handling
locking
.. only:: subproject
diff --git a/Documentation/driver-api/soundwire/locking.rst b/Documentation/driver-api/soundwire/locking.rst new file mode 100644 index 000000000000..253f73555255 --- /dev/null +++ b/Documentation/driver-api/soundwire/locking.rst @@ -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.
- 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.
- Acquire lock for Bus instance associated with Master 1.
- 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.
- 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
| |
+ +
diff --git a/Documentation/driver-api/soundwire/stream.rst b/Documentation/driver-api/soundwire/stream.rst new file mode 100644 index 000000000000..83830e1dd138 --- /dev/null +++ b/Documentation/driver-api/soundwire/stream.rst @@ -0,0 +1,368 @@ +========================= +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 |
+----------+ +------------+ +----------+
Patch 2 describes the states as below:
+enum sdw_stream_state { + SDW_STREAM_RELEASED = 0, + SDW_STREAM_ALLOCATED = 1, + SDW_STREAM_CONFIGURED = 2, + SDW_STREAM_PREPARED = 3, + SDW_STREAM_ENABLED = 4, + SDW_STREAM_DISABLED = 5, + SDW_STREAM_DEPREPARED = 6,
which isn't the same picture as the ascii art above. The RELEASED state is the starting point, and there's an arrow missing from RELEASED to ALLOCATED.
+NOTE: State transition between prepare and deprepare is supported in Spec +but not in the software (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.
On Thu, Apr 05, 2018 at 04:37:14PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+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 |
+----------+ +------------+ +----------+
Patch 2 describes the states as below:
+enum sdw_stream_state {
- SDW_STREAM_RELEASED = 0,
- SDW_STREAM_ALLOCATED = 1,
- SDW_STREAM_CONFIGURED = 2,
- SDW_STREAM_PREPARED = 3,
- SDW_STREAM_ENABLED = 4,
- SDW_STREAM_DISABLED = 5,
- SDW_STREAM_DEPREPARED = 6,
which isn't the same picture as the ascii art above. The RELEASED state is the starting point, and there's an arrow missing from RELEASED to ALLOCATED.
The enumerator describes the values given for each state. That has no relation whatsoever to state transitions allowed. I don't recall why people made SDW_STREAM_RELEASED as Zero, it could very well have been 7 and nothing changes in diagram.
Now in the last review I did mention to you that there is NO transition between RELEASED and ALLOCATED, hence no arrow can be made.
Your point on initial state is not right. I can we go ahead and do a kmalloc() instead of current kzalloc() for the structure which would make stream state from garbage/uninitialized to ALLOCATED and eventually finally into RELEASED and it being freed. See no move between ALLOCATED and RELEASED!
On 4/5/18 10:24 PM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 04:37:14PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+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 |
+----------+ +------------+ +----------+
Patch 2 describes the states as below:
+enum sdw_stream_state {
- SDW_STREAM_RELEASED = 0,
- SDW_STREAM_ALLOCATED = 1,
- SDW_STREAM_CONFIGURED = 2,
- SDW_STREAM_PREPARED = 3,
- SDW_STREAM_ENABLED = 4,
- SDW_STREAM_DISABLED = 5,
- SDW_STREAM_DEPREPARED = 6,
which isn't the same picture as the ascii art above. The RELEASED state is the starting point, and there's an arrow missing from RELEASED to ALLOCATED.
The enumerator describes the values given for each state. That has no relation whatsoever to state transitions allowed. I don't recall why people made SDW_STREAM_RELEASED as Zero, it could very well have been 7 and nothing changes in diagram.
Now in the last review I did mention to you that there is NO transition between RELEASED and ALLOCATED, hence no arrow can be made.
Your point on initial state is not right. I can we go ahead and do a kmalloc() instead of current kzalloc() for the structure which would make stream state from garbage/uninitialized to ALLOCATED and eventually finally into RELEASED and it being freed. See no move between ALLOCATED and RELEASED!
since RELEASED is the value zero, it is the implicit start state, like it or not. If you don't initialize your structure then your tests may fail or your stream might be allocated when it's not.
It's just engineering 101 to define a state machine with a known start point...
On Fri, Apr 06, 2018 at 10:24:08AM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 10:24 PM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 04:37:14PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+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 |
+----------+ +------------+ +----------+
Patch 2 describes the states as below:
+enum sdw_stream_state {
- SDW_STREAM_RELEASED = 0,
- SDW_STREAM_ALLOCATED = 1,
- SDW_STREAM_CONFIGURED = 2,
- SDW_STREAM_PREPARED = 3,
- SDW_STREAM_ENABLED = 4,
- SDW_STREAM_DISABLED = 5,
- SDW_STREAM_DEPREPARED = 6,
which isn't the same picture as the ascii art above. The RELEASED state is the starting point, and there's an arrow missing from RELEASED to ALLOCATED.
The enumerator describes the values given for each state. That has no relation whatsoever to state transitions allowed. I don't recall why people made SDW_STREAM_RELEASED as Zero, it could very well have been 7 and nothing changes in diagram.
Now in the last review I did mention to you that there is NO transition between RELEASED and ALLOCATED, hence no arrow can be made.
Your point on initial state is not right. I can we go ahead and do a kmalloc() instead of current kzalloc() for the structure which would make stream state from garbage/uninitialized to ALLOCATED and eventually finally into RELEASED and it being freed. See no move between ALLOCATED and RELEASED!
since RELEASED is the value zero, it is the implicit start state, like it or not. If you don't initialize your structure then your tests may fail or your stream might be allocated when it's not.
As I said above we can make SDW_STREAM_RELEASED = 7. And the code block is:
stream = kmalloc(sizeof(*stream), GFP_KERNEL); if (!stream) return NULL;
stream->state = SDW_STREAM_ALLOCATED;
So there is no implicit start state :)
It's just engineering 101 to define a state machine with a known start point...
See above code, for us it is SDW_STREAM_ALLOCATED
Alternately I can also do:
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, }
and keep the code as is. That should make ALLOCATED as default start state. That seems more sensible to me.
Again there is no state transition between ALLOCATED and RELEASED.
Thanks
From: Sanyog Kale sanyog.r.kale@intel.com
This patch adds APIs and relevant stream data structures for initialization and release of stream.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 1 + drivers/soundwire/bus.h | 36 +++++ drivers/soundwire/stream.c | 354 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 109 +++++++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/stream.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index e1a74c5692aa..5817beaca0e1 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -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 diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index d6dc8e7a8614..abf046f6b188 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -32,6 +32,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); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 345c34d697e9..3c66b6aecc14 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,42 @@ struct sdw_msg { bool page; };
+/** + * 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 + */ +struct sdw_slave_runtime { + struct sdw_slave *slave; + enum sdw_data_direction direction; + unsigned int ch_count; + struct list_head m_rt_node; +}; + +/** + * 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 + * @slave_rt_list: Slave runtime list + * @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 bus_node; +}; + 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); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c new file mode 100644 index 000000000000..5c34177e4954 --- /dev/null +++ b/drivers/soundwire/stream.c @@ -0,0 +1,354 @@ +// 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.h> +#include "bus.h" + +/** + * 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 + */ +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; + 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->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; + + + s_rt->ch_count = stream_config->ch_count; + s_rt->direction = stream_config->direction; + s_rt->slave = slave; + + return s_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; + } + } +} + +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_release_slave_stream(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 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); + 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 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_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 ((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; + if (is_slave) + stream->params.ch_count += stream_config->ch_count; + + 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 + * @stream: Soundwire stream + */ +int sdw_stream_add_master(struct sdw_bus *bus, + struct sdw_stream_config *stream_config, + struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt = NULL; + int ret; + + if (stream->state != SDW_STREAM_ALLOCATED && + stream->state != SDW_STREAM_CONFIGURED) { + dev_err(bus->dev, + "Invalid stream state %d", stream->state); + return -EINVAL; + } + + 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; + + if (!list_empty(&m_rt->slave_rt_list) && + stream->state == SDW_STREAM_ALLOCATED) + 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 + */ +int sdw_stream_add_slave(struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + struct sdw_stream_runtime *stream) +{ + struct sdw_slave_runtime *s_rt; + struct sdw_master_runtime *m_rt; + int ret; + + if (stream->state != SDW_STREAM_ALLOCATED && + stream->state != SDW_STREAM_CONFIGURED) { + dev_err(&slave->dev, + "Invalid stream state %d", stream->state); + return -EINVAL; + } + + mutex_lock(&slave->bus->bus_lock); + + /* + * If this API is invoked by slave first then m_rt is not valid. + * So, allocate that and add the 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); + + stream->state = SDW_STREAM_CONFIGURED; + goto error; + +stream_error: + sdw_release_master_stream(stream); +error: + mutex_unlock(&slave->bus->bus_lock); + return ret; +} +EXPORT_SYMBOL(sdw_stream_add_slave); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index e91fdcf41049..893e1b6b4914 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -61,6 +61,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 */ @@ -450,6 +474,9 @@ struct sdw_master_ops { * @msg_lock: message lock * @ops: Master callback ops * @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 */ @@ -462,6 +489,7 @@ struct sdw_bus { struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop; + struct list_head m_rt_list; struct sdw_defer defer_msg; unsigned int clk_stop_timeout; }; @@ -469,6 +497,87 @@ struct sdw_bus { int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus);
+/** + * 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_RELEASED: Stream released + * @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 + */ +enum sdw_stream_state { + SDW_STREAM_RELEASED = 0, + SDW_STREAM_ALLOCATED = 1, + SDW_STREAM_CONFIGURED = 2, + SDW_STREAM_PREPARED = 3, + SDW_STREAM_ENABLED = 4, + SDW_STREAM_DISABLED = 5, + SDW_STREAM_DEPREPARED = 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_stream_runtime *stream); +int sdw_stream_add_slave(struct sdw_slave *slave, + struct sdw_stream_config *stream_config, + 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); + /* messaging and data APIs */
int sdw_read(struct sdw_slave *slave, u32 addr);
On 4/5/18 11:48 AM, Vinod Koul wrote:
From: Sanyog Kale sanyog.r.kale@intel.com
This patch adds APIs and relevant stream data structures for initialization and release of stream.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 1 + drivers/soundwire/bus.h | 36 +++++ drivers/soundwire/stream.c | 354 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 109 +++++++++++++ 5 files changed, 501 insertions(+), 1 deletion(-) create mode 100644 drivers/soundwire/stream.c
diff --git a/drivers/soundwire/Makefile b/drivers/soundwire/Makefile index e1a74c5692aa..5817beaca0e1 100644 --- a/drivers/soundwire/Makefile +++ b/drivers/soundwire/Makefile @@ -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 diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index d6dc8e7a8614..abf046f6b188 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -32,6 +32,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);
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 345c34d697e9..3c66b6aecc14 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,42 @@ struct sdw_msg { bool page; };
+/**
- 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
- */
+struct sdw_slave_runtime {
- struct sdw_slave *slave;
- enum sdw_data_direction direction;
- unsigned int ch_count;
- struct list_head m_rt_node;
+};
+/**
- 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
Didn't we say that this count could be zero for device-to-device communication? 'handled by the Master' is not really correct.
- @slave_rt_list: Slave runtime list
- @bus_node: sdw_bus m_rt_list node
I think the intention was to progress in steps, but by omitting references to the multi-master case in this series or comments explaining the next steps the structure and code are not easy to follow.
There should be a clear explanation that - a stream can be connected a least one Slave Device with one or more ports. - a stream may be connected to zero or more Master Devices. In the former case one of the Slave devices has provide TX ports. - the slave runtime keeps track of all the masters the stream is connected to - the master runtime keeps track of 1. all the Slaves it is physically connected to which support a stream. 2. all bus instances (and related masters) that support this stream.
- */
+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 bus_node;
+};
- 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);
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c new file mode 100644 index 000000000000..5c34177e4954 --- /dev/null +++ b/drivers/soundwire/stream.c @@ -0,0 +1,354 @@ +// 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.h> +#include "bus.h"
+/**
- 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
You should explain who the caller might be, and probably check if a stream with the same name was not already allocated.
- */
+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;
- if (m_rt)
goto stream_config;
I know there is a reason for this code pattern but I just can't remember it and there is no comment indicated why this is valid.
- m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
- if (!m_rt)
return NULL;
- /* Initialization of Master runtime handle */
- 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;
- s_rt->ch_count = stream_config->ch_count;
- s_rt->direction = stream_config->direction;
- s_rt->slave = slave;
- return s_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;
}
- }
+}
+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_release_slave_stream(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 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);
- 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 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_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 ((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;
- if (is_slave)
stream->params.ch_count += stream_config->ch_count;
- 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
- @stream: Soundwire stream
- */
+int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_master_runtime *m_rt = NULL;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(bus->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
Humm, this check looks like new code but I don't get it. Why would one add a master in either of those two states? Why not ALLOCATED only?
The documentation states that:
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
- 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;
- if (!list_empty(&m_rt->slave_rt_list) &&
stream->state == SDW_STREAM_ALLOCATED)
stream->state = SDW_STREAM_CONFIGURED;
this looks also like new code, what is the idea about this configuration business? To me this is inconsistent with the documentation which says sdw_stream_add_master is called in ALLOCATED state. Under what circumstances would this routing be called from two different states?
This may be ok but the intent is clear as mud.
+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
- */
+int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_slave_runtime *s_rt;
- struct sdw_master_runtime *m_rt;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(&slave->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
same as for the master, why would one call this routine from two separate states?
- mutex_lock(&slave->bus->bus_lock);
- /*
* If this API is invoked by slave first then m_rt is not valid.
* So, allocate that and add the 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);
so you have a potential to add the same node twice from ALLOCATED and CONFIGURED states? I am completely lost.
- stream->state = SDW_STREAM_CONFIGURED;
- goto error;
+stream_error:
- sdw_release_master_stream(stream);
maybe add a comment that this will also release all previously allocated slave_rt nodes for that stream?
+error:
- mutex_unlock(&slave->bus->bus_lock);
- return ret;
+} +EXPORT_SYMBOL(sdw_stream_add_slave); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index e91fdcf41049..893e1b6b4914 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -61,6 +61,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
@@ -450,6 +474,9 @@ struct sdw_master_ops {
- @msg_lock: message lock
- @ops: Master callback ops
- @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
@@ -462,6 +489,7 @@ struct sdw_bus { struct mutex msg_lock; const struct sdw_master_ops *ops; struct sdw_master_prop prop;
- struct list_head m_rt_list; struct sdw_defer defer_msg; unsigned int clk_stop_timeout; };
@@ -469,6 +497,87 @@ struct sdw_bus { int sdw_add_bus_master(struct sdw_bus *bus); void sdw_delete_bus_master(struct sdw_bus *bus);
+/**
- 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_RELEASED: Stream released
- @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
- */
+enum sdw_stream_state {
- SDW_STREAM_RELEASED = 0,
- SDW_STREAM_ALLOCATED = 1,
- SDW_STREAM_CONFIGURED = 2,
- SDW_STREAM_PREPARED = 3,
- SDW_STREAM_ENABLED = 4,
- SDW_STREAM_DISABLED = 5,
- SDW_STREAM_DEPREPARED = 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_stream_runtime *stream);
+int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
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);
/* messaging and data APIs */
int sdw_read(struct sdw_slave *slave, u32 addr);
On Thu, Apr 05, 2018 at 05:34:18PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+/**
- 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
Didn't we say that this count could be zero for device-to-device communication? 'handled by the Master' is not really correct.
Yes for device-to-device this would be zero. And master will handle no channels as indicated by zero value. Won't that be right?
- @slave_rt_list: Slave runtime list
- @bus_node: sdw_bus m_rt_list node
I think the intention was to progress in steps, but by omitting references to the multi-master case in this series or comments explaining the next steps the structure and code are not easy to follow.
We cannot have any ref to multi-master as it is not in this series. That is how patches would be done, you add a step and then increment with another.
Ofcourse if you have any question feel free to ask and we can clarify, but I do not think it would be correct to add comments/reference here for multi-master. These will be updated as required when we post multi-master.
There should be a clear explanation that
- a stream can be connected a least one Slave Device with one or more ports.
explained in document patch
- a stream may be connected to zero or more Master Devices. In the former
case one of the Slave devices has provide TX ports.
That's multi-link/device-device not under review here
- the slave runtime keeps track of all the masters the stream is connected
to
not that is not a correct assumption
- the master runtime keeps track of
- all the Slaves it is physically connected to which support a stream.
Yes that is added here
- all bus instances (and related masters) that support this stream.
That's again multi-link!
Again we are not writing a spec but code and comments to explain the code. The above is not related to "this" series so we should not add it here. When we add device-device support, multi-master support these can be added
We are adding blocks in a giant building, but you are only looking at the giant picture but not the block in discussion!
+/**
- 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
You should explain who the caller might be, and probably check if a stream with the same name was not already allocated.
Checking name might be bit iffy as we would need to check all streams. The name is used for prints so if caller screws up name then it would be at his own peril to get confused between same stream names in logs :)
+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;
- if (m_rt)
goto stream_config;
I know there is a reason for this code pattern but I just can't remember it and there is no comment indicated why this is valid.
If m_rt is already allocated we skip the initialization and configure it. As we have told you earlier, the slave runtime can be added which triggers master runtime to be allocated as well, so we need to check.
We do have such a comment in sdw_stream_add_slave() as you advised. Should we duplicate it here too?
+int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_master_runtime *m_rt = NULL;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(bus->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
Humm, this check looks like new code but I don't get it. Why would one add a master in either of those two states? Why not ALLOCATED only?
So slave or master can be added to a stream in any order. Only if slave is called first we need master_rt to be allocated too. In ideal world we would do allocation and then configuration, but due to user calling in any order we may do allocation and configuration of slave first followed by configuring the master.
The documentation states that:
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
Yes you have a valid point wrt documentation, will update it.
- 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;
- if (!list_empty(&m_rt->slave_rt_list) &&
stream->state == SDW_STREAM_ALLOCATED)
stream->state = SDW_STREAM_CONFIGURED;
this looks also like new code, what is the idea about this configuration business? To me this is inconsistent with the documentation which says sdw_stream_add_master is called in ALLOCATED state. Under what circumstances would this routing be called from two different states?
Yes it "may" be due to sequencing in callers control.
This may be ok but the intent is clear as mud.
Yes that is a bit unfortunate :(
+int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_slave_runtime *s_rt;
- struct sdw_master_runtime *m_rt;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(&slave->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
same as for the master, why would one call this routine from two separate states?
as discussed above
- mutex_lock(&slave->bus->bus_lock);
- /*
* If this API is invoked by slave first then m_rt is not valid.
* So, allocate that and add the 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);
so you have a potential to add the same node twice from ALLOCATED and CONFIGURED states? I am completely lost.
That would be the case if Slave invokes this routine erroneously twice. We expect this would be called only once by caller.
On 4/5/18 11:53 PM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 05:34:18PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+/**
- 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
Didn't we say that this count could be zero for device-to-device communication? 'handled by the Master' is not really correct.
Yes for device-to-device this would be zero. And master will handle no channels as indicated by zero value. Won't that be right?
It doesn't hurt to say that zero is an acceptable value by design.
- @slave_rt_list: Slave runtime list
- @bus_node: sdw_bus m_rt_list node
I think the intention was to progress in steps, but by omitting references to the multi-master case in this series or comments explaining the next steps the structure and code are not easy to follow.
We cannot have any ref to multi-master as it is not in this series. That is how patches would be done, you add a step and then increment with another.
The point is that the bus_node is only relevant for multi-master, so you have structure fields are present, not currently used but will be. It's perfectly ok to progress in steps, but you have to be consistent. If you keep a structure that will only be used later, you need to tell this to reviewers.
Ofcourse if you have any question feel free to ask and we can clarify, but I do not think it would be correct to add comments/reference here for multi-master. These will be updated as required when we post multi-master.
There should be a clear explanation that
- a stream can be connected a least one Slave Device with one or more ports.
explained in document patch
- a stream may be connected to zero or more Master Devices. In the former
case one of the Slave devices has provide TX ports.
That's multi-link/device-device not under review here
zero or more describes the single master case.
- the slave runtime keeps track of all the masters the stream is connected
to
not that is not a correct assumption
yes this is wrong, I meant "the master runtime keeps tracks off all the Slaves the stream is connected to"
- the master runtime keeps track of
- all the Slaves it is physically connected to which support a stream.
Yes that is added here
- all bus instances (and related masters) that support this stream.
That's again multi-link!
the statement is still correct with a single instance :-)
Again we are not writing a spec but code and comments to explain the code. The above is not related to "this" series so we should not add it here. When we add device-device support, multi-master support these can be added
We are adding blocks in a giant building, but you are only looking at the giant picture but not the block in discussion!
no, you have keep mentions to multi-link that are questionable. Either you remove them or you state it's reserved for future use.
+/**
- 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
You should explain who the caller might be, and probably check if a stream with the same name was not already allocated.
Checking name might be bit iffy as we would need to check all streams. The name is used for prints so if caller screws up name then it would be at his own peril to get confused between same stream names in logs :)
You didn't answer to the question: who is supposed to call sdw_alloc_steam()? the machine driver? The platform driver? the codec driver? How do you garantee that it's called once
+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;
- if (m_rt)
goto stream_config;
I know there is a reason for this code pattern but I just can't remember it and there is no comment indicated why this is valid.
If m_rt is already allocated we skip the initialization and configure it. As we have told you earlier, the slave runtime can be added which triggers master runtime to be allocated as well, so we need to check.
We do have such a comment in sdw_stream_add_slave() as you advised. Should we duplicate it here too?
doesn't hurt to add a comment pointing to sdw_stream_add_slave() here as well, the reviewers read linearly and if the comment is in 200 lines it doesn't help.
+int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_master_runtime *m_rt = NULL;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(bus->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
Humm, this check looks like new code but I don't get it. Why would one add a master in either of those two states? Why not ALLOCATED only?
So slave or master can be added to a stream in any order. Only if slave is called first we need master_rt to be allocated too. In ideal world we would do allocation and then configuration, but due to user calling in any order we may do allocation and configuration of slave first followed by configuring the master.
that makes no sense. You have a state diagram that defines a state transition which precludes this order between allocation and configuration.
The documentation states that:
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
Yes you have a valid point wrt documentation, will update it.
It's not just the documentation, I wonder if it actually works. You would want to make sure all masters and slaves are allocated before configuring them, otherwise the error handling flow is just awful to deal with. You could end-up in a situation where all Slaves are configured but the master allocation fails.
- 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;
- if (!list_empty(&m_rt->slave_rt_list) &&
stream->state == SDW_STREAM_ALLOCATED)
stream->state = SDW_STREAM_CONFIGURED;
this looks also like new code, what is the idea about this configuration business? To me this is inconsistent with the documentation which says sdw_stream_add_master is called in ALLOCATED state. Under what circumstances would this routing be called from two different states?
Yes it "may" be due to sequencing in callers control.
This may be ok but the intent is clear as mud.
Yes that is a bit unfortunate :(
You'll need to really rework this, i don't get why this major change comes in a v2 without the associated collateral explaining the rationale for this change.
+int sdw_stream_add_slave(struct sdw_slave *slave,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_slave_runtime *s_rt;
- struct sdw_master_runtime *m_rt;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(&slave->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
same as for the master, why would one call this routine from two separate states?
as discussed above
- mutex_lock(&slave->bus->bus_lock);
- /*
* If this API is invoked by slave first then m_rt is not valid.
* So, allocate that and add the 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);
so you have a potential to add the same node twice from ALLOCATED and CONFIGURED states? I am completely lost.
That would be the case if Slave invokes this routine erroneously twice. We expect this would be called only once by caller.
On Fri, Apr 06, 2018 at 10:21:05AM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:53 PM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 05:34:18PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+/**
- 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
Didn't we say that this count could be zero for device-to-device communication? 'handled by the Master' is not really correct.
Yes for device-to-device this would be zero. And master will handle no channels as indicated by zero value. Won't that be right?
It doesn't hurt to say that zero is an acceptable value by design.
ok
- @slave_rt_list: Slave runtime list
- @bus_node: sdw_bus m_rt_list node
I think the intention was to progress in steps, but by omitting references to the multi-master case in this series or comments explaining the next steps the structure and code are not easy to follow.
We cannot have any ref to multi-master as it is not in this series. That is how patches would be done, you add a step and then increment with another.
The point is that the bus_node is only relevant for multi-master, so you have structure fields are present, not currently used but will be. It's perfectly ok to progress in steps, but you have to be consistent. If you keep a structure that will only be used later, you need to tell this to reviewers.
Sorry no it is not the case. bus_node keeps track of master_rt on a bus. This is used in non multi-link case Ex multiple streams over a bus. We need to redo bandwidth calculation etc on a bus when a new stream arrives or existing stream frees up, so a bus will always track the master_rt
Ofcourse if you have any question feel free to ask and we can clarify, but I do not think it would be correct to add comments/reference here for multi-master. These will be updated as required when we post multi-master.
There should be a clear explanation that
- a stream can be connected a least one Slave Device with one or more ports.
explained in document patch
- a stream may be connected to zero or more Master Devices. In the former
case one of the Slave devices has provide TX ports.
That's multi-link/device-device not under review here
zero or more describes the single master case.
- the slave runtime keeps track of all the masters the stream is connected
to
not that is not a correct assumption
yes this is wrong, I meant "the master runtime keeps tracks off all the Slaves the stream is connected to"
- the master runtime keeps track of
- all the Slaves it is physically connected to which support a stream.
Yes that is added here
- all bus instances (and related masters) that support this stream.
That's again multi-link!
the statement is still correct with a single instance :-)
Again we are not writing a spec but code and comments to explain the code. The above is not related to "this" series so we should not add it here. When we add device-device support, multi-master support these can be added
We are adding blocks in a giant building, but you are only looking at the giant picture but not the block in discussion!
no, you have keep mentions to multi-link that are questionable. Either you remove them or you state it's reserved for future use.
I have clarified on bus_node above, can you check and tell me if there is anything else?
+/**
- 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
You should explain who the caller might be, and probably check if a stream with the same name was not already allocated.
Checking name might be bit iffy as we would need to check all streams. The name is used for prints so if caller screws up name then it would be at his own peril to get confused between same stream names in logs :)
You didn't answer to the question: who is supposed to call sdw_alloc_steam()? the machine driver? The platform driver? the codec driver? How do you garantee that it's called once
Sorry about that, it would by machine or platform but only once. We can't guarantee that as it is caller prerogative but code will not work if someone calls multiple times and they will debug.
Btw this assumption is explicitly added in documentation.
+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;
- if (m_rt)
goto stream_config;
I know there is a reason for this code pattern but I just can't remember it and there is no comment indicated why this is valid.
If m_rt is already allocated we skip the initialization and configure it. As we have told you earlier, the slave runtime can be added which triggers master runtime to be allocated as well, so we need to check.
We do have such a comment in sdw_stream_add_slave() as you advised. Should we duplicate it here too?
doesn't hurt to add a comment pointing to sdw_stream_add_slave() here as well, the reviewers read linearly and if the comment is in 200 lines it doesn't help.
ok
+int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_master_runtime *m_rt = NULL;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(bus->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
Humm, this check looks like new code but I don't get it. Why would one add a master in either of those two states? Why not ALLOCATED only?
So slave or master can be added to a stream in any order. Only if slave is called first we need master_rt to be allocated too. In ideal world we would do allocation and then configuration, but due to user calling in any order we may do allocation and configuration of slave first followed by configuring the master.
that makes no sense. You have a state diagram that defines a state transition which precludes this order between allocation and configuration.
Okay so we are going to track if master and slaves runtimes are configured by adding a flag in each of them. Once all the components are configured the stream stated will be updated. Does that sound ok?
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
Yes you have a valid point wrt documentation, will update it.
It's not just the documentation, I wonder if it actually works. You would want to make sure all masters and slaves are allocated before configuring them, otherwise the error handling flow is just awful to deal with. You could end-up in a situation where all Slaves are configured but the master allocation fails.
Ok
- 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;
- if (!list_empty(&m_rt->slave_rt_list) &&
stream->state == SDW_STREAM_ALLOCATED)
stream->state = SDW_STREAM_CONFIGURED;
this looks also like new code, what is the idea about this configuration business? To me this is inconsistent with the documentation which says sdw_stream_add_master is called in ALLOCATED state. Under what circumstances would this routing be called from two different states?
Yes it "may" be due to sequencing in callers control.
This may be ok but the intent is clear as mud.
Yes that is a bit unfortunate :(
You'll need to really rework this, i don't get why this major change comes in a v2 without the associated collateral explaining the rationale for this change.
Btw this change was not introduced in v2, it was there in v1 too
- @slave_rt_list: Slave runtime list
- @bus_node: sdw_bus m_rt_list node
I think the intention was to progress in steps, but by omitting references to the multi-master case in this series or comments explaining the next steps the structure and code are not easy to follow.
We cannot have any ref to multi-master as it is not in this series. That is how patches would be done, you add a step and then increment with another.
The point is that the bus_node is only relevant for multi-master, so you have structure fields are present, not currently used but will be. It's perfectly ok to progress in steps, but you have to be consistent. If you keep a structure that will only be used later, you need to tell this to reviewers.
Sorry no it is not the case. bus_node keeps track of master_rt on a bus. This is used in non multi-link case Ex multiple streams over a bus. We need to redo bandwidth calculation etc on a bus when a new stream arrives or existing stream frees up, so a bus will always track the master_rt
I stand corrected, thanks.
Ofcourse if you have any question feel free to ask and we can clarify, but I do not think it would be correct to add comments/reference here for multi-master. These will be updated as required when we post multi-master.
There should be a clear explanation that
- a stream can be connected a least one Slave Device with one or more ports.
explained in document patch
- a stream may be connected to zero or more Master Devices. In the former
case one of the Slave devices has provide TX ports.
That's multi-link/device-device not under review here
zero or more describes the single master case.
- the slave runtime keeps track of all the masters the stream is connected
to
not that is not a correct assumption
yes this is wrong, I meant "the master runtime keeps tracks off all the Slaves the stream is connected to"
- the master runtime keeps track of
- all the Slaves it is physically connected to which support a stream.
Yes that is added here
- all bus instances (and related masters) that support this stream.
That's again multi-link!
the statement is still correct with a single instance :-)
Again we are not writing a spec but code and comments to explain the code. The above is not related to "this" series so we should not add it here. When we add device-device support, multi-master support these can be added
We are adding blocks in a giant building, but you are only looking at the giant picture but not the block in discussion!
no, you have keep mentions to multi-link that are questionable. Either you remove them or you state it's reserved for future use.
I have clarified on bus_node above, can you check and tell me if there is anything else?
The code pattern for the bank switch was inspired by multi-link.
+/**
- 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
You should explain who the caller might be, and probably check if a stream with the same name was not already allocated.
Checking name might be bit iffy as we would need to check all streams. The name is used for prints so if caller screws up name then it would be at his own peril to get confused between same stream names in logs :)
You didn't answer to the question: who is supposed to call sdw_alloc_steam()? the machine driver? The platform driver? the codec driver? How do you garantee that it's called once
Sorry about that, it would by machine or platform but only once. We can't guarantee that as it is caller prerogative but code will not work if someone calls multiple times and they will debug.
Btw this assumption is explicitly added in documentation.
I don't get how this would work. To me the platform drivers and codec drivers expose DAIs, and the machine drivers connect them. The stream is an additional level that the machine driver should know. I just don't see how a platform driver would handle this?
+int sdw_stream_add_master(struct sdw_bus *bus,
struct sdw_stream_config *stream_config,
struct sdw_stream_runtime *stream)
+{
- struct sdw_master_runtime *m_rt = NULL;
- int ret;
- if (stream->state != SDW_STREAM_ALLOCATED &&
stream->state != SDW_STREAM_CONFIGURED) {
dev_err(bus->dev,
"Invalid stream state %d", stream->state);
return -EINVAL;
- }
Humm, this check looks like new code but I don't get it. Why would one add a master in either of those two states? Why not ALLOCATED only?
So slave or master can be added to a stream in any order. Only if slave is called first we need master_rt to be allocated too. In ideal world we would do allocation and then configuration, but due to user calling in any order we may do allocation and configuration of slave first followed by configuring the master.
that makes no sense. You have a state diagram that defines a state transition which precludes this order between allocation and configuration.
Okay so we are going to track if master and slaves runtimes are configured by adding a flag in each of them. Once all the components are configured the stream stated will be updated. Does that sound ok?
How do you detect that all components are configured? We have a distributed allocation/configuration with multiple actors, I don't what triggers the transition to a new state. see more below.
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
Yes you have a valid point wrt documentation, will update it.
It's not just the documentation, I wonder if it actually works. You would want to make sure all masters and slaves are allocated before configuring them, otherwise the error handling flow is just awful to deal with. You could end-up in a situation where all Slaves are configured but the master allocation fails.
Ok
If the ALLOCATED and CONFIGURED states are clearly separated out, then what if the flag you mentioned used for?
- 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;
- if (!list_empty(&m_rt->slave_rt_list) &&
stream->state == SDW_STREAM_ALLOCATED)
stream->state = SDW_STREAM_CONFIGURED;
this looks also like new code, what is the idea about this configuration business? To me this is inconsistent with the documentation which says sdw_stream_add_master is called in ALLOCATED state. Under what circumstances would this routing be called from two different states?
Yes it "may" be due to sequencing in callers control.
This may be ok but the intent is clear as mud.
Yes that is a bit unfortunate :(
You'll need to really rework this, i don't get why this major change comes in a v2 without the associated collateral explaining the rationale for this change.
Btw this change was not introduced in v2, it was there in v1 too
It wasn't there in the initial patches shared with me in February. There were too many missing parts in v1 that I stopped the review.
On Tue, Apr 10, 2018 at 10:47:24AM -0500, Pierre-Louis Bossart wrote:
Again we are not writing a spec but code and comments to explain the code. The above is not related to "this" series so we should not add it here. When we add device-device support, multi-master support these can be added
We are adding blocks in a giant building, but you are only looking at the giant picture but not the block in discussion!
no, you have keep mentions to multi-link that are questionable. Either you remove them or you state it's reserved for future use.
I have clarified on bus_node above, can you check and tell me if there is anything else?
The code pattern for the bank switch was inspired by multi-link.
As you pointed out, we are fixing that too
Okay so we are going to track if master and slaves runtimes are configured by adding a flag in each of them. Once all the components are configured the stream stated will be updated. Does that sound ok?
How do you detect that all components are configured? We have a distributed allocation/configuration with multiple actors, I don't what triggers the transition to a new state. see more below.
+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.
so how can Master ports be configured if it is added to a stream *after* the CONFIGURED state?
Yes you have a valid point wrt documentation, will update it.
It's not just the documentation, I wonder if it actually works. You would want to make sure all masters and slaves are allocated before configuring them, otherwise the error handling flow is just awful to deal with. You could end-up in a situation where all Slaves are configured but the master allocation fails.
Ok
If the ALLOCATED and CONFIGURED states are clearly separated out, then what if the flag you mentioned used for?
As you rightly pointed out that we have distributed allocation/configuration with multiple actors. So to ensure that all actors are properly configured before we move stream state to CONFIGURED, we need to check and to do so the flag will be very useful :-)
From: Sanyog Kale sanyog.r.kale@intel.com
Add Soundwire port data structures and APIS for initialization and release of ports.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.h | 25 +++++++ drivers/soundwire/stream.c | 151 +++++++++++++++++++++++++++++++++++++++++- include/linux/soundwire/sdw.h | 67 +++++++++++++++++++ 3 files changed, 241 insertions(+), 2 deletions(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 3c66b6aecc14..2e834a8038ed 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -46,6 +46,27 @@ struct sdw_msg { };
/** + * 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 @@ -53,12 +74,14 @@ struct sdw_msg { * @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; };
/** @@ -70,6 +93,7 @@ struct sdw_slave_runtime { * @ch_count: Number of channels handled by the Master for * this stream * @slave_rt_list: Slave runtime list + * @port_list: List of Master Ports configured for this stream * @bus_node: sdw_bus m_rt_list node */ struct sdw_master_runtime { @@ -78,6 +102,7 @@ struct sdw_master_runtime { 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; };
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 5c34177e4954..4b10f07b3e9e 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -75,6 +75,7 @@ static struct sdw_master_runtime 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;
@@ -109,6 +110,7 @@ static struct sdw_slave_runtime 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; @@ -117,6 +119,41 @@ static struct sdw_slave_runtime return s_rt; }
+static void sdw_master_port_deconfig(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_deconfig(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 * @@ -161,7 +198,7 @@ static void sdw_release_master_stream(struct sdw_stream_runtime *stream) * @bus: SDW Bus instance * @stream: Soundwire stream * - * This removes and frees master_rt from a 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) @@ -169,6 +206,7 @@ int sdw_stream_remove_master(struct sdw_bus *bus, mutex_lock(&bus->bus_lock);
sdw_release_master_stream(stream); + sdw_master_port_deconfig(bus, stream->m_rt); stream->state = SDW_STREAM_RELEASED; kfree(stream->m_rt); stream->m_rt = NULL; @@ -185,13 +223,14 @@ EXPORT_SYMBOL(sdw_stream_remove_master); * @slave: SDW Slave instance * @stream: Soundwire stream * - * This removes and frees slave_rt from a 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_deconfig(slave->bus, slave, stream); sdw_release_slave_stream(slave, stream);
mutex_unlock(&slave->bus->bus_lock); @@ -241,15 +280,111 @@ static int sdw_config_stream(struct device *dev, 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, ret; + + /* 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; + + ret = sdw_is_valid_port_range(bus->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, &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; + + 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; @@ -277,6 +412,10 @@ int sdw_stream_add_master(struct sdw_bus *bus, if (ret) goto stream_error;
+ ret = sdw_master_port_config(bus, m_rt, port_config, num_ports); + if (ret) + goto stream_error; + if (!list_empty(&m_rt->slave_rt_list) && stream->state == SDW_STREAM_ALLOCATED) stream->state = SDW_STREAM_CONFIGURED; @@ -295,10 +434,14 @@ EXPORT_SYMBOL(sdw_stream_add_master); * * @slave: SDW Slave 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_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; @@ -342,6 +485,10 @@ int sdw_stream_add_slave(struct sdw_slave *slave,
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;
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 893e1b6b4914..228fdbf506fe 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -26,6 +26,8 @@ struct sdw_slave;
#define SDW_MAX_DEVICES 11
+#define SDW_VALID_PORT_RANGE(n) (n <= 14 && n >= 1) + /** * enum sdw_slave_status - Slave status * @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus. @@ -430,6 +432,56 @@ 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_msg;
/** @@ -498,6 +550,17 @@ 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 @@ -569,9 +632,13 @@ 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);
On 4/5/18 11:48 AM, Vinod Koul wrote:
From: Sanyog Kale sanyog.r.kale@intel.com
Add Soundwire port data structures and APIS for initialization and release of ports.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/bus.h | 25 +++++++ drivers/soundwire/stream.c | 151 +++++++++++++++++++++++++++++++++++++++++- include/linux/soundwire/sdw.h | 67 +++++++++++++++++++ 3 files changed, 241 insertions(+), 2 deletions(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 3c66b6aecc14..2e834a8038ed 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -46,6 +46,27 @@ struct sdw_msg { };
/**
- 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
@@ -53,12 +74,14 @@ struct sdw_msg {
- @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; };
/**
@@ -70,6 +93,7 @@ struct sdw_slave_runtime {
- @ch_count: Number of channels handled by the Master for
- this stream
- @slave_rt_list: Slave runtime list
- @port_list: List of Master Ports configured for this stream
possibly empty for device to device communication.
- @bus_node: sdw_bus m_rt_list node
*/ struct sdw_master_runtime { @@ -78,6 +102,7 @@ struct sdw_master_runtime { 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; };
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 5c34177e4954..4b10f07b3e9e 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -75,6 +75,7 @@ static struct sdw_master_runtime 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;
@@ -109,6 +110,7 @@ static struct sdw_slave_runtime 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;
@@ -117,6 +119,41 @@ static struct sdw_slave_runtime return s_rt; }
+static void sdw_master_port_deconfig(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);
- }
+}
I still don't get the naming conventions. There is no DECONFIGURED state, why not call it release? In which state is this called?
+static void sdw_slave_port_deconfig(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);
}
can we use common code between master and slaves? This looks virtually identical.
- }
+}
- /**
- sdw_release_slave_stream: Free Slave(s) runtime handle
@@ -161,7 +198,7 @@ static void sdw_release_master_stream(struct sdw_stream_runtime *stream)
- @bus: SDW Bus instance
- @stream: Soundwire stream
- This removes and frees master_rt from a stream
*/ int sdw_stream_remove_master(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
- This removes and frees port_rt and master_rt from a stream
@@ -169,6 +206,7 @@ int sdw_stream_remove_master(struct sdw_bus *bus, mutex_lock(&bus->bus_lock);
sdw_release_master_stream(stream);
- sdw_master_port_deconfig(bus, stream->m_rt); stream->state = SDW_STREAM_RELEASED; kfree(stream->m_rt); stream->m_rt = NULL;
@@ -185,13 +223,14 @@ EXPORT_SYMBOL(sdw_stream_remove_master);
- @slave: SDW Slave instance
- @stream: Soundwire stream
- This removes and frees slave_rt from a 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_deconfig(slave->bus, slave, stream);
then call it sdw_slave_port_release as I mentioned above...
sdw_release_slave_stream(slave, stream);
mutex_unlock(&slave->bus->bus_lock); @@ -241,15 +280,111 @@ static int sdw_config_stream(struct device *dev, 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, ret;
- /* 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; > +
ret = sdw_is_valid_port_range(bus->dev, p_rt);
a master has no definition of ports. You could have more than 14 ports. Even if you have a description of those ports, it has to be checking not for the standard definition but what the hardware can support
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, &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;
ret = sdw_is_valid_port_range(&slave->dev, p_rt);
this is optimistic. You should check the actual port range (as defined in DisCo properties or driver), not just the worst case allowed by the standard. This should include a check that the bi-dir ports are configured for the right role and that the direction is compatible for regular fixed-direction ports.
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
*/ int sdw_stream_add_master(struct sdw_bus *bus, struct sdw_stream_config *stream_config,
- @num_ports: Number of ports
- @stream: Soundwire stream
struct sdw_port_config *port_config,
struct sdw_stream_runtime *stream) { struct sdw_master_runtime *m_rt = NULL;unsigned int num_ports,
@@ -277,6 +412,10 @@ int sdw_stream_add_master(struct sdw_bus *bus, if (ret) goto stream_error;
- ret = sdw_master_port_config(bus, m_rt, port_config, num_ports);
- if (ret)
goto stream_error;
- if (!list_empty(&m_rt->slave_rt_list) && stream->state == SDW_STREAM_ALLOCATED) stream->state = SDW_STREAM_CONFIGURED;
@@ -295,10 +434,14 @@ EXPORT_SYMBOL(sdw_stream_add_master);
- @slave: SDW Slave instance
- @stream_config: Stream configuration for audio stream
- @port_config: Port configuration for audio stream
*/ int sdw_stream_add_slave(struct sdw_slave *slave, struct sdw_stream_config *stream_config,
- @num_ports: Number of ports
- @stream: Soundwire stream
struct sdw_port_config *port_config,
struct sdw_stream_runtime *stream) { struct sdw_slave_runtime *s_rt;unsigned int num_ports,
@@ -342,6 +485,10 @@ int sdw_stream_add_slave(struct sdw_slave *slave,
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;
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 893e1b6b4914..228fdbf506fe 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -26,6 +26,8 @@ struct sdw_slave;
#define SDW_MAX_DEVICES 11
+#define SDW_VALID_PORT_RANGE(n) (n <= 14 && n >= 1)
- /**
- enum sdw_slave_status - Slave status
- @SDW_SLAVE_UNATTACHED: Slave is not attached with the bus.
@@ -430,6 +432,56 @@ 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_msg;
/**
@@ -498,6 +550,17 @@ 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
@@ -569,9 +632,13 @@ 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,
struct sdw_stream_runtime *stream); int sdw_stream_add_slave(struct sdw_slave *slave, struct sdw_stream_config *stream_config,unsigned int num_ports,
struct sdw_port_config *port_config,
struct sdw_stream_runtime *stream); int sdw_stream_remove_master(struct sdw_bus *bus, struct sdw_stream_runtime *stream);unsigned int num_ports,
On Thu, Apr 05, 2018 at 06:04:32PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
@@ -70,6 +93,7 @@ struct sdw_slave_runtime {
- @ch_count: Number of channels handled by the Master for
- this stream
- @slave_rt_list: Slave runtime list
- @port_list: List of Master Ports configured for this stream
possibly empty for device to device communication.
Not in scope, will add in that series
+static void sdw_master_port_deconfig(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);
- }
+}
I still don't get the naming conventions. There is no DECONFIGURED state, why not call it release? In which state is this called?
it is NOT about stream state, but deconfigure the ports. Well we can make it sdw_master_port_release() if that makes you happy!
+static void sdw_slave_port_deconfig(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);
}
can we use common code between master and slaves? This looks virtually identical.
Nope it is not, we tried in past and end result looked uglier. Only 4 lines of code are same and previous one iterates over master and here we have slave...
int sdw_stream_remove_slave(struct sdw_slave *slave, struct sdw_stream_runtime *stream) { mutex_lock(&slave->bus->bus_lock);
- sdw_slave_port_deconfig(slave->bus, slave, stream);
then call it sdw_slave_port_release as I mentioned above...
ok
+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, ret;
- /* 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; > +
ret = sdw_is_valid_port_range(bus->dev, p_rt);
a master has no definition of ports. You could have more than 14 ports. Even if you have a description of those ports, it has to be checking not for the standard definition but what the hardware can support
ok will remove check for master
+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;
ret = sdw_is_valid_port_range(&slave->dev, p_rt);
this is optimistic. You should check the actual port range (as defined in DisCo properties or driver), not just the worst case allowed by the standard. This should include a check that the bi-dir ports are configured for the right role and that the direction is compatible for regular fixed-direction ports.
well this is better that no check but yes that can be further improved in future to comprehend DisCo properties and port direction. I will add that to my list
On 4/6/18 12:00 AM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 06:04:32PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
@@ -70,6 +93,7 @@ struct sdw_slave_runtime {
- @ch_count: Number of channels handled by the Master for
- this stream
- @slave_rt_list: Slave runtime list
- @port_list: List of Master Ports configured for this stream
possibly empty for device to device communication.
Not in scope, will add in that series
+static void sdw_master_port_deconfig(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);
- }
+}
I still don't get the naming conventions. There is no DECONFIGURED state, why not call it release? In which state is this called?
it is NOT about stream state, but deconfigure the ports. Well we can make it sdw_master_port_release() if that makes you happy!
yes please. There is no operation called 'deconfigure'
+static void sdw_slave_port_deconfig(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);
}
can we use common code between master and slaves? This looks virtually identical.
Nope it is not, we tried in past and end result looked uglier. Only 4 lines of code are same and previous one iterates over master and here we have slave...
ok
int sdw_stream_remove_slave(struct sdw_slave *slave, struct sdw_stream_runtime *stream) { mutex_lock(&slave->bus->bus_lock);
- sdw_slave_port_deconfig(slave->bus, slave, stream);
then call it sdw_slave_port_release as I mentioned above...
ok
+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, ret;
- /* 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; > +
ret = sdw_is_valid_port_range(bus->dev, p_rt);
a master has no definition of ports. You could have more than 14 ports. Even if you have a description of those ports, it has to be checking not for the standard definition but what the hardware can support
ok will remove check for master
+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;
ret = sdw_is_valid_port_range(&slave->dev, p_rt);
this is optimistic. You should check the actual port range (as defined in DisCo properties or driver), not just the worst case allowed by the standard. This should include a check that the bi-dir ports are configured for the right role and that the direction is compatible for regular fixed-direction ports.
well this is better that no check but yes that can be further improved in future to comprehend DisCo properties and port direction. I will add that to my list
ok, I am fine with a TODO.
From: Sanyog Kale sanyog.r.kale@intel.com
Master and Slave port registers need to be programmed for each port used in a stream. Add the helpers for port register programming.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com --- drivers/soundwire/bus.h | 4 + drivers/soundwire/stream.c | 262 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 47 +++++++- 3 files changed, 312 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 2e834a8038ed..33dc31c8f992 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -106,6 +106,10 @@ struct sdw_master_runtime { 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_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); diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 4b10f07b3e9e..61417a4decc5 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -11,9 +11,238 @@ #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"
+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; + u8 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) & + SDW_DPN_SAMPLECTRL_HIGH) >> + 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->hstop | (t_params->hstart << + SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART))); + 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_release_stream: Free the assigned stream runtime * @@ -499,3 +728,36 @@ int sdw_stream_add_slave(struct sdw_slave *slave, 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; +} diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 228fdbf506fe..f2b952148e1a 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -367,7 +367,30 @@ 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_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 + */ +struct sdw_bus_params { + enum sdw_reg_bank curr_bank; + enum sdw_reg_bank next_bank; +}; + +/** + * struct sdw_slave_ops: Slave driver callback ops + * * @read_prop: Read Slave properties * @interrupt_callback: Device interrupt notification (invoked in thread * context) @@ -482,6 +505,24 @@ struct sdw_transport_params { unsigned int lane_ctrl; };
+/** + * 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 + */ +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); +}; + struct sdw_msg;
/** @@ -525,6 +566,8 @@ 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, @@ -540,6 +583,8 @@ 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;
On 4/5/18 11:48 AM, Vinod Koul wrote:
From: Sanyog Kale sanyog.r.kale@intel.com
Master and Slave port registers need to be programmed for each port used in a stream. Add the helpers for port register programming.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com
drivers/soundwire/bus.h | 4 + drivers/soundwire/stream.c | 262 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 47 +++++++- 3 files changed, 312 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 2e834a8038ed..33dc31c8f992 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -106,6 +106,10 @@ struct sdw_master_runtime { 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_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);
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 4b10f07b3e9e..61417a4decc5 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -11,9 +11,238 @@ #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"
+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;
- u8 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) &
SDW_DPN_SAMPLECTRL_HIGH) >>
SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
wbuf = t_params->sample_interval - 1; wbuf &= SDW_DPN_SAMPLECTRL_HIGH); wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
same lines of code, simpler to read?
- 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->hstop | (t_params->hstart <<
SDW_REG_SHIFT(SDW_DPN_HCTRL_HSTART)));
eyesore again.
- 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_release_stream: Free the assigned stream runtime
@@ -499,3 +728,36 @@ int sdw_stream_add_slave(struct sdw_slave *slave, 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;
+} diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 228fdbf506fe..f2b952148e1a 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -367,7 +367,30 @@ 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_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
- */
+struct sdw_bus_params {
- enum sdw_reg_bank curr_bank;
- enum sdw_reg_bank next_bank;
+};
+/**
- struct sdw_slave_ops: Slave driver callback ops
- @read_prop: Read Slave properties
- @interrupt_callback: Device interrupt notification (invoked in thread
- context)
@@ -482,6 +505,24 @@ struct sdw_transport_params { unsigned int lane_ctrl; };
+/**
- 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
- */
+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);
+};
struct sdw_msg;
/**
@@ -525,6 +566,8 @@ 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,
@@ -540,6 +583,8 @@ 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;
On Thu, Apr 05, 2018 at 06:14:58PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
- /* Program DPN_SampleCtrl2 register */
- wbuf = ((t_params->sample_interval - 1) &
SDW_DPN_SAMPLECTRL_HIGH) >>
SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
wbuf = t_params->sample_interval - 1; wbuf &= SDW_DPN_SAMPLECTRL_HIGH); wbuf >>= SDW_REG_SHIFT(SDW_DPN_SAMPLECTRL_HIGH);
same lines of code, simpler to read?
ok will fix both of these
From: Sanyog Kale sanyog.r.kale@intel.com
Add helpers to configure, prepare, enable, disable and de-prepare ports.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 26 +++++ drivers/soundwire/bus.h | 2 + drivers/soundwire/stream.c | 260 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 54 +++++++++ 4 files changed, 342 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index abf046f6b188..b8c93f0ac0a0 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -577,6 +577,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; diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 33dc31c8f992..4471823de1f8 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -109,6 +109,8 @@ struct sdw_master_runtime { 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, diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 61417a4decc5..fc3a27ae4e7f 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -243,6 +243,266 @@ static int sdw_program_port_params(struct sdw_master_runtime *m_rt) return 0; }
+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_mstr_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.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, trying %s\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_mstr_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 being 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_mstr_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_mstr_ports(m_rt, p_rt, prep); + if (ret < 0) + return ret; + } + + return ret; +} + /** * sdw_release_stream: Free the assigned stream runtime * diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index f2b952148e1a..fcec63c59ec0 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -377,6 +377,37 @@ enum sdw_reg_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) @@ -395,6 +426,7 @@ struct sdw_bus_params { * @interrupt_callback: Device interrupt notification (invoked in thread * context) * @update_status: Update Slave status + * @port_prep: Prepare the port with parameters */ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw); @@ -402,6 +434,9 @@ struct sdw_slave_ops { struct sdw_slave_intr_status *status); int (*update_status)(struct sdw_slave *slave, enum sdw_slave_status status); + int (*port_prep)(struct sdw_slave *slave, + struct sdw_prepare_ch *prepare_ch, + enum sdw_port_prep_ops pre_ops); };
/** @@ -506,6 +541,19 @@ struct sdw_transport_params { };
/** + * 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 num; + unsigned int ch_mask; + bool enable; +}; + +/** * struct sdw_master_port_ops: Callback functions from bus to Master * driver to set Master Data ports. * @@ -513,6 +561,8 @@ struct sdw_transport_params { * 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, @@ -521,6 +571,10 @@ struct sdw_master_port_ops { 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;
+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) {
you sure about this? isn't it if (!time_left || val) ? val is one for NotFinished.
dev_err(&s_rt->slave->dev,
"Chn prep failed for port:%d", prep_ch.num);
return -ETIMEDOUT;
}
- }
- /* Inform slaves about ports being prepared */
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_mstr_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_mstr_ports(m_rt, p_rt, prep);
if (ret < 0)
return ret;
- }
- return ret;
+}
- /**
- sdw_release_stream: Free the assigned stream runtime
diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index f2b952148e1a..fcec63c59ec0 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -377,6 +377,37 @@ enum sdw_reg_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)
@@ -395,6 +426,7 @@ struct sdw_bus_params {
- @interrupt_callback: Device interrupt notification (invoked in thread
- context)
- @update_status: Update Slave status
*/ struct sdw_slave_ops { int (*read_prop)(struct sdw_slave *sdw);
- @port_prep: Prepare the port with parameters
@@ -402,6 +434,9 @@ struct sdw_slave_ops { struct sdw_slave_intr_status *status); int (*update_status)(struct sdw_slave *slave, enum sdw_slave_status status);
int (*port_prep)(struct sdw_slave *slave,
struct sdw_prepare_ch *prepare_ch,
enum sdw_port_prep_ops pre_ops);
};
/**
@@ -506,6 +541,19 @@ struct sdw_transport_params { };
/**
- 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 num;
port_num then?
- unsigned int ch_mask;
- bool enable;
+};
+/**
- struct sdw_master_port_ops: Callback functions from bus to Master
- driver to set Master Data ports.
@@ -513,6 +561,8 @@ struct sdw_transport_params {
- 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.
*/ struct sdw_master_port_ops { int (*dpn_set_port_params)(struct sdw_bus *bus,
- @dpn_port_enable_ch: Enable the channels of Master Port.
@@ -521,6 +571,10 @@ struct sdw_master_port_ops { 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;
On Thu, Apr 05, 2018 at 06:27:59PM -0500, Pierre-Louis Bossart wrote:
/* 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) {
you sure about this? isn't it if (!time_left || val) ? val is one for NotFinished.
Yeah it should be val, thanks for spotting this
dev_err(&s_rt->slave->dev,
"Chn prep failed for port:%d", prep_ch.num);
return -ETIMEDOUT;
}
- }
- /* Inform slaves about ports being prepared */
about ports prepared.
ok
/**
- 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 num;
port_num then?
yeah doesnt hurt
From: Sanyog Kale sanyog.r.kale@intel.com
SoundWire supports two registers banks. So, program the alternate bank with new configuration and then performs bank switch.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 7 ++ drivers/soundwire/bus.h | 5 + drivers/soundwire/stream.c | 232 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 48 +++++++++ 4 files changed, 292 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index b8c93f0ac0a0..084bf71b2b87 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -78,6 +78,13 @@ int sdw_add_bus_master(struct sdw_bus *bus) return ret; }
+ /* + * Default active bank will be 0 as out of reset the Slaves have + * to start with bank 0 (Table 40 of Spec) + */ + bus->params.curr_bank = SDW_BANK0; + bus->params.next_bank = SDW_BANK1; + return 0; } EXPORT_SYMBOL(sdw_add_bus_master); diff --git a/drivers/soundwire/bus.h b/drivers/soundwire/bus.h index 4471823de1f8..fe511a25d086 100644 --- a/drivers/soundwire/bus.h +++ b/drivers/soundwire/bus.h @@ -45,6 +45,11 @@ 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 * diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index fc3a27ae4e7f..4efc9bef4733 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -15,6 +15,43 @@ #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, @@ -504,6 +541,201 @@ static int sdw_prep_deprep_ports(struct sdw_master_runtime *m_rt, bool prep) }
/** + * 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 sdw_bank_switch(struct sdw_bus *bus) +{ + const struct sdw_master_ops *ops = bus->ops; + int ret = 0; + + /* 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 op failed: %d", ret); + return ret; + } + + return ret; +} + +static int sdw_post_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; + + /* 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; + } + } + + return ret; +} + +static int do_bank_switch(struct sdw_stream_runtime *stream) +{ + struct sdw_master_runtime *m_rt = stream->m_rt; + struct sdw_bus *bus = m_rt->bus; + int ret; + + /* Bank switch */ + ret = sdw_bank_switch(bus); + if (ret < 0) { + dev_err(bus->dev, "Bank switch failed: %d", ret); + goto err; + } + + ret = sdw_post_bank_switch(stream); + if (ret < 0) + dev_err(bus->dev, "Post Bank switch failed: %d", ret); + +err: + return ret; +} + +/** * sdw_release_stream: Free the assigned stream runtime * * @stream: SoundWire stream runtime diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index fcec63c59ec0..4120a220ae42 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -23,7 +23,17 @@ 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) @@ -377,6 +387,21 @@ enum sdw_reg_bank { };
/** + * 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 @@ -413,10 +438,20 @@ enum sdw_port_prep_ops { * @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; };
/** @@ -426,6 +461,7 @@ struct sdw_bus_params { * @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 { @@ -434,6 +470,8 @@ 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); @@ -597,6 +635,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); @@ -608,6 +649,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); + };
/** @@ -628,6 +674,7 @@ struct sdw_master_ops { * 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; @@ -643,6 +690,7 @@ struct sdw_bus { 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);
+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 sdw_bank_switch(struct sdw_bus *bus) +{
- const struct sdw_master_ops *ops = bus->ops;
- int ret = 0;
- /* 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 op failed: %d", ret);
return ret;
- }
- return ret;
+}
+static int sdw_post_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;
- /* 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;
}
- }
- return ret;
+}
+static int do_bank_switch(struct sdw_stream_runtime *stream) +{
- struct sdw_master_runtime *m_rt = stream->m_rt;
- struct sdw_bus *bus = m_rt->bus;
- int ret;
- /* Bank switch */
- ret = sdw_bank_switch(bus);
- if (ret < 0) {
dev_err(bus->dev, "Bank switch failed: %d", ret);
goto err;
- }
- ret = sdw_post_bank_switch(stream);
- if (ret < 0)
dev_err(bus->dev, "Post Bank switch failed: %d", ret);
The structure of the code makes little sense here, it could be pre-bank-switch(); bank-switch(); post-band-switch(); in the same routine.
you need to add information that post-bank will need handled in a specific manner with multi-link streams.
+err:
- return ret;
+}
On Thu, Apr 05, 2018 at 06:35:26PM -0500, Pierre-Louis Bossart wrote:
The structure of the code makes little sense here, it could be pre-bank-switch(); bank-switch(); post-band-switch(); in the same routine.
Okay let me restruture this part.
you need to add information that post-bank will need handled in a specific manner with multi-link streams.
Yes we already have that but not in the scope of this series
From: Sanyog Kale sanyog.r.kale@intel.com
Add APIs for prepare, enable, disable and de-prepare stream.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/bus.c | 9 ++ drivers/soundwire/stream.c | 294 ++++++++++++++++++++++++++++++++++++++++++ include/linux/soundwire/sdw.h | 4 + 3 files changed, 307 insertions(+)
diff --git a/drivers/soundwire/bus.c b/drivers/soundwire/bus.c index 084bf71b2b87..dcc0ff9f0c22 100644 --- a/drivers/soundwire/bus.c +++ b/drivers/soundwire/bus.c @@ -17,6 +17,7 @@ */ int sdw_add_bus_master(struct sdw_bus *bus) { + struct sdw_master_prop *prop = NULL; int ret;
if (!bus->dev) { @@ -79,9 +80,17 @@ int sdw_add_bus_master(struct sdw_bus *bus) }
/* + * 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;
diff --git a/drivers/soundwire/stream.c b/drivers/soundwire/stream.c index 4efc9bef4733..28d4f1f866fd 100644 --- a/drivers/soundwire/stream.c +++ b/drivers/soundwire/stream.c @@ -1253,3 +1253,297 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
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(¶ms, &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 */ + 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, ¶ms, 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); + + if (stream->state == SDW_STREAM_DISABLED) + goto error; + + if (stream->state != SDW_STREAM_CONFIGURED) { + ret = -EINVAL; + goto error; + } + + ret = _sdw_prepare_stream(stream); + if (ret < 0) { + pr_err("Prepare for stream:%s failed: %d", stream->name, ret); + goto error; + } + +error: + 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); + + if (stream->state == SDW_STREAM_ENABLED) + goto error; + + if ((stream->state != SDW_STREAM_PREPARED) && + (stream->state != SDW_STREAM_DISABLED)) { + ret = -EINVAL; + goto error; + } + + ret = _sdw_enable_stream(stream); + if (ret < 0) { + pr_err("Enable for stream:%s failed: %d", stream->name, ret); + goto error; + } + +error: + 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); + + if (stream->state == SDW_STREAM_DISABLED) + goto error; + + if (stream->state != SDW_STREAM_ENABLED) { + ret = -EINVAL; + goto error; + } + + ret = _sdw_disable_stream(stream); + if (ret < 0) { + pr_err("Disable for stream:%s failed: %d", stream->name, ret); + goto error; + } + +error: + 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; + } + + bus->params.bandwidth -= m_rt->stream->params.rate * + m_rt->ch_count * m_rt->stream->params.bps; + + if (!bus->params.bandwidth) { + bus->params.row = 0; + bus->params.col = 0; + goto exit; + + } + + /* 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); + +exit: + stream->state = SDW_STREAM_DEPREPARED; + + return ret; +} + +/** + * 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); + + if (stream->state != SDW_STREAM_DISABLED) { + ret = -EINVAL; + goto error; + } + + ret = _sdw_deprepare_stream(stream); + if (ret < 0) { + pr_err("De-prepare for stream:%d failed: %d", ret, ret); + goto error; + } + +error: + mutex_unlock(&stream->m_rt->bus->bus_lock); + return ret; +} +EXPORT_SYMBOL(sdw_deprepare_stream); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 4120a220ae42..4d9c3f86d0f0 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -791,6 +791,10 @@ 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 */
+/**
- 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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_CONFIGURED) {
ret = -EINVAL;
goto error;
- }
this seems to be a new pattern in this file. Why is the first test even needed?
- ret = _sdw_prepare_stream(stream);
- if (ret < 0) {
pr_err("Prepare for stream:%s failed: %d", stream->name, ret);
goto error;
- }
+error:
- 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);
- if (stream->state == SDW_STREAM_ENABLED)
goto error;
- if ((stream->state != SDW_STREAM_PREPARED) &&
(stream->state != SDW_STREAM_DISABLED)) {
ret = -EINVAL;
goto error;
- }
same here, why would you enable a stream that's already enabled? Why is this an error that returns 0?
- ret = _sdw_enable_stream(stream);
- if (ret < 0) {
pr_err("Enable for stream:%s failed: %d", stream->name, ret);
goto error;
- }
+error:
- 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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_ENABLED) {
ret = -EINVAL;
goto error;
- }
and here to.
- ret = _sdw_disable_stream(stream);
- if (ret < 0) {
pr_err("Disable for stream:%s failed: %d", stream->name, ret);
goto error;
- }
+error:
- 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;
- }
- bus->params.bandwidth -= m_rt->stream->params.rate *
m_rt->ch_count * m_rt->stream->params.bps;
- if (!bus->params.bandwidth) {
bus->params.row = 0;
bus->params.col = 0;
goto exit;
- }
- /* 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);
+exit:
- stream->state = SDW_STREAM_DEPREPARED;
- return ret;
+}
+/**
- 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);
- if (stream->state != SDW_STREAM_DISABLED) {
ret = -EINVAL;
goto error;
- }
- ret = _sdw_deprepare_stream(stream);
- if (ret < 0) {
pr_err("De-prepare for stream:%d failed: %d", ret, ret);
goto error;
- }
+error:
- mutex_unlock(&stream->m_rt->bus->bus_lock);
- return ret;
+} +EXPORT_SYMBOL(sdw_deprepare_stream); diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 4120a220ae42..4d9c3f86d0f0 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -791,6 +791,10 @@ 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 */
On Thu, Apr 05, 2018 at 06:40:20PM -0500, Pierre-Louis Bossart wrote:
+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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_CONFIGURED) {
ret = -EINVAL;
goto error;
- }
this seems to be a new pattern in this file. Why is the first test even needed?
Looking it again, the state transition is from CONFIGURED, so the first check is not required and will be removed.
+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);
- if (stream->state == SDW_STREAM_ENABLED)
goto error;
- if ((stream->state != SDW_STREAM_PREPARED) &&
(stream->state != SDW_STREAM_DISABLED)) {
ret = -EINVAL;
goto error;
- }
same here, why would you enable a stream that's already enabled? Why is this an error that returns 0?
So first, we think stream should not be enabled if it is already enabled. The code right now doesnt treat it as error, but we should so will fix it up...
+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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_ENABLED) {
ret = -EINVAL;
goto error;
- }
and here to.
yup, here too :)
On 4/6/18 3:48 AM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 06:40:20PM -0500, Pierre-Louis Bossart wrote:
+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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_CONFIGURED) {
ret = -EINVAL;
goto error;
- }
this seems to be a new pattern in this file. Why is the first test even needed?
Looking it again, the state transition is from CONFIGURED, so the first check is not required and will be removed.
+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);
- if (stream->state == SDW_STREAM_ENABLED)
goto error;
- if ((stream->state != SDW_STREAM_PREPARED) &&
(stream->state != SDW_STREAM_DISABLED)) {
ret = -EINVAL;
goto error;
- }
same here, why would you enable a stream that's already enabled? Why is this an error that returns 0?
So first, we think stream should not be enabled if it is already enabled. The code right now doesnt treat it as error, but we should so will fix it up...
ok
+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);
- if (stream->state == SDW_STREAM_DISABLED)
goto error;
- if (stream->state != SDW_STREAM_ENABLED) {
ret = -EINVAL;
goto error;
- }
and here to.
yup, here too :)
From: Shreyas NC shreyas.nc@intel.com
SoundWire stream needs to be propagated to all the DAIs(cpu, codec). So, add a snd_soc_dai_set_sdw_stream() API for the same.
Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- include/sound/soc-dai.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 58acd00cae19..405c4c304f87 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -168,6 +168,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. @@ -359,4 +361,23 @@ 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. + * + * 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
On 4/5/18 11:48 AM, Vinod Koul wrote:
From: Shreyas NC shreyas.nc@intel.com
SoundWire stream needs to be propagated to all the DAIs(cpu, codec). So, add a snd_soc_dai_set_sdw_stream() API for the same.
Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
include/sound/soc-dai.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+)
diff --git a/include/sound/soc-dai.h b/include/sound/soc-dai.h index 58acd00cae19..405c4c304f87 100644 --- a/include/sound/soc-dai.h +++ b/include/sound/soc-dai.h @@ -168,6 +168,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.
@@ -359,4 +361,23 @@ 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 == source, Capture == sink? what is the mapping convention?
- 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
On Thu, Apr 05, 2018 at 06:42:53PM -0500, Pierre-Louis Bossart wrote:
+/**
- 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 == source, Capture == sink? what is the mapping convention?
oops, will fix
From: Shreyas NC shreyas.nc@intel.com
There can be instances where drivers using Cadence IP might want to set sdw_master_ops differently per instance of it's use, so remove the cdns_master_ops and export the APIs.
Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/cadence_master.c | 17 ++++++----------- drivers/soundwire/cadence_master.h | 8 ++++++++ drivers/soundwire/intel.c | 11 +++++++++-- 3 files changed, 23 insertions(+), 13 deletions(-)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 3a9b1462039b..b0c09efd8f83 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -396,7 +396,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 +422,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 +444,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 +458,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 @@ -727,14 +730,6 @@ 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, -}; -EXPORT_SYMBOL(sdw_cdns_master_ops); - /** * sdw_cdns_probe() - Cadence probe routine * @cdns: Cadence instance diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index beaf6c9804eb..3ec74fa5f4f9 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -44,5 +44,13 @@ irqreturn_t sdw_cdns_thread(int irq, void *dev_id); int sdw_cdns_init(struct sdw_cdns *cdns); int sdw_cdns_enable_interrupt(struct sdw_cdns *cdns);
+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); #endif /* __SDW_CADENCE_H */ diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 86a7bd1fc912..aa0c60133de5 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -252,6 +252,13 @@ 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, +}; + /* * probe and init */ @@ -276,8 +283,8 @@ 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;
platform_set_drvdata(pdev, sdw);
Add support for Cadence port management and implement master port ops.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/cadence_master.c | 256 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 100 +++++++++++++++ drivers/soundwire/intel.c | 1 + 3 files changed, 357 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index b0c09efd8f83..89a4ae86d36a 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -669,6 +669,133 @@ 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 start, 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, 0, + stream->num_bd, offset); + if (ret) + goto pcm_error; + + offset += stream->num_bd; + + ret = cdns_allocate_pdi(cdns, &stream->in, 0, + stream->num_in, offset); + if (ret) + goto pcm_error; + + + offset += stream->num_in; + + ret = cdns_allocate_pdi(cdns, &stream->out, 0, + stream->num_out, offset); + if (ret) + goto pcm_error; + + /* 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, 0, + stream->num_bd, offset); + if (ret) + goto pdm_error; + + offset += stream->num_bd; + + ret = cdns_allocate_pdi(cdns, &stream->in, 0, + stream->num_in, offset); + if (ret) + goto pdm_error; + + offset += stream->num_in; + + ret = cdns_allocate_pdi(cdns, &stream->out, 0, + stream->num_out, offset); + if (ret) + goto pdm_error; + + /* 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; + goto pdm_error; + } + + 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; + +pdm_error: + kfree(stream->bd); + kfree(stream->in); + kfree(stream->out); + +pcm_error: + stream = &cdns->pcm; + kfree(stream->bd); + kfree(stream->in); + kfree(stream->out); + return ret; +} +EXPORT_SYMBOL(sdw_cdns_pdi_init); + /** * sdw_cdns_init() - Cadence initialization * @cdns: Cadence instance @@ -730,6 +857,134 @@ int sdw_cdns_init(struct sdw_cdns *cdns) } EXPORT_SYMBOL(sdw_cdns_init);
+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->num); + else + dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->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, +}; + /** * sdw_cdns_probe() - Cadence probe routine * @cdns: Cadence instance @@ -737,6 +992,7 @@ EXPORT_SYMBOL(sdw_cdns_init); int sdw_cdns_probe(struct sdw_cdns *cdns) { init_completion(&cdns->tx_complete); + cdns->bus.port_ops = &cdns_port_ops;
return 0; } diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 3ec74fa5f4f9..98a17f57918f 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -5,6 +5,92 @@ #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 - Cadence driver context * @dev: Linux device * @bus: Bus handle @@ -12,6 +98,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 +115,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,6 +138,8 @@ 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);
enum sdw_command_response @@ -53,4 +151,6 @@ cdns_xfer_msg_defer(struct sdw_bus *bus,
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); #endif /* __SDW_CADENCE_H */ diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index aa0c60133de5..a64f87a08cfd 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -257,6 +257,7 @@ static struct sdw_master_ops sdw_intel_ops = { .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, };
/*
On 4/5/18 11:48 AM, Vinod Koul wrote:
Add support for Cadence port management and implement master port ops.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/cadence_master.c | 256 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 100 +++++++++++++++ drivers/soundwire/intel.c | 1 + 3 files changed, 357 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index b0c09efd8f83..89a4ae86d36a 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -669,6 +669,133 @@ 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 start, u32 num, u32 pdi_offset)
the start parameter doesn't seem to be used, remove and remove the useless zeroes in all the calls to this routine?
+{
- 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, 0,
stream->num_bd, offset);
- if (ret)
goto pcm_error;
- offset += stream->num_bd;
- ret = cdns_allocate_pdi(cdns, &stream->in, 0,
stream->num_in, offset);
- if (ret)
goto pcm_error;
- offset += stream->num_in;
- ret = cdns_allocate_pdi(cdns, &stream->out, 0,
stream->num_out, offset);
- if (ret)
goto pcm_error;
- /* 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, 0,
stream->num_bd, offset);
- if (ret)
goto pdm_error;
- offset += stream->num_bd;
- ret = cdns_allocate_pdi(cdns, &stream->in, 0,
stream->num_in, offset);
- if (ret)
goto pdm_error;
- offset += stream->num_in;
- ret = cdns_allocate_pdi(cdns, &stream->out, 0,
stream->num_out, offset);
- if (ret)
goto pdm_error;
- /* 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;
goto pdm_error;
- }
- 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;
+pdm_error:
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
+pcm_error:
- stream = &cdns->pcm;
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
call me a grumpy old fart if you want, I still don't like people freeing memory they never allocated. It may be legal but it's sloppy.
- return ret;
+} +EXPORT_SYMBOL(sdw_cdns_pdi_init);
- /**
- sdw_cdns_init() - Cadence initialization
- @cdns: Cadence instance
@@ -730,6 +857,134 @@ int sdw_cdns_init(struct sdw_cdns *cdns) } EXPORT_SYMBOL(sdw_cdns_init);
+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->num);
- else
dpn_chnen_off = CDNS_DPN_B0_CH_EN(enable_ch->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,
+};
- /**
- sdw_cdns_probe() - Cadence probe routine
- @cdns: Cadence instance
@@ -737,6 +992,7 @@ EXPORT_SYMBOL(sdw_cdns_init); int sdw_cdns_probe(struct sdw_cdns *cdns) { init_completion(&cdns->tx_complete);
cdns->bus.port_ops = &cdns_port_ops;
return 0; }
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 3ec74fa5f4f9..98a17f57918f 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -5,6 +5,92 @@ #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 - Cadence driver context
- @dev: Linux device
- @bus: Bus handle
@@ -12,6 +98,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 +115,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,6 +138,8 @@ 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);
enum sdw_command_response
@@ -53,4 +151,6 @@ cdns_xfer_msg_defer(struct sdw_bus *bus,
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); #endif /* __SDW_CADENCE_H */ diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index aa0c60133de5..a64f87a08cfd 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -257,6 +257,7 @@ static struct sdw_master_ops sdw_intel_ops = { .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, };
/*
On Thu, Apr 05, 2018 at 07:19:43PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+static int cdns_allocate_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi **stream,
u32 start, u32 num, u32 pdi_offset)
the start parameter doesn't seem to be used, remove and remove the useless zeroes in all the calls to this routine?
Yes this seems to be the case, will fix
+pdm_error:
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
+pcm_error:
- stream = &cdns->pcm;
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
call me a grumpy old fart if you want, I still don't like people freeing memory they never allocated. It may be legal but it's sloppy.
Actually looking at it freeing is not required as we are doing devm_ allocations, so this can be removed and return error should do the trick and we propagate the error and cleanup, thanks for the catch
On 4/6/18 3:55 AM, Vinod Koul wrote:
On Thu, Apr 05, 2018 at 07:19:43PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+static int cdns_allocate_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi **stream,
u32 start, u32 num, u32 pdi_offset)
the start parameter doesn't seem to be used, remove and remove the useless zeroes in all the calls to this routine?
Yes this seems to be the case, will fix
+pdm_error:
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
+pcm_error:
- stream = &cdns->pcm;
- kfree(stream->bd);
- kfree(stream->in);
- kfree(stream->out);
call me a grumpy old fart if you want, I still don't like people freeing memory they never allocated. It may be legal but it's sloppy.
Actually looking at it freeing is not required as we are doing devm_ allocations, so this can be removed and return error should do the trick and we propagate the error and cleanup, thanks for the catch
sounds good.
Add support for Cadence stream initialization and implement stream APIs.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/cadence_master.c | 180 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 43 +++++++++ 2 files changed, 223 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 89a4ae86d36a..9400327c9fe2 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -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"
@@ -998,5 +1000,183 @@ int sdw_cdns_probe(struct sdw_cdns *cdns) } 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); + +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); + +static int cdns_get_pdi(struct sdw_cdns *cdns, + struct sdw_cdns_pdi *pdi, + unsigned int num, u32 ch) +{ + int i, pdis = 0; + u32 ch_count = ch; + + 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_pdi(cdns, stream->in, stream->num_in, ch); + else + pdis = cdns_get_pdi(cdns, stream->out, stream->num_out, ch); + + /* check if we found PDI, else find in bi-directional */ + if (!pdis) + pdis = cdns_get_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"); diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 98a17f57918f..eb902b19c5a4 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -1,5 +1,6 @@ // 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 @@ -91,6 +92,26 @@ struct sdw_cdns_stream_config { };
/** + * 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 * @bus: Bus handle @@ -142,6 +163,25 @@ 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);
@@ -153,4 +193,7 @@ 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 */
On 4/5/18 11:48 AM, Vinod Koul wrote:
Add support for Cadence stream initialization and implement stream APIs.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com
drivers/soundwire/cadence_master.c | 180 +++++++++++++++++++++++++++++++++++++ drivers/soundwire/cadence_master.h | 43 +++++++++ 2 files changed, 223 insertions(+)
diff --git a/drivers/soundwire/cadence_master.c b/drivers/soundwire/cadence_master.c index 89a4ae86d36a..9400327c9fe2 100644 --- a/drivers/soundwire/cadence_master.c +++ b/drivers/soundwire/cadence_master.c @@ -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"
@@ -998,5 +1000,183 @@ int sdw_cdns_probe(struct sdw_cdns *cdns) } 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);
+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);
+static int cdns_get_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi *pdi,
unsigned int num, u32 ch)
+{
- int i, pdis = 0;
- u32 ch_count = ch;
redundant variable without added value...
- 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_pdi(cdns, stream->in, stream->num_in, ch);
- else
pdis = cdns_get_pdi(cdns, stream->out, stream->num_out, ch);
- /* check if we found PDI, else find in bi-directional */
- if (!pdis)
pdis = cdns_get_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);
can you clarify the difference between _get_pdi and _find_pdi and alloc_stream/get_stream.
It's pretty confusing.
+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");
diff --git a/drivers/soundwire/cadence_master.h b/drivers/soundwire/cadence_master.h index 98a17f57918f..eb902b19c5a4 100644 --- a/drivers/soundwire/cadence_master.h +++ b/drivers/soundwire/cadence_master.h @@ -1,5 +1,6 @@ // 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 @@ -91,6 +92,26 @@ struct sdw_cdns_stream_config { };
/**
- 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
- @bus: Bus handle
@@ -142,6 +163,25 @@ 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);
@@ -153,4 +193,7 @@ 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,
#endif /* __SDW_CADENCE_H */void *stream, bool pcm, int direction);
On Thu, Apr 05, 2018 at 07:29:11PM -0500, Pierre-Louis Bossart wrote:
On 4/5/18 11:48 AM, Vinod Koul wrote:
+static int cdns_get_pdi(struct sdw_cdns *cdns,
struct sdw_cdns_pdi *pdi,
unsigned int num, u32 ch)
+{
- int i, pdis = 0;
- u32 ch_count = ch;
redundant variable without added value...
ok will remove
+EXPORT_SYMBOL(sdw_cdns_alloc_stream);
can you clarify the difference between _get_pdi and _find_pdi and alloc_stream/get_stream.
It's pretty confusing.
Okay will add few notes and try to rename the functions.
Add Intel stream init routines which initialize the Physical Data Interface (PDI), Audio Link Hub (ALH) and Audio shim. Also add bank switch routines.
Signed-off-by: Hardik T Shah hardik.t.shah@intel.com Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/intel.c | 156 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+)
diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index a64f87a08cfd..279cbd51cdfa 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -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> @@ -234,6 +236,150 @@ 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_prop_read(struct sdw_bus *bus) { /* Initialize with default handler to read all DisCo properties */ @@ -265,6 +411,7 @@ static struct sdw_master_ops sdw_intel_ops = { */ static int intel_probe(struct platform_device *pdev) { + struct sdw_cdns_stream_config config; struct sdw_intel *sdw; int ret;
@@ -287,6 +434,9 @@ static int intel_probe(struct platform_device *pdev) 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);
ret = sdw_add_bus_master(&sdw->cdns.bus); @@ -304,9 +454,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,
Add DAI registration and DAI ops for the Intel driver along with callback for topology configuration.
Signed-off-by: Sanyog Kale sanyog.r.kale@intel.com Signed-off-by: Shreyas NC shreyas.nc@intel.com Signed-off-by: Vinod Koul vinod.koul@intel.com --- drivers/soundwire/Kconfig | 2 +- drivers/soundwire/intel.c | 360 ++++++++++++++++++++++++++++++++++++ drivers/soundwire/intel.h | 4 + drivers/soundwire/intel_init.c | 3 + include/linux/soundwire/sdw.h | 3 + include/linux/soundwire/sdw_intel.h | 14 ++ 6 files changed, 385 insertions(+), 1 deletion(-)
diff --git a/drivers/soundwire/Kconfig b/drivers/soundwire/Kconfig index b46084b4b1f8..19c8efb9a5ee 100644 --- a/drivers/soundwire/Kconfig +++ b/drivers/soundwire/Kconfig @@ -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 diff --git a/drivers/soundwire/intel.c b/drivers/soundwire/intel.c index 279cbd51cdfa..46c317171f71 100644 --- a/drivers/soundwire/intel.c +++ b/drivers/soundwire/intel.c @@ -87,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; @@ -380,6 +386,349 @@ intel_pdi_alh_configure(struct sdw_intel *sdw, struct sdw_cdns_pdi *pdi) 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 */ @@ -473,8 +822,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: @@ -488,6 +847,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; diff --git a/drivers/soundwire/intel.h b/drivers/soundwire/intel.h index ffa30d9535a2..c1a5bac6212e 100644 --- a/drivers/soundwire/intel.h +++ b/drivers/soundwire/intel.h @@ -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 */ diff --git a/drivers/soundwire/intel_init.c b/drivers/soundwire/intel_init.c index 6f2bb99526f2..d1ea6b4d0ad3 100644 --- a/drivers/soundwire/intel_init.c +++ b/drivers/soundwire/intel_init.c @@ -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; diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h index 4d9c3f86d0f0..d995b40cf8b1 100644 --- a/include/linux/soundwire/sdw.h +++ b/include/linux/soundwire/sdw.h @@ -38,6 +38,9 @@ struct sdw_slave;
#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. diff --git a/include/linux/soundwire/sdw_intel.h b/include/linux/soundwire/sdw_intel.h index 4b37528f592d..2b9573b8aedd 100644 --- a/include/linux/soundwire/sdw_intel.h +++ b/include/linux/soundwire/sdw_intel.h @@ -5,17 +5,31 @@ #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);
On 4/5/18 11:48 AM, Vinod Koul wrote:
This series adds support in SoundWire subsystem for:
- Documentation for stream support
- stream management
- data port management
- DAI ops in cadence and Intel drivers
- ASoC API to propagate SDW stream
Updates in v2:
- Make ASoC API inlined
- Make stream states as states and not action
- Update the direction enum
- Fix some typos and comment updates
This is better than v1 and has few minor comments to be addressed but one big problem: you shot yourselves in the foot with the changes in state management that are inconsistent with the documentation (see e.g the patch #2). Without explanations on the intent that entire stream management section makes little sense. If you can fix this in a v3 the series will be in much better shape. Thanks!
Sanyog Kale (7): soundwire: Add more documentation soundwire: Add support for SoundWire stream management soundwire: Add support for port management soundwire: Add Master and Slave port programming soundwire: Add helpers for ports operations soundwire: Add bank switch routine soundwire: Add stream configuration APIs
Shreyas NC (2): ASoC: Add SoundWire stream programming interface soundwire: Remove cdns_master_ops
Vinod Koul (4): soundwire: cdns: Add port routines soundwire: cdns: Add stream routines soundwire: intel: Add stream initialization soundwire: intel: Add audio DAI ops
.../driver-api/soundwire/error_handling.rst | 65 + Documentation/driver-api/soundwire/index.rst | 3 + Documentation/driver-api/soundwire/locking.rst | 106 ++ Documentation/driver-api/soundwire/stream.rst | 368 +++++ drivers/soundwire/Kconfig | 2 +- drivers/soundwire/Makefile | 2 +- drivers/soundwire/bus.c | 43 + drivers/soundwire/bus.h | 72 + drivers/soundwire/cadence_master.c | 449 +++++- drivers/soundwire/cadence_master.h | 151 ++ drivers/soundwire/intel.c | 528 ++++++- drivers/soundwire/intel.h | 4 + drivers/soundwire/intel_init.c | 3 + drivers/soundwire/stream.c | 1549 ++++++++++++++++++++ include/linux/soundwire/sdw.h | 332 ++++- include/linux/soundwire/sdw_intel.h | 14 + include/sound/soc-dai.h | 21 + 17 files changed, 3698 insertions(+), 14 deletions(-) create mode 100644 Documentation/driver-api/soundwire/error_handling.rst create mode 100644 Documentation/driver-api/soundwire/locking.rst create mode 100644 Documentation/driver-api/soundwire/stream.rst create mode 100644 drivers/soundwire/stream.c
participants (2)
-
Pierre-Louis Bossart
-
Vinod Koul