mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-16 15:39:42 -04:00
Merge tag 'soundwire-6.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire
Pull soundwire updates from Vinod Koul: - Support for SoundWire Bulk Register Access (BRA) protocol in core along with Intel driver support and ASoC bits required - AMD driver updates and support for ACP 7.0 and 7.1 platforms * tag 'soundwire-6.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire: (28 commits) soundwire: take in count the bandwidth of a prepared stream ASoC: rt711-sdca: add DP0 support soundwire: debugfs: add interface for BPT/BRA transfers ASoC: SOF: Intel: hda-sdw-bpt: add CHAIN_DMA support soundwire: intel_ace2x: add BPT send_async/wait callbacks soundwire: intel: add BPT context definition ASoC: SOF: Intel: hda-sdw-bpt: add helpers for SoundWire BPT DMA soundwire: intel_auxdevice: add indirection for BPT send_async/wait soundwire: cadence: add BTP/BRA helpers to format data soundwire: bus: add bpt_stream pointer soundwire: bus: add send_async/wait APIs for BPT protocol soundwire: stream: reuse existing code for BPT stream soundwire: stream: special-case the bus compute_params() routine soundwire: stream: extend sdw_alloc_stream() to take 'type' parameter soundwire: extend sdw_stream_type to BPT soundwire: cadence: add BTP support for DP0 Documentation: driver: add SoundWire BRA description soundwire: amd: change the log level for command response log soundwire: slave: fix an OF node reference leak in soundwire slave device soundwire: Use str_enable_disable-like helpers ...
This commit is contained in:
336
Documentation/driver-api/soundwire/bra.rst
Normal file
336
Documentation/driver-api/soundwire/bra.rst
Normal file
@@ -0,0 +1,336 @@
|
||||
==========================
|
||||
Bulk Register Access (BRA)
|
||||
==========================
|
||||
|
||||
Conventions
|
||||
-----------
|
||||
|
||||
Capitalized words used in this documentation are intentional and refer
|
||||
to concepts of the SoundWire 1.x specification.
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The SoundWire 1.x specification provides a mechanism to speed-up
|
||||
command/control transfers by reclaiming parts of the audio
|
||||
bandwidth. The Bulk Register Access (BRA) protocol is a standard
|
||||
solution based on the Bulk Payload Transport (BPT) definitions.
|
||||
|
||||
The regular control channel uses Column 0 and can only send/retrieve
|
||||
one byte per frame with write/read commands. With a typical 48kHz
|
||||
frame rate, only 48kB/s can be transferred.
|
||||
|
||||
The optional Bulk Register Access capability can transmit up to 12
|
||||
Mbits/s and reduce transfer times by several orders of magnitude, but
|
||||
has multiple design constraints:
|
||||
|
||||
(1) Each frame can only support a read or a write transfer, with a
|
||||
10-byte overhead per frame (header and footer response).
|
||||
|
||||
(2) The read/writes SHALL be from/to contiguous register addresses
|
||||
in the same frame. A fragmented register space decreases the
|
||||
efficiency of the protocol by requiring multiple BRA transfers
|
||||
scheduled in different frames.
|
||||
|
||||
(3) The targeted Peripheral device SHALL support the optional Data
|
||||
Port 0, and likewise the Manager SHALL expose audio-like Ports
|
||||
to insert BRA packets in the audio payload using the concepts of
|
||||
Sample Interval, HSTART, HSTOP, etc.
|
||||
|
||||
(4) The BRA transport efficiency depends on the available
|
||||
bandwidth. If there are no on-going audio transfers, the entire
|
||||
frame minus Column 0 can be reclaimed for BRA. The frame shape
|
||||
also impacts efficiency: since Column0 cannot be used for
|
||||
BTP/BRA, the frame should rely on a large number of columns and
|
||||
minimize the number of rows. The bus clock should be as high as
|
||||
possible.
|
||||
|
||||
(5) The number of bits transferred per frame SHALL be a multiple of
|
||||
8 bits. Padding bits SHALL be inserted if necessary at the end
|
||||
of the data.
|
||||
|
||||
(6) The regular read/write commands can be issued in parallel with
|
||||
BRA transfers. This is convenient to e.g. deal with alerts, jack
|
||||
detection or change the volume during firmware download, but
|
||||
accessing the same address with two independent protocols has to
|
||||
be avoided to avoid undefined behavior.
|
||||
|
||||
(7) Some implementations may not be capable of handling the
|
||||
bandwidth of the BRA protocol, e.g. in the case of a slow I2C
|
||||
bus behind the SoundWire IP. In this case, the transfers may
|
||||
need to be spaced in time or flow-controlled.
|
||||
|
||||
(8) Each BRA packet SHALL be marked as 'Active' when valid data is
|
||||
to be transmitted. This allows for software to allocate a BRA
|
||||
stream but not transmit/discard data while processing the
|
||||
results or preparing the next batch of data, or allowing the
|
||||
peripheral to deal with the previous transfer. In addition BRA
|
||||
transfer can be started early on without data being ready.
|
||||
|
||||
(9) Up to 470 bytes may be transmitted per frame.
|
||||
|
||||
(10) The address is represented with 32 bits and does not rely on
|
||||
the paging registers used for the regular command/control
|
||||
protocol in Column 0.
|
||||
|
||||
|
||||
Error checking
|
||||
--------------
|
||||
|
||||
Firmware download is one of the key usages of the Bulk Register Access
|
||||
protocol. To make sure the binary data integrity is not compromised by
|
||||
transmission or programming errors, each BRA packet provides:
|
||||
|
||||
(1) A CRC on the 7-byte header. This CRC helps the Peripheral Device
|
||||
check if it is addressed and set the start address and number of
|
||||
bytes. The Peripheral Device provides a response in Byte 7.
|
||||
|
||||
(2) A CRC on the data block (header excluded). This CRC is
|
||||
transmitted as the last-but-one byte in the packet, prior to the
|
||||
footer response.
|
||||
|
||||
The header response can be one of:
|
||||
(a) Ack
|
||||
(b) Nak
|
||||
(c) Not Ready
|
||||
|
||||
The footer response can be one of:
|
||||
(1) Ack
|
||||
(2) Nak (CRC failure)
|
||||
(3) Good (operation completed)
|
||||
(4) Bad (operation failed)
|
||||
|
||||
Example frame
|
||||
-------------
|
||||
|
||||
The example below is not to scale and makes simplifying assumptions
|
||||
for clarity. The different chunks in the BRA packets are not required
|
||||
to start on a new SoundWire Row, and the scale of data may vary.
|
||||
|
||||
::
|
||||
|
||||
+---+--------------------------------------------+
|
||||
+ | |
|
||||
+ | BRA HEADER |
|
||||
+ | |
|
||||
+ +--------------------------------------------+
|
||||
+ C | HEADER CRC |
|
||||
+ O +--------------------------------------------+
|
||||
+ M | HEADER RESPONSE |
|
||||
+ M +--------------------------------------------+
|
||||
+ A | |
|
||||
+ N | |
|
||||
+ D | DATA |
|
||||
+ | |
|
||||
+ | |
|
||||
+ | |
|
||||
+ +--------------------------------------------+
|
||||
+ | DATA CRC |
|
||||
+ +--------------------------------------------+
|
||||
+ | FOOTER RESPONSE |
|
||||
+---+--------------------------------------------+
|
||||
|
||||
|
||||
Assuming the frame uses N columns, the configuration shown above can
|
||||
be programmed by setting the DP0 registers as:
|
||||
|
||||
- HSTART = 1
|
||||
- HSTOP = N - 1
|
||||
- Sampling Interval = N
|
||||
- WordLength = N - 1
|
||||
|
||||
Addressing restrictions
|
||||
-----------------------
|
||||
|
||||
The Device Number specified in the Header follows the SoundWire
|
||||
definitions, and broadcast and group addressing are permitted. For now
|
||||
the Linux implementation only allows for a single BPT transfer to a
|
||||
single device at a time. This might be revisited at a later point as
|
||||
an optimization to send the same firmware to multiple devices, but
|
||||
this would only be beneficial for single-link solutions.
|
||||
|
||||
In the case of multiple Peripheral devices attached to different
|
||||
Managers, the broadcast and group addressing is not supported by the
|
||||
SoundWire specification. Each device must be handled with separate BRA
|
||||
streams, possibly in parallel - the links are really independent.
|
||||
|
||||
Unsupported features
|
||||
--------------------
|
||||
|
||||
The Bulk Register Access specification provides a number of
|
||||
capabilities that are not supported in known implementations, such as:
|
||||
|
||||
(1) Transfers initiated by a Peripheral Device. The BRA Initiator is
|
||||
always the Manager Device.
|
||||
|
||||
(2) Flow-control capabilities and retransmission based on the
|
||||
'NotReady' header response require extra buffering in the
|
||||
SoundWire IP and are not implemented.
|
||||
|
||||
Bi-directional handling
|
||||
-----------------------
|
||||
|
||||
The BRA protocol can handle writes as well as reads, and in each
|
||||
packet the header and footer response are provided by the Peripheral
|
||||
Target device. On the Peripheral device, the BRA protocol is handled
|
||||
by a single DP0 data port, and at the low-level the bus ownership can
|
||||
will change for header/footer response as well as the data transmitted
|
||||
during a read.
|
||||
|
||||
On the host side, most implementations rely on a Port-like concept,
|
||||
with two FIFOs consuming/generating data transfers in parallel
|
||||
(Host->Peripheral and Peripheral->Host). The amount of data
|
||||
consumed/produced by these FIFOs is not symmetrical, as a result
|
||||
hardware typically inserts markers to help software and hardware
|
||||
interpret raw data
|
||||
|
||||
Each packet will typically have:
|
||||
|
||||
(1) a 'Start of Packet' indicator.
|
||||
|
||||
(2) an 'End of Packet' indicator.
|
||||
|
||||
(3) a packet identifier to correlate the data requested and
|
||||
transmitted, and the error status for each frame
|
||||
|
||||
Hardware implementations can check errors at the frame level, and
|
||||
retry a transfer in case of errors. However, as for the flow-control
|
||||
case, this requires extra buffering and intelligence in the
|
||||
hardware. The Linux support assumes that the entire transfer is
|
||||
cancelled if a single error is detected in one of the responses.
|
||||
|
||||
Abstraction required
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are no standard registers or mandatory implementation at the
|
||||
Manager level, so the low-level BPT/BRA details must be hidden in
|
||||
Manager-specific code. For example the Cadence IP format above is not
|
||||
known to the codec drivers.
|
||||
|
||||
Likewise, codec drivers should not have to know the frame size. The
|
||||
computation of CRC and handling of responses is handled in helpers and
|
||||
Manager-specific code.
|
||||
|
||||
The host BRA driver may also have restrictions on pages allocated for
|
||||
DMA, or other host-DSP communication protocols. The codec driver
|
||||
should not be aware of any of these restrictions, since it might be
|
||||
reused in combination with different implementations of Manager IPs.
|
||||
|
||||
Concurrency between BRA and regular read/write
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The existing 'nread/nwrite' API already relies on a notion of start
|
||||
address and number of bytes, so it would be possible to extend this
|
||||
API with a 'hint' requesting BPT/BRA be used.
|
||||
|
||||
However BRA transfers could be quite long, and the use of a single
|
||||
mutex for regular read/write and BRA is a show-stopper. Independent
|
||||
operation of the control/command and BRA transfers is a fundamental
|
||||
requirement, e.g. to change the volume level with the existing regmap
|
||||
interface while downloading firmware. The integration must however
|
||||
ensure that there are no concurrent access to the same address with
|
||||
the command/control protocol and the BRA protocol.
|
||||
|
||||
In addition, the 'sdw_msg' structure hard-codes support for 16-bit
|
||||
addresses and paging registers which are irrelevant for BPT/BRA
|
||||
support based on native 32-bit addresses. A separate API with
|
||||
'sdw_bpt_msg' makes more sense.
|
||||
|
||||
One possible strategy to speed-up all initialization tasks would be to
|
||||
start a BRA transfer for firmware download, then deal with all the
|
||||
"regular" read/writes in parallel with the command channel, and last
|
||||
to wait for the BRA transfers to complete. This would allow for a
|
||||
degree of overlap instead of a purely sequential solution. As such,
|
||||
the BRA API must support async transfers and expose a separate wait
|
||||
function.
|
||||
|
||||
|
||||
Peripheral/bus interface
|
||||
------------------------
|
||||
|
||||
The bus interface for BPT/BRA is made of two functions:
|
||||
|
||||
- sdw_bpt_send_async(bpt_message)
|
||||
|
||||
This function sends the data using the Manager
|
||||
implementation-defined capabilities (typically DMA or IPC
|
||||
protocol).
|
||||
|
||||
Queueing is currently not supported, the caller
|
||||
needs to wait for completion of the requested transfer.
|
||||
|
||||
- sdw_bpt_wait()
|
||||
|
||||
This function waits for the entire message provided by the
|
||||
codec driver in the 'send_async' stage. Intermediate status for
|
||||
smaller chunks will not be provided back to the codec driver,
|
||||
only a return code will be provided.
|
||||
|
||||
Regmap use
|
||||
~~~~~~~~~~
|
||||
|
||||
Existing codec drivers rely on regmap to download firmware to
|
||||
Peripherals. regmap exposes an async interface similar to the
|
||||
send/wait API suggested above, so at a high-level it would seem
|
||||
natural to combine BRA and regmap. The regmap layer could check if BRA
|
||||
is available or not, and use a regular read-write command channel in
|
||||
the latter case.
|
||||
|
||||
The regmap integration will be handled in a second step.
|
||||
|
||||
BRA stream model
|
||||
----------------
|
||||
|
||||
For regular audio transfers, the machine driver exposes a dailink
|
||||
connecting CPU DAI(s) and Codec DAI(s).
|
||||
|
||||
This model is not required BRA support:
|
||||
|
||||
(1) The SoundWire DAIs are mainly wrappers for SoundWire Data
|
||||
Ports, with possibly some analog or audio conversion
|
||||
capabilities bolted behind the Data Port. In the context of
|
||||
BRA, the DP0 is the destination. DP0 registers are standard and
|
||||
can be programmed blindly without knowing what Peripheral is
|
||||
connected to each link. In addition, if there are multiple
|
||||
Peripherals on a link and some of them do not support DP0, the
|
||||
write commands to program DP0 registers will generate harmless
|
||||
COMMAND_IGNORED responses that will be wired-ORed with
|
||||
responses from Peripherals which support DP0. In other words,
|
||||
the DP0 programming can be done with broadcast commands, and
|
||||
the information on the Target device can be added only in the
|
||||
BRA Header.
|
||||
|
||||
(2) At the CPU level, the DAI concept is not useful for BRA; the
|
||||
machine driver will not create a dailink relying on DP0. The
|
||||
only concept that is needed is the notion of port.
|
||||
|
||||
(3) The stream concept relies on a set of master_rt and slave_rt
|
||||
concepts. All of these entities represent ports and not DAIs.
|
||||
|
||||
(4) With the assumption that a single BRA stream is used per link,
|
||||
that stream can connect master ports as well as all peripheral
|
||||
DP0 ports.
|
||||
|
||||
(5) BRA transfers only make sense in the context of one
|
||||
Manager/Link, so the BRA stream handling does not rely on the
|
||||
concept of multi-link aggregation allowed by regular DAI links.
|
||||
|
||||
Audio DMA support
|
||||
-----------------
|
||||
|
||||
Some DMAs, such as HDaudio, require an audio format field to be
|
||||
set. This format is in turn used to define acceptable bursts. BPT/BRA
|
||||
support is not fully compatible with these definitions in that the
|
||||
format and bandwidth may vary between read and write commands.
|
||||
|
||||
In addition, on Intel HDaudio Intel platforms the DMAs need to be
|
||||
programmed with a PCM format matching the bandwidth of the BPT/BRA
|
||||
transfer. The format is based on 192kHz 32-bit samples, and the number
|
||||
of channels varies to adjust the bandwidth. The notion of channel is
|
||||
completely notional since the data is not typical audio
|
||||
PCM. Programming such channels helps reserve enough bandwidth and adjust
|
||||
FIFO sizes to avoid xruns.
|
||||
|
||||
Alignment requirements are currently not enforced at the core level
|
||||
but at the platform-level, e.g. for Intel the data sizes must be
|
||||
multiples of 32 bytes.
|
||||
66
Documentation/driver-api/soundwire/bra_cadence.rst
Normal file
66
Documentation/driver-api/soundwire/bra_cadence.rst
Normal file
@@ -0,0 +1,66 @@
|
||||
Cadence IP BRA support
|
||||
----------------------
|
||||
|
||||
Format requirements
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Cadence IP relies on PDI0 for TX and PDI1 for RX. The data needs
|
||||
to be formatted with the following conventions:
|
||||
|
||||
(1) all Data is stored in bits 15..0 of the 32-bit PDI FIFOs.
|
||||
|
||||
(2) the start of packet is BIT(31).
|
||||
|
||||
(3) the end of packet is BIT(30).
|
||||
|
||||
(4) A packet ID is stored in bits 19..16. This packet ID is
|
||||
determined by software and is typically a rolling counter.
|
||||
|
||||
(5) Padding shall be inserted as needed so that the Header CRC,
|
||||
Header response, Footer CRC, Footer response are always in
|
||||
Byte0. Padding is inserted by software for writes, and on reads
|
||||
software shall discard the padding added by the hardware.
|
||||
|
||||
Example format
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
The following table represents the sequence provided to PDI0 for a
|
||||
write command followed by a read command.
|
||||
|
||||
::
|
||||
|
||||
+---+---+--------+---------------+---------------+
|
||||
+ 1 | 0 | ID = 0 | WR HDR[1] | WR HDR[0] |
|
||||
+ | | | WR HDR[3] | WR HDR[2] |
|
||||
+ | | | WR HDR[5] | WR HDR[4] |
|
||||
+ | | | pad | WR HDR CRC |
|
||||
+ | | | WR Data[1] | WR Data[0] |
|
||||
+ | | | WR Data[3] | WR Data[2] |
|
||||
+ | | | WR Data[n-2] | WR Data[n-3] |
|
||||
+ | | | pad | WR Data[n-1] |
|
||||
+ 0 | 1 | | pad | WR Data CRC |
|
||||
+---+---+--------+---------------+---------------+
|
||||
+ 1 | 0 | ID = 1 | RD HDR[1] | RD HDR[0] |
|
||||
+ | | | RD HDR[3] | RD HDR[2] |
|
||||
+ | | | RD HDR[5] | RD HDR[4] |
|
||||
+ 0 | 1 | | pad | RD HDR CRC |
|
||||
+---+---+--------+---------------+---------------+
|
||||
|
||||
|
||||
The table below represents the data received on PDI1 for the same
|
||||
write command followed by a read command.
|
||||
|
||||
::
|
||||
|
||||
+---+---+--------+---------------+---------------+
|
||||
+ 1 | 0 | ID = 0 | pad | WR Hdr Rsp |
|
||||
+ 0 | 1 | | pad | WR Ftr Rsp |
|
||||
+---+---+--------+---------------+---------------+
|
||||
+ 1 | 0 | ID = 0 | pad | Rd Hdr Rsp |
|
||||
+ | | | RD Data[1] | RD Data[0] |
|
||||
+ | | | RD Data[3] | RD Data[2] |
|
||||
+ | | | RD HDR[n-2] | RD Data[n-3] |
|
||||
+ | | | pad | RD Data[n-1] |
|
||||
+ | | | pad | RD Data CRC |
|
||||
+ 0 | 1 | | pad | RD Ftr Rsp |
|
||||
+---+---+--------+---------------+---------------+
|
||||
@@ -9,6 +9,8 @@ SoundWire Documentation
|
||||
stream
|
||||
error_handling
|
||||
locking
|
||||
bra
|
||||
bra_cadence
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
||||
@@ -291,7 +291,7 @@ per stream. From ASoC DPCM framework, this stream state maybe linked to
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int sdw_alloc_stream(char * stream_name);
|
||||
int sdw_alloc_stream(char * stream_name, enum sdw_stream_type type);
|
||||
|
||||
The SoundWire core provides a sdw_startup_stream() helper function,
|
||||
typically called during a dailink .startup() callback, which performs
|
||||
|
||||
@@ -184,14 +184,6 @@ function that provides capabilities information. Bus needs to know a set of
|
||||
Slave capabilities to program Slave registers and to control the Bus
|
||||
reconfigurations.
|
||||
|
||||
Future enhancements to be done
|
||||
==============================
|
||||
|
||||
(1) Bulk Register Access (BRA) transfers.
|
||||
|
||||
|
||||
(2) Multiple data lane support.
|
||||
|
||||
Links
|
||||
=====
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ config SOUNDWIRE_AMD
|
||||
|
||||
config SOUNDWIRE_CADENCE
|
||||
tristate
|
||||
select CRC8
|
||||
|
||||
config SOUNDWIRE_INTEL
|
||||
tristate "Intel SoundWire Master driver"
|
||||
|
||||
@@ -143,6 +143,57 @@ static void amd_sdw_wake_enable(struct amd_sdw_manager *amd_manager, bool enable
|
||||
writel(wake_ctrl, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_MASK_8TO11);
|
||||
}
|
||||
|
||||
static int amd_sdw_set_device_state(struct amd_sdw_manager *amd_manager, u32 target_device_state)
|
||||
{
|
||||
u32 sdw_dev_state;
|
||||
|
||||
sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
|
||||
switch (amd_manager->instance) {
|
||||
case ACP_SDW0:
|
||||
u32p_replace_bits(&sdw_dev_state, target_device_state,
|
||||
AMD_SDW0_DEVICE_STATE_MASK);
|
||||
break;
|
||||
case ACP_SDW1:
|
||||
u32p_replace_bits(&sdw_dev_state, target_device_state,
|
||||
AMD_SDW1_DEVICE_STATE_MASK);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
writel(sdw_dev_state, amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
|
||||
sdw_dev_state = readl(amd_manager->acp_mmio + AMD_SDW_DEVICE_STATE);
|
||||
dev_dbg(amd_manager->dev, "AMD_SDW_DEVICE_STATE:0x%x\n", sdw_dev_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int amd_sdw_host_wake_enable(struct amd_sdw_manager *amd_manager, bool enable)
|
||||
{
|
||||
u32 intr_cntl1;
|
||||
u32 sdw_host_wake_irq_mask;
|
||||
|
||||
if (!amd_manager->wake_en_mask)
|
||||
return 0;
|
||||
|
||||
switch (amd_manager->instance) {
|
||||
case ACP_SDW0:
|
||||
sdw_host_wake_irq_mask = AMD_SDW0_HOST_WAKE_INTR_MASK;
|
||||
break;
|
||||
case ACP_SDW1:
|
||||
sdw_host_wake_irq_mask = AMD_SDW1_HOST_WAKE_INTR_MASK;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
intr_cntl1 = readl(amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1));
|
||||
if (enable)
|
||||
intr_cntl1 |= sdw_host_wake_irq_mask;
|
||||
else
|
||||
intr_cntl1 &= ~sdw_host_wake_irq_mask;
|
||||
writel(intr_cntl1, amd_manager->acp_mmio + ACP_EXTERNAL_INTR_CNTL(ACP_SDW1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void amd_sdw_ctl_word_prep(u32 *lower_word, u32 *upper_word, struct sdw_msg *msg,
|
||||
int cmd_offset)
|
||||
{
|
||||
@@ -295,7 +346,7 @@ static enum sdw_command_response amd_sdw_fill_msg_resp(struct amd_sdw_manager *a
|
||||
msg->dev_num);
|
||||
return SDW_CMD_FAIL;
|
||||
}
|
||||
dev_err_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n",
|
||||
dev_dbg_ratelimited(amd_manager->dev, "command is ignored for Slave %d\n",
|
||||
msg->dev_num);
|
||||
return SDW_CMD_IGNORED;
|
||||
}
|
||||
@@ -446,6 +497,10 @@ static int amd_sdw_port_params(struct sdw_bus *bus, struct sdw_port_params *p_pa
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ACP70_PCI_REV_ID:
|
||||
case ACP71_PCI_REV_ID:
|
||||
frame_fmt_reg = acp70_sdw_dp_reg[p_params->num].frame_fmt_reg;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -494,6 +549,14 @@ static int amd_sdw_transport_params(struct sdw_bus *bus,
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ACP70_PCI_REV_ID:
|
||||
case ACP71_PCI_REV_ID:
|
||||
frame_fmt_reg = acp70_sdw_dp_reg[params->port_num].frame_fmt_reg;
|
||||
sample_int_reg = acp70_sdw_dp_reg[params->port_num].sample_int_reg;
|
||||
hctrl_dp0_reg = acp70_sdw_dp_reg[params->port_num].hctrl_dp0_reg;
|
||||
offset_reg = acp70_sdw_dp_reg[params->port_num].offset_reg;
|
||||
lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[params->port_num].lane_ctrl_ch_en_reg;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -549,6 +612,10 @@ static int amd_sdw_port_enable(struct sdw_bus *bus,
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ACP70_PCI_REV_ID:
|
||||
case ACP71_PCI_REV_ID:
|
||||
lane_ctrl_ch_en_reg = acp70_sdw_dp_reg[enable_ch->port_num].lane_ctrl_ch_en_reg;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -849,6 +916,7 @@ static void amd_sdw_update_slave_status(u32 status_change_0to7, u32 status_chang
|
||||
|
||||
static void amd_sdw_process_wake_event(struct amd_sdw_manager *amd_manager)
|
||||
{
|
||||
dev_dbg(amd_manager->dev, "SoundWire Wake event reported\n");
|
||||
pm_request_resume(amd_manager->dev);
|
||||
writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
|
||||
writel(0x00, amd_manager->mmio + ACP_SW_STATE_CHANGE_STATUS_8TO11);
|
||||
@@ -965,6 +1033,11 @@ static int amd_sdw_manager_probe(struct platform_device *pdev)
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case ACP70_PCI_REV_ID:
|
||||
case ACP71_PCI_REV_ID:
|
||||
amd_manager->num_dout_ports = AMD_ACP70_SDW_MAX_TX_PORTS;
|
||||
amd_manager->num_din_ports = AMD_ACP70_SDW_MAX_RX_PORTS;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1137,8 +1210,21 @@ static int __maybe_unused amd_suspend(struct device *dev)
|
||||
|
||||
if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
|
||||
amd_sdw_wake_enable(amd_manager, false);
|
||||
return amd_sdw_clock_stop(amd_manager);
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
ret = amd_sdw_clock_stop(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
|
||||
amd_sdw_wake_enable(amd_manager, false);
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* As per hardware programming sequence on AMD platforms,
|
||||
* clock stop should be invoked first before powering-off
|
||||
@@ -1146,7 +1232,14 @@ static int __maybe_unused amd_suspend(struct device *dev)
|
||||
ret = amd_sdw_clock_stop(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
return amd_deinit_sdw_manager(amd_manager);
|
||||
ret = amd_deinit_sdw_manager(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1156,6 +1249,7 @@ static int __maybe_unused amd_suspend_runtime(struct device *dev)
|
||||
struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev);
|
||||
struct sdw_bus *bus = &amd_manager->bus;
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
if (bus->prop.hw_disabled) {
|
||||
dev_dbg(bus->dev, "SoundWire manager %d is disabled,\n",
|
||||
@@ -1164,12 +1258,40 @@ static int __maybe_unused amd_suspend_runtime(struct device *dev)
|
||||
}
|
||||
if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
|
||||
amd_sdw_wake_enable(amd_manager, true);
|
||||
return amd_sdw_clock_stop(amd_manager);
|
||||
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
ret = amd_sdw_clock_stop(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
return amd_deinit_sdw_manager(amd_manager);
|
||||
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
|
||||
amd_sdw_wake_enable(amd_manager, true);
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, true);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
ret = amd_sdw_clock_stop(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = amd_deinit_sdw_manager(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D3);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (amd_manager->wake_en_mask) {
|
||||
val = readl(amd_manager->acp_mmio + ACP_PME_EN);
|
||||
if (!val) {
|
||||
writel(1, amd_manager->acp_mmio + ACP_PME_EN);
|
||||
val = readl(amd_manager->acp_mmio + ACP_PME_EN);
|
||||
dev_dbg(amd_manager->dev, "ACP_PME_EN:0x%x\n", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1188,9 +1310,21 @@ static int __maybe_unused amd_resume_runtime(struct device *dev)
|
||||
}
|
||||
|
||||
if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) {
|
||||
return amd_sdw_clock_stop_exit(amd_manager);
|
||||
ret = amd_sdw_clock_stop_exit(amd_manager);
|
||||
if (ret)
|
||||
return ret;
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
} else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) {
|
||||
writel(0x00, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance));
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_host_wake_enable(amd_manager, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL);
|
||||
if (val) {
|
||||
val |= AMD_SDW_CLK_RESUME_REQ;
|
||||
@@ -1211,6 +1345,11 @@ static int __maybe_unused amd_resume_runtime(struct device *dev)
|
||||
return ret;
|
||||
amd_sdw_set_frameshape(amd_manager);
|
||||
}
|
||||
if (amd_manager->acp_rev >= ACP70_PCI_REV_ID) {
|
||||
ret = amd_sdw_set_device_state(amd_manager, AMD_SDW_DEVICE_STATE_D0);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,8 +159,11 @@
|
||||
#define AMD_ACP63_SDW0_MAX_RX_PORTS 3
|
||||
#define AMD_ACP63_SDW1_MAX_TX_PORTS 1
|
||||
#define AMD_ACP63_SDW1_MAX_RX_PORTS 1
|
||||
#define AMD_ACP70_SDW_MAX_TX_PORTS 3
|
||||
#define AMD_ACP70_SDW_MAX_RX_PORTS 3
|
||||
#define AMD_ACP63_SDW0_MAX_DAI 6
|
||||
#define AMD_ACP63_SDW1_MAX_DAI 2
|
||||
#define AMD_ACP70_SDW_MAX_DAI 6
|
||||
#define AMD_SDW_SLAVE_0_ATTACHED 5
|
||||
#define AMD_SDW_SSP_COUNTER_VAL 3
|
||||
|
||||
@@ -191,6 +194,14 @@
|
||||
#define AMD_SDW_CLK_RESUME_DONE 3
|
||||
#define AMD_SDW_WAKE_STAT_MASK BIT(16)
|
||||
#define AMD_SDW_WAKE_INTR_MASK BIT(16)
|
||||
#define AMD_SDW0_HOST_WAKE_INTR_MASK BIT(22)
|
||||
#define AMD_SDW1_HOST_WAKE_INTR_MASK BIT(23)
|
||||
#define AMD_SDW_DEVICE_STATE 0x1430
|
||||
#define AMD_SDW0_DEVICE_STATE_MASK GENMASK(1, 0)
|
||||
#define AMD_SDW1_DEVICE_STATE_MASK GENMASK(3, 2)
|
||||
#define AMD_SDW_DEVICE_STATE_D0 0
|
||||
#define AMD_SDW_DEVICE_STATE_D3 3
|
||||
#define ACP_PME_EN 0x0001400
|
||||
|
||||
static u32 amd_sdw_freq_tbl[AMD_SDW_MAX_FREQ_NUM] = {
|
||||
AMD_SDW_DEFAULT_CLK_FREQ,
|
||||
@@ -244,6 +255,21 @@ static struct sdw_manager_dp_reg acp63_sdw1_dp_reg[AMD_ACP63_SDW1_MAX_DAI] = {
|
||||
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0}
|
||||
};
|
||||
|
||||
static struct sdw_manager_dp_reg acp70_sdw_dp_reg[AMD_ACP70_SDW_MAX_DAI] = {
|
||||
{ACP_SW_AUDIO0_TX_FRAME_FORMAT, ACP_SW_AUDIO0_TX_SAMPLEINTERVAL, ACP_SW_AUDIO0_TX_HCTRL_DP0,
|
||||
ACP_SW_AUDIO0_TX_OFFSET_DP0, ACP_SW_AUDIO0_TX_CHANNEL_ENABLE_DP0},
|
||||
{ACP_SW_AUDIO1_TX_FRAME_FORMAT, ACP_SW_AUDIO1_TX_SAMPLEINTERVAL, ACP_SW_AUDIO1_TX_HCTRL,
|
||||
ACP_SW_AUDIO1_TX_OFFSET, ACP_SW_AUDIO1_TX_CHANNEL_ENABLE_DP0},
|
||||
{ACP_SW_AUDIO2_TX_FRAME_FORMAT, ACP_SW_AUDIO2_TX_SAMPLEINTERVAL, ACP_SW_AUDIO2_TX_HCTRL,
|
||||
ACP_SW_AUDIO2_TX_OFFSET, ACP_SW_AUDIO2_TX_CHANNEL_ENABLE_DP0},
|
||||
{ACP_SW_AUDIO0_RX_FRAME_FORMAT, ACP_SW_AUDIO0_RX_SAMPLEINTERVAL, ACP_SW_AUDIO0_RX_HCTRL_DP0,
|
||||
ACP_SW_AUDIO0_RX_OFFSET_DP0, ACP_SW_AUDIO0_RX_CHANNEL_ENABLE_DP0},
|
||||
{ACP_SW_AUDIO1_RX_FRAME_FORMAT, ACP_SW_AUDIO1_RX_SAMPLEINTERVAL, ACP_SW_AUDIO1_RX_HCTRL,
|
||||
ACP_SW_AUDIO1_RX_OFFSET, ACP_SW_AUDIO1_RX_CHANNEL_ENABLE_DP0},
|
||||
{ACP_SW_AUDIO2_RX_FRAME_FORMAT, ACP_SW_AUDIO2_RX_SAMPLEINTERVAL, ACP_SW_AUDIO2_RX_HCTRL,
|
||||
ACP_SW_AUDIO2_RX_OFFSET, ACP_SW_AUDIO2_RX_CHANNEL_ENABLE_DP0},
|
||||
};
|
||||
|
||||
static u32 sdw_manager_reg_mask_array[AMD_SDW_MAX_MANAGER_COUNT] = {
|
||||
AMD_SDW0_EXT_INTR_MASK,
|
||||
AMD_SDW1_EXT_INTR_MASK
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <linux/soundwire/sdw_registers.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include "bus.h"
|
||||
#include "irq.h"
|
||||
#include "sysfs_local.h"
|
||||
@@ -277,7 +278,7 @@ static int sdw_transfer_unlocked(struct sdw_bus *bus, struct sdw_msg *msg)
|
||||
if (ret != 0 && ret != -ENODATA)
|
||||
dev_err(bus->dev, "trf on Slave %d failed:%d %s addr %x count %d\n",
|
||||
msg->dev_num, ret,
|
||||
(msg->flags & SDW_MSG_FLAG_WRITE) ? "write" : "read",
|
||||
str_write_read(msg->flags & SDW_MSG_FLAG_WRITE),
|
||||
msg->addr, msg->len);
|
||||
|
||||
return ret;
|
||||
@@ -1263,7 +1264,7 @@ int sdw_configure_dpn_intr(struct sdw_slave *slave,
|
||||
|
||||
if (slave->bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL) {
|
||||
dev_dbg(&slave->dev, "TEST FAIL interrupt %s\n",
|
||||
enable ? "on" : "off");
|
||||
str_on_off(enable));
|
||||
mask |= SDW_DPN_INT_TEST_FAIL;
|
||||
}
|
||||
|
||||
@@ -2038,3 +2039,46 @@ void sdw_clear_slave_status(struct sdw_bus *bus, u32 request)
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_clear_slave_status);
|
||||
|
||||
int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
|
||||
{
|
||||
if (msg->len > SDW_BPT_MSG_MAX_BYTES) {
|
||||
dev_err(bus->dev, "Invalid BPT message length %d\n", msg->len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* check device is enumerated */
|
||||
if (slave->dev_num == SDW_ENUM_DEV_NUM ||
|
||||
slave->dev_num > SDW_MAX_DEVICES) {
|
||||
dev_err(&slave->dev, "Invalid device number %d\n", slave->dev_num);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* make sure all callbacks are defined */
|
||||
if (!bus->ops->bpt_send_async ||
|
||||
!bus->ops->bpt_wait) {
|
||||
dev_err(bus->dev, "BPT callbacks not defined\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return bus->ops->bpt_send_async(bus, slave, msg);
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_bpt_send_async);
|
||||
|
||||
int sdw_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
|
||||
{
|
||||
return bus->ops->bpt_wait(bus, slave, msg);
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_bpt_wait);
|
||||
|
||||
int sdw_bpt_send_sync(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = sdw_bpt_send_async(bus, slave, msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return sdw_bpt_wait(bus, slave, msg);
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_bpt_send_sync);
|
||||
|
||||
@@ -72,6 +72,24 @@ struct sdw_msg {
|
||||
bool page;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sdw_btp_msg - Message structure
|
||||
* @addr: Start Register address accessed in the Slave
|
||||
* @len: number of bytes to transfer. More than 64Kb can be transferred
|
||||
* but a practical limit of SDW_BPT_MSG_MAX_BYTES is enforced.
|
||||
* @dev_num: Slave device number
|
||||
* @flags: transfer flags, indicate if xfer is read or write
|
||||
* @buf: message data buffer (filled by host for write, filled
|
||||
* by Peripheral hardware for reads)
|
||||
*/
|
||||
struct sdw_bpt_msg {
|
||||
u32 addr;
|
||||
u32 len;
|
||||
u8 dev_num;
|
||||
u8 flags;
|
||||
u8 *buf;
|
||||
};
|
||||
|
||||
#define SDW_DOUBLE_RATE_FACTOR 2
|
||||
#define SDW_STRM_RATE_GROUPING 1
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/crc8.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/debugfs.h>
|
||||
@@ -184,6 +185,7 @@ MODULE_PARM_DESC(cdns_mcp_int_mask, "Cadence MCP IntMask");
|
||||
#define CDNS_PORTCTRL_TEST_FAILED BIT(1)
|
||||
#define CDNS_PORTCTRL_DIRN BIT(7)
|
||||
#define CDNS_PORTCTRL_BANK_INVERT BIT(8)
|
||||
#define CDNS_PORTCTRL_BULK_ENABLE BIT(16)
|
||||
|
||||
#define CDNS_PORT_OFFSET 0x80
|
||||
|
||||
@@ -1341,7 +1343,7 @@ static u32 cdns_set_initial_frame_shape(int n_rows, int n_cols)
|
||||
return val;
|
||||
}
|
||||
|
||||
static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
|
||||
static int cdns_init_clock_ctrl(struct sdw_cdns *cdns)
|
||||
{
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
struct sdw_master_prop *prop = &bus->prop;
|
||||
@@ -1355,14 +1357,25 @@ static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
|
||||
prop->default_row,
|
||||
prop->default_col);
|
||||
|
||||
if (!prop->default_frame_rate || !prop->default_row) {
|
||||
dev_err(cdns->dev, "Default frame_rate %d or row %d is invalid\n",
|
||||
prop->default_frame_rate, prop->default_row);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Set clock divider */
|
||||
divider = (prop->mclk_freq / prop->max_clk_freq) - 1;
|
||||
divider = (prop->mclk_freq * SDW_DOUBLE_RATE_FACTOR /
|
||||
bus->params.curr_dr_freq) - 1;
|
||||
|
||||
cdns_updatel(cdns, CDNS_MCP_CLK_CTRL0,
|
||||
CDNS_MCP_CLK_MCLKD_MASK, divider);
|
||||
cdns_updatel(cdns, CDNS_MCP_CLK_CTRL1,
|
||||
CDNS_MCP_CLK_MCLKD_MASK, divider);
|
||||
|
||||
/* Set frame shape base on the actual bus frequency. */
|
||||
prop->default_col = bus->params.curr_dr_freq /
|
||||
prop->default_frame_rate / prop->default_row;
|
||||
|
||||
/*
|
||||
* Frame shape changes after initialization have to be done
|
||||
* with the bank switch mechanism
|
||||
@@ -1375,6 +1388,8 @@ static void cdns_init_clock_ctrl(struct sdw_cdns *cdns)
|
||||
ssp_interval = prop->default_frame_rate / SDW_CADENCE_GSYNC_HZ;
|
||||
cdns_writel(cdns, CDNS_MCP_SSP_CTRL0, ssp_interval);
|
||||
cdns_writel(cdns, CDNS_MCP_SSP_CTRL1, ssp_interval);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1408,9 +1423,12 @@ EXPORT_SYMBOL(sdw_cdns_soft_reset);
|
||||
*/
|
||||
int sdw_cdns_init(struct sdw_cdns *cdns)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
cdns_init_clock_ctrl(cdns);
|
||||
ret = cdns_init_clock_ctrl(cdns);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
sdw_cdns_check_self_clearing_bits(cdns, __func__, false, 0);
|
||||
|
||||
@@ -1901,13 +1919,20 @@ void sdw_cdns_config_stream(struct sdw_cdns *cdns,
|
||||
|
||||
if (cdns->bus.params.m_data_mode != SDW_PORT_DATA_MODE_NORMAL)
|
||||
val |= CDNS_PORTCTRL_TEST_FAILED;
|
||||
} else if (pdi->num == 0 || pdi->num == 1) {
|
||||
val |= CDNS_PORTCTRL_BULK_ENABLE;
|
||||
}
|
||||
offset = CDNS_PORTCTRL + pdi->num * CDNS_PORT_OFFSET;
|
||||
cdns_updatel(cdns, offset,
|
||||
CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED,
|
||||
CDNS_PORTCTRL_DIRN | CDNS_PORTCTRL_TEST_FAILED |
|
||||
CDNS_PORTCTRL_BULK_ENABLE,
|
||||
val);
|
||||
|
||||
val = pdi->num;
|
||||
/* The DataPort0 needs to be mapped to both PDI0 and PDI1 ! */
|
||||
if (pdi->num == 1)
|
||||
val = 0;
|
||||
else
|
||||
val = pdi->num;
|
||||
val |= CDNS_PDI_CONFIG_SOFT_RESET;
|
||||
val |= FIELD_PREP(CDNS_PDI_CONFIG_CHANNEL, (1 << ch) - 1);
|
||||
cdns_writel(cdns, CDNS_PDI_CONFIG(pdi->num), val);
|
||||
@@ -1952,5 +1977,638 @@ struct sdw_cdns_pdi *sdw_cdns_alloc_pdi(struct sdw_cdns *cdns,
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_alloc_pdi);
|
||||
|
||||
/*
|
||||
* the MIPI SoundWire CRC8 polynomial is X^8 + X^6 + X^3 + X^2 + 1, MSB first
|
||||
* The value is (1)01001101 = 0x4D
|
||||
*
|
||||
* the table below was generated with
|
||||
*
|
||||
* u8 crc8_lookup_table[CRC8_TABLE_SIZE];
|
||||
* crc8_populate_msb(crc8_lookup_table, SDW_CRC8_POLY);
|
||||
*
|
||||
*/
|
||||
#define SDW_CRC8_SEED 0xFF
|
||||
#define SDW_CRC8_POLY 0x4D
|
||||
|
||||
static const u8 sdw_crc8_lookup_msb[CRC8_TABLE_SIZE] = {
|
||||
0x00, 0x4d, 0x9a, 0xd7, 0x79, 0x34, 0xe3, 0xae, /* 0 - 7 */
|
||||
0xf2, 0xbf, 0x68, 0x25, 0x8b, 0xc6, 0x11, 0x5c, /* 8 -15 */
|
||||
0xa9, 0xe4, 0x33, 0x7e, 0xd0, 0x9d, 0x4a, 0x07, /* 16 - 23 */
|
||||
0x5b, 0x16, 0xc1, 0x8c, 0x22, 0x6f, 0xb8, 0xf5, /* 24 - 31 */
|
||||
0x1f, 0x52, 0x85, 0xc8, 0x66, 0x2b, 0xfc, 0xb1, /* 32 - 39 */
|
||||
0xed, 0xa0, 0x77, 0x3a, 0x94, 0xd9, 0x0e, 0x43, /* 40 - 47 */
|
||||
0xb6, 0xfb, 0x2c, 0x61, 0xcf, 0x82, 0x55, 0x18, /* 48 - 55 */
|
||||
0x44, 0x09, 0xde, 0x93, 0x3d, 0x70, 0xa7, 0xea, /* 56 - 63 */
|
||||
0x3e, 0x73, 0xa4, 0xe9, 0x47, 0x0a, 0xdd, 0x90, /* 64 - 71 */
|
||||
0xcc, 0x81, 0x56, 0x1b, 0xb5, 0xf8, 0x2f, 0x62, /* 72 - 79 */
|
||||
0x97, 0xda, 0x0d, 0x40, 0xee, 0xa3, 0x74, 0x39, /* 80 - 87 */
|
||||
0x65, 0x28, 0xff, 0xb2, 0x1c, 0x51, 0x86, 0xcb, /* 88 - 95 */
|
||||
0x21, 0x6c, 0xbb, 0xf6, 0x58, 0x15, 0xc2, 0x8f, /* 96 - 103 */
|
||||
0xd3, 0x9e, 0x49, 0x04, 0xaa, 0xe7, 0x30, 0x7d, /* 104 - 111 */
|
||||
0x88, 0xc5, 0x12, 0x5f, 0xf1, 0xbc, 0x6b, 0x26, /* 112 - 119 */
|
||||
0x7a, 0x37, 0xe0, 0xad, 0x03, 0x4e, 0x99, 0xd4, /* 120 - 127 */
|
||||
0x7c, 0x31, 0xe6, 0xab, 0x05, 0x48, 0x9f, 0xd2, /* 128 - 135 */
|
||||
0x8e, 0xc3, 0x14, 0x59, 0xf7, 0xba, 0x6d, 0x20, /* 136 - 143 */
|
||||
0xd5, 0x98, 0x4f, 0x02, 0xac, 0xe1, 0x36, 0x7b, /* 144 - 151 */
|
||||
0x27, 0x6a, 0xbd, 0xf0, 0x5e, 0x13, 0xc4, 0x89, /* 152 - 159 */
|
||||
0x63, 0x2e, 0xf9, 0xb4, 0x1a, 0x57, 0x80, 0xcd, /* 160 - 167 */
|
||||
0x91, 0xdc, 0x0b, 0x46, 0xe8, 0xa5, 0x72, 0x3f, /* 168 - 175 */
|
||||
0xca, 0x87, 0x50, 0x1d, 0xb3, 0xfe, 0x29, 0x64, /* 176 - 183 */
|
||||
0x38, 0x75, 0xa2, 0xef, 0x41, 0x0c, 0xdb, 0x96, /* 184 - 191 */
|
||||
0x42, 0x0f, 0xd8, 0x95, 0x3b, 0x76, 0xa1, 0xec, /* 192 - 199 */
|
||||
0xb0, 0xfd, 0x2a, 0x67, 0xc9, 0x84, 0x53, 0x1e, /* 200 - 207 */
|
||||
0xeb, 0xa6, 0x71, 0x3c, 0x92, 0xdf, 0x08, 0x45, /* 208 - 215 */
|
||||
0x19, 0x54, 0x83, 0xce, 0x60, 0x2d, 0xfa, 0xb7, /* 216 - 223 */
|
||||
0x5d, 0x10, 0xc7, 0x8a, 0x24, 0x69, 0xbe, 0xf3, /* 224 - 231 */
|
||||
0xaf, 0xe2, 0x35, 0x78, 0xd6, 0x9b, 0x4c, 0x01, /* 232 - 239 */
|
||||
0xf4, 0xb9, 0x6e, 0x23, 0x8d, 0xc0, 0x17, 0x5a, /* 240 - 247 */
|
||||
0x06, 0x4b, 0x9c, 0xd1, 0x7f, 0x32, 0xe5, 0xa8 /* 248 - 255 */
|
||||
};
|
||||
|
||||
/* BPT/BRA helpers */
|
||||
|
||||
#define SDW_CDNS_BRA_HDR 6 /* defined by MIPI */
|
||||
#define SDW_CDNS_BRA_HDR_CRC 1 /* defined by MIPI */
|
||||
#define SDW_CDNS_BRA_HDR_CRC_PAD 1 /* Cadence only */
|
||||
#define SDW_CDNS_BRA_HDR_RESP 1 /* defined by MIPI */
|
||||
#define SDW_CDNS_BRA_HDR_RESP_PAD 1 /* Cadence only */
|
||||
|
||||
#define SDW_CDNS_BRA_DATA_PAD 1 /* Cadence only */
|
||||
#define SDW_CDNS_BRA_DATA_CRC 1 /* defined by MIPI */
|
||||
#define SDW_CDNS_BRA_DATA_CRC_PAD 1 /* Cadence only */
|
||||
|
||||
#define SDW_CDNS_BRA_FOOTER_RESP 1 /* defined by MIPI */
|
||||
#define SDW_CDNS_BRA_FOOTER_RESP_PAD 1 /* Cadence only */
|
||||
|
||||
#define SDW_CDNS_WRITE_PDI1_BUFFER_SIZE \
|
||||
((SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD + \
|
||||
SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD) * 2)
|
||||
|
||||
#define SDW_CDNS_READ_PDI0_BUFFER_SIZE \
|
||||
((SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD) * 2)
|
||||
|
||||
static unsigned int sdw_cdns_bra_actual_data_size(unsigned int allocated_bytes_per_frame)
|
||||
{
|
||||
unsigned int total;
|
||||
|
||||
if (allocated_bytes_per_frame < (SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC +
|
||||
SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_DATA_CRC +
|
||||
SDW_CDNS_BRA_FOOTER_RESP))
|
||||
return 0;
|
||||
|
||||
total = allocated_bytes_per_frame - SDW_CDNS_BRA_HDR - SDW_CDNS_BRA_HDR_CRC -
|
||||
SDW_CDNS_BRA_HDR_RESP - SDW_CDNS_BRA_DATA_CRC - SDW_CDNS_BRA_FOOTER_RESP;
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
static unsigned int sdw_cdns_write_pdi0_buffer_size(unsigned int actual_data_size)
|
||||
{
|
||||
unsigned int total;
|
||||
|
||||
total = SDW_CDNS_BRA_HDR + SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD;
|
||||
|
||||
total += actual_data_size;
|
||||
if (actual_data_size & 1)
|
||||
total += SDW_CDNS_BRA_DATA_PAD;
|
||||
|
||||
total += SDW_CDNS_BRA_DATA_CRC + SDW_CDNS_BRA_DATA_CRC_PAD;
|
||||
|
||||
return total * 2;
|
||||
}
|
||||
|
||||
static unsigned int sdw_cdns_read_pdi1_buffer_size(unsigned int actual_data_size)
|
||||
{
|
||||
unsigned int total;
|
||||
|
||||
total = SDW_CDNS_BRA_HDR_RESP + SDW_CDNS_BRA_HDR_RESP_PAD;
|
||||
|
||||
total += actual_data_size;
|
||||
if (actual_data_size & 1)
|
||||
total += SDW_CDNS_BRA_DATA_PAD;
|
||||
|
||||
total += SDW_CDNS_BRA_HDR_CRC + SDW_CDNS_BRA_HDR_CRC_PAD;
|
||||
|
||||
total += SDW_CDNS_BRA_FOOTER_RESP + SDW_CDNS_BRA_FOOTER_RESP_PAD;
|
||||
|
||||
return total * 2;
|
||||
}
|
||||
|
||||
int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */
|
||||
int row, int col, unsigned int data_bytes,
|
||||
unsigned int requested_bytes_per_frame,
|
||||
unsigned int *data_per_frame, unsigned int *pdi0_buffer_size,
|
||||
unsigned int *pdi1_buffer_size, unsigned int *num_frames)
|
||||
{
|
||||
unsigned int bpt_bits = row * (col - 1);
|
||||
unsigned int bpt_bytes = bpt_bits >> 3;
|
||||
unsigned int actual_bpt_bytes;
|
||||
unsigned int pdi0_tx_size;
|
||||
unsigned int pdi1_rx_size;
|
||||
unsigned int remainder;
|
||||
|
||||
if (!data_bytes)
|
||||
return -EINVAL;
|
||||
|
||||
actual_bpt_bytes = sdw_cdns_bra_actual_data_size(bpt_bytes);
|
||||
if (!actual_bpt_bytes)
|
||||
return -EINVAL;
|
||||
|
||||
if (data_bytes < actual_bpt_bytes)
|
||||
actual_bpt_bytes = data_bytes;
|
||||
|
||||
/*
|
||||
* the caller may want to set the number of bytes per frame,
|
||||
* allow when possible
|
||||
*/
|
||||
if (requested_bytes_per_frame < actual_bpt_bytes)
|
||||
actual_bpt_bytes = requested_bytes_per_frame;
|
||||
|
||||
*data_per_frame = actual_bpt_bytes;
|
||||
|
||||
if (command == 0) {
|
||||
/*
|
||||
* for writes we need to send all the data_bytes per frame,
|
||||
* even for the last frame which may only transport fewer bytes
|
||||
*/
|
||||
|
||||
*num_frames = DIV_ROUND_UP(data_bytes, actual_bpt_bytes);
|
||||
|
||||
pdi0_tx_size = sdw_cdns_write_pdi0_buffer_size(actual_bpt_bytes);
|
||||
pdi1_rx_size = SDW_CDNS_WRITE_PDI1_BUFFER_SIZE;
|
||||
|
||||
*pdi0_buffer_size = pdi0_tx_size * *num_frames;
|
||||
*pdi1_buffer_size = pdi1_rx_size * *num_frames;
|
||||
} else {
|
||||
/*
|
||||
* for reads we need to retrieve only what is requested in the BPT
|
||||
* header, so the last frame needs to be special-cased
|
||||
*/
|
||||
*num_frames = data_bytes / actual_bpt_bytes;
|
||||
|
||||
pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE;
|
||||
pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(actual_bpt_bytes);
|
||||
|
||||
*pdi0_buffer_size = pdi0_tx_size * *num_frames;
|
||||
*pdi1_buffer_size = pdi1_rx_size * *num_frames;
|
||||
|
||||
remainder = data_bytes % actual_bpt_bytes;
|
||||
if (remainder) {
|
||||
pdi0_tx_size = SDW_CDNS_READ_PDI0_BUFFER_SIZE;
|
||||
pdi1_rx_size = sdw_cdns_read_pdi1_buffer_size(remainder);
|
||||
|
||||
*num_frames = *num_frames + 1;
|
||||
*pdi0_buffer_size += pdi0_tx_size;
|
||||
*pdi1_buffer_size += pdi1_rx_size;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_bpt_find_buffer_sizes);
|
||||
|
||||
static int sdw_cdns_copy_write_data(u8 *data, int data_size, u8 *dma_buffer, int dma_buffer_size)
|
||||
{
|
||||
/*
|
||||
* the implementation copies the data one byte at a time. Experiments with
|
||||
* two bytes at a time did not seem to improve the performance
|
||||
*/
|
||||
int i, j;
|
||||
|
||||
/* size check to prevent out of bounds access */
|
||||
i = data_size - 1;
|
||||
j = (2 * i) - (i & 1);
|
||||
if (data_size & 1)
|
||||
j++;
|
||||
j += 2;
|
||||
if (j >= dma_buffer_size)
|
||||
return -EINVAL;
|
||||
|
||||
/* copy data */
|
||||
for (i = 0; i < data_size; i++) {
|
||||
j = (2 * i) - (i & 1);
|
||||
dma_buffer[j] = data[i];
|
||||
}
|
||||
/* add required pad */
|
||||
if (data_size & 1)
|
||||
dma_buffer[++j] = 0;
|
||||
/* skip last two bytes */
|
||||
j += 2;
|
||||
|
||||
/* offset and data are off-by-one */
|
||||
return j + 1;
|
||||
}
|
||||
|
||||
static int sdw_cdns_prepare_write_pd0_buffer(u8 *header, unsigned int header_size,
|
||||
u8 *data, unsigned int data_size,
|
||||
u8 *dma_buffer, unsigned int dma_buffer_size,
|
||||
unsigned int *dma_data_written,
|
||||
unsigned int frame_counter)
|
||||
{
|
||||
int data_written;
|
||||
u8 *last_byte;
|
||||
u8 crc;
|
||||
|
||||
*dma_data_written = 0;
|
||||
|
||||
data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer[3] = BIT(7);
|
||||
dma_buffer[3] |= frame_counter & GENMASK(3, 0);
|
||||
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
crc = SDW_CRC8_SEED;
|
||||
crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc);
|
||||
|
||||
data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
data_written = sdw_cdns_copy_write_data(data, data_size, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
crc = SDW_CRC8_SEED;
|
||||
crc = crc8(sdw_crc8_lookup_msb, data, data_size, crc);
|
||||
data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
/* tag last byte */
|
||||
last_byte = dma_buffer - 1;
|
||||
last_byte[0] = BIT(6);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sdw_cdns_prepare_read_pd0_buffer(u8 *header, unsigned int header_size,
|
||||
u8 *dma_buffer, unsigned int dma_buffer_size,
|
||||
unsigned int *dma_data_written,
|
||||
unsigned int frame_counter)
|
||||
{
|
||||
int data_written;
|
||||
u8 *last_byte;
|
||||
u8 crc;
|
||||
|
||||
*dma_data_written = 0;
|
||||
|
||||
data_written = sdw_cdns_copy_write_data(header, header_size, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer[3] = BIT(7);
|
||||
dma_buffer[3] |= frame_counter & GENMASK(3, 0);
|
||||
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
crc = SDW_CRC8_SEED;
|
||||
crc = crc8(sdw_crc8_lookup_msb, header, header_size, crc);
|
||||
|
||||
data_written = sdw_cdns_copy_write_data(&crc, 1, dma_buffer, dma_buffer_size);
|
||||
if (data_written < 0)
|
||||
return data_written;
|
||||
dma_buffer += data_written;
|
||||
dma_buffer_size -= data_written;
|
||||
*dma_data_written += data_written;
|
||||
|
||||
/* tag last byte */
|
||||
last_byte = dma_buffer - 1;
|
||||
last_byte[0] = BIT(6);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define CDNS_BPT_ROLLING_COUNTER_START 1
|
||||
|
||||
int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size,
|
||||
int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
|
||||
int *dma_buffer_total_bytes)
|
||||
{
|
||||
int total_dma_data_written = 0;
|
||||
u8 *p_dma_buffer = dma_buffer;
|
||||
u8 header[SDW_CDNS_BRA_HDR];
|
||||
int dma_data_written;
|
||||
u8 *p_data = data;
|
||||
u8 counter;
|
||||
int ret;
|
||||
|
||||
counter = CDNS_BPT_ROLLING_COUNTER_START;
|
||||
|
||||
header[0] = BIT(1); /* write command: BIT(1) set */
|
||||
header[0] |= GENMASK(7, 6); /* header is active */
|
||||
header[0] |= (dev_num << 2);
|
||||
|
||||
while (data_size >= data_per_frame) {
|
||||
header[1] = data_per_frame;
|
||||
header[2] = start_register >> 24 & 0xFF;
|
||||
header[3] = start_register >> 16 & 0xFF;
|
||||
header[4] = start_register >> 8 & 0xFF;
|
||||
header[5] = start_register >> 0 & 0xFF;
|
||||
|
||||
ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR,
|
||||
p_data, data_per_frame,
|
||||
p_dma_buffer, dma_buffer_size,
|
||||
&dma_data_written, counter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
counter++;
|
||||
|
||||
p_data += data_per_frame;
|
||||
data_size -= data_per_frame;
|
||||
|
||||
p_dma_buffer += dma_data_written;
|
||||
dma_buffer_size -= dma_data_written;
|
||||
total_dma_data_written += dma_data_written;
|
||||
|
||||
start_register += data_per_frame;
|
||||
}
|
||||
|
||||
if (data_size) {
|
||||
header[1] = data_size;
|
||||
header[2] = start_register >> 24 & 0xFF;
|
||||
header[3] = start_register >> 16 & 0xFF;
|
||||
header[4] = start_register >> 8 & 0xFF;
|
||||
header[5] = start_register >> 0 & 0xFF;
|
||||
|
||||
ret = sdw_cdns_prepare_write_pd0_buffer(header, SDW_CDNS_BRA_HDR,
|
||||
p_data, data_size,
|
||||
p_dma_buffer, dma_buffer_size,
|
||||
&dma_data_written, counter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
total_dma_data_written += dma_data_written;
|
||||
}
|
||||
|
||||
*dma_buffer_total_bytes = total_dma_data_written;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_prepare_write_dma_buffer);
|
||||
|
||||
int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size,
|
||||
int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
|
||||
int *dma_buffer_total_bytes)
|
||||
{
|
||||
int total_dma_data_written = 0;
|
||||
u8 *p_dma_buffer = dma_buffer;
|
||||
u8 header[SDW_CDNS_BRA_HDR];
|
||||
int dma_data_written;
|
||||
u8 counter;
|
||||
int ret;
|
||||
|
||||
counter = CDNS_BPT_ROLLING_COUNTER_START;
|
||||
|
||||
header[0] = 0; /* read command: BIT(1) cleared */
|
||||
header[0] |= GENMASK(7, 6); /* header is active */
|
||||
header[0] |= (dev_num << 2);
|
||||
|
||||
while (data_size >= data_per_frame) {
|
||||
header[1] = data_per_frame;
|
||||
header[2] = start_register >> 24 & 0xFF;
|
||||
header[3] = start_register >> 16 & 0xFF;
|
||||
header[4] = start_register >> 8 & 0xFF;
|
||||
header[5] = start_register >> 0 & 0xFF;
|
||||
|
||||
ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer,
|
||||
dma_buffer_size, &dma_data_written,
|
||||
counter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
counter++;
|
||||
|
||||
data_size -= data_per_frame;
|
||||
|
||||
p_dma_buffer += dma_data_written;
|
||||
dma_buffer_size -= dma_data_written;
|
||||
total_dma_data_written += dma_data_written;
|
||||
|
||||
start_register += data_per_frame;
|
||||
}
|
||||
|
||||
if (data_size) {
|
||||
header[1] = data_size;
|
||||
header[2] = start_register >> 24 & 0xFF;
|
||||
header[3] = start_register >> 16 & 0xFF;
|
||||
header[4] = start_register >> 8 & 0xFF;
|
||||
header[5] = start_register >> 0 & 0xFF;
|
||||
|
||||
ret = sdw_cdns_prepare_read_pd0_buffer(header, SDW_CDNS_BRA_HDR, p_dma_buffer,
|
||||
dma_buffer_size, &dma_data_written,
|
||||
counter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
total_dma_data_written += dma_data_written;
|
||||
}
|
||||
|
||||
*dma_buffer_total_bytes = total_dma_data_written;
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_prepare_read_dma_buffer);
|
||||
|
||||
static int check_counter(u32 val, u8 counter)
|
||||
{
|
||||
u8 frame;
|
||||
|
||||
frame = (val >> 24) & GENMASK(3, 0);
|
||||
if (counter != frame)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_response(u32 val)
|
||||
{
|
||||
u8 response;
|
||||
|
||||
response = (val >> 3) & GENMASK(1, 0);
|
||||
if (response == 0) /* Ignored */
|
||||
return -ENODATA;
|
||||
if (response != 1) /* ACK */
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int check_frame_start(u32 header, u8 counter)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* check frame_start marker */
|
||||
if (!(header & BIT(31)))
|
||||
return -EIO;
|
||||
|
||||
ret = check_counter(header, counter);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return check_response(header);
|
||||
}
|
||||
|
||||
static int check_frame_end(u32 footer)
|
||||
{
|
||||
/* check frame_end marker */
|
||||
if (!(footer & BIT(30)))
|
||||
return -EIO;
|
||||
|
||||
return check_response(footer);
|
||||
}
|
||||
|
||||
int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer,
|
||||
int dma_buffer_size, int num_frames)
|
||||
{
|
||||
u32 *p_data;
|
||||
int counter;
|
||||
u32 header;
|
||||
u32 footer;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
/* paranoia check on buffer size */
|
||||
if (dma_buffer_size != num_frames * 8)
|
||||
return -EINVAL;
|
||||
|
||||
counter = CDNS_BPT_ROLLING_COUNTER_START;
|
||||
p_data = (u32 *)dma_buffer;
|
||||
|
||||
for (i = 0; i < num_frames; i++) {
|
||||
header = *p_data++;
|
||||
footer = *p_data++;
|
||||
|
||||
ret = check_frame_start(header, counter);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: bad frame %d/%d start header %x\n",
|
||||
__func__, i, num_frames, header);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = check_frame_end(footer);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: bad frame %d/%d end footer %x\n",
|
||||
__func__, i, num_frames, footer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
counter++;
|
||||
counter &= GENMASK(3, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_check_write_response);
|
||||
|
||||
static u8 extract_read_data(u32 *data, int num_bytes, u8 *buffer)
|
||||
{
|
||||
u32 val;
|
||||
int i;
|
||||
u8 crc;
|
||||
u8 b0;
|
||||
u8 b1;
|
||||
|
||||
crc = SDW_CRC8_SEED;
|
||||
|
||||
/* process two bytes at a time */
|
||||
for (i = 0; i < num_bytes / 2; i++) {
|
||||
val = *data++;
|
||||
|
||||
b0 = val & 0xff;
|
||||
b1 = (val >> 8) & 0xff;
|
||||
|
||||
*buffer++ = b0;
|
||||
crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc);
|
||||
|
||||
*buffer++ = b1;
|
||||
crc = crc8(sdw_crc8_lookup_msb, &b1, 1, crc);
|
||||
}
|
||||
/* handle remaining byte if it exists */
|
||||
if (num_bytes & 1) {
|
||||
val = *data;
|
||||
|
||||
b0 = val & 0xff;
|
||||
|
||||
*buffer++ = b0;
|
||||
crc = crc8(sdw_crc8_lookup_msb, &b0, 1, crc);
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size,
|
||||
u8 *buffer, int buffer_size, int num_frames, int data_per_frame)
|
||||
{
|
||||
int total_num_bytes = 0;
|
||||
u32 *p_data;
|
||||
u8 *p_buf;
|
||||
int counter;
|
||||
u32 header;
|
||||
u32 footer;
|
||||
u8 expected_crc;
|
||||
u8 crc;
|
||||
int len;
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
counter = CDNS_BPT_ROLLING_COUNTER_START;
|
||||
p_data = (u32 *)dma_buffer;
|
||||
p_buf = buffer;
|
||||
|
||||
for (i = 0; i < num_frames; i++) {
|
||||
header = *p_data++;
|
||||
|
||||
ret = check_frame_start(header, counter);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: bad frame %d/%d start header %x\n",
|
||||
__func__, i, num_frames, header);
|
||||
return ret;
|
||||
}
|
||||
|
||||
len = data_per_frame;
|
||||
if (total_num_bytes + data_per_frame > buffer_size)
|
||||
len = buffer_size - total_num_bytes;
|
||||
|
||||
crc = extract_read_data(p_data, len, p_buf);
|
||||
|
||||
p_data += (len + 1) / 2;
|
||||
expected_crc = *p_data++ & 0xff;
|
||||
|
||||
if (crc != expected_crc) {
|
||||
dev_err(dev, "%s: bad frame %d/%d crc %#x expected %#x\n",
|
||||
__func__, i, num_frames, crc, expected_crc);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
p_buf += len;
|
||||
total_num_bytes += len;
|
||||
|
||||
footer = *p_data++;
|
||||
ret = check_frame_end(footer);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: bad frame %d/%d end footer %x\n",
|
||||
__func__, i, num_frames, footer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
counter++;
|
||||
counter &= GENMASK(3, 0);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_cdns_check_read_response);
|
||||
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("Cadence Soundwire Library");
|
||||
|
||||
@@ -208,4 +208,24 @@ void sdw_cdns_check_self_clearing_bits(struct sdw_cdns *cdns, const char *string
|
||||
void sdw_cdns_config_update(struct sdw_cdns *cdns);
|
||||
int sdw_cdns_config_update_set_wait(struct sdw_cdns *cdns);
|
||||
|
||||
/* SoundWire BPT/BRA helpers to format data */
|
||||
int sdw_cdns_bpt_find_buffer_sizes(int command, /* 0: write, 1: read */
|
||||
int row, int col, unsigned int data_bytes,
|
||||
unsigned int requested_bytes_per_frame,
|
||||
unsigned int *data_per_frame, unsigned int *pdi0_buffer_size,
|
||||
unsigned int *pdi1_buffer_size, unsigned int *num_frames);
|
||||
|
||||
int sdw_cdns_prepare_write_dma_buffer(u8 dev_num, u32 start_register, u8 *data, int data_size,
|
||||
int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
|
||||
int *dma_buffer_total_bytes);
|
||||
|
||||
int sdw_cdns_prepare_read_dma_buffer(u8 dev_num, u32 start_register, int data_size,
|
||||
int data_per_frame, u8 *dma_buffer, int dma_buffer_size,
|
||||
int *dma_buffer_total_bytes);
|
||||
|
||||
int sdw_cdns_check_write_response(struct device *dev, u8 *dma_buffer,
|
||||
int dma_buffer_size, int num_frames);
|
||||
|
||||
int sdw_cdns_check_read_response(struct device *dev, u8 *dma_buffer, int dma_buffer_size,
|
||||
u8 *buffer, int buffer_size, int num_frames, int data_per_frame);
|
||||
#endif /* __SDW_CADENCE_H */
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_registers.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include "bus.h"
|
||||
|
||||
static struct dentry *sdw_debugfs_root;
|
||||
@@ -135,9 +136,10 @@ static int sdw_slave_reg_show(struct seq_file *s_file, void *data)
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(sdw_slave_reg);
|
||||
|
||||
#define MAX_CMD_BYTES 256
|
||||
#define MAX_CMD_BYTES (1024 * 1024)
|
||||
|
||||
static int cmd;
|
||||
static int cmd_type;
|
||||
static u32 start_addr;
|
||||
static size_t num_bytes;
|
||||
static u8 read_buffer[MAX_CMD_BYTES];
|
||||
@@ -153,7 +155,7 @@ static int set_command(void *data, u64 value)
|
||||
/* Userspace changed the hardware state behind the kernel's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
dev_dbg(&slave->dev, "command: %s\n", value ? "read" : "write");
|
||||
dev_dbg(&slave->dev, "command: %s\n", str_read_write(value));
|
||||
cmd = value;
|
||||
|
||||
return 0;
|
||||
@@ -161,6 +163,25 @@ static int set_command(void *data, u64 value)
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(set_command_fops, NULL,
|
||||
set_command, "%llu\n");
|
||||
|
||||
static int set_command_type(void *data, u64 value)
|
||||
{
|
||||
struct sdw_slave *slave = data;
|
||||
|
||||
if (value > 1)
|
||||
return -EINVAL;
|
||||
|
||||
/* Userspace changed the hardware state behind the kernel's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
dev_dbg(&slave->dev, "command type: %s\n", value ? "BRA" : "Column0");
|
||||
|
||||
cmd_type = (int)value;
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(set_command_type_fops, NULL,
|
||||
set_command_type, "%llu\n");
|
||||
|
||||
static int set_start_address(void *data, u64 value)
|
||||
{
|
||||
struct sdw_slave *slave = data;
|
||||
@@ -196,9 +217,28 @@ static int set_num_bytes(void *data, u64 value)
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(set_num_bytes_fops, NULL,
|
||||
set_num_bytes, "%llu\n");
|
||||
|
||||
static int do_bpt_sequence(struct sdw_slave *slave, bool write, u8 *buffer)
|
||||
{
|
||||
struct sdw_bpt_msg msg = {0};
|
||||
|
||||
msg.addr = start_addr;
|
||||
msg.len = num_bytes;
|
||||
msg.dev_num = slave->dev_num;
|
||||
if (write)
|
||||
msg.flags = SDW_MSG_FLAG_WRITE;
|
||||
else
|
||||
msg.flags = SDW_MSG_FLAG_READ;
|
||||
msg.buf = buffer;
|
||||
|
||||
return sdw_bpt_send_sync(slave->bus, slave, &msg);
|
||||
}
|
||||
|
||||
static int cmd_go(void *data, u64 value)
|
||||
{
|
||||
const struct firmware *fw = NULL;
|
||||
struct sdw_slave *slave = data;
|
||||
ktime_t start_t;
|
||||
ktime_t finish_t;
|
||||
int ret;
|
||||
|
||||
if (value != 1)
|
||||
@@ -215,40 +255,52 @@ static int cmd_go(void *data, u64 value)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Userspace changed the hardware state behind the kernel's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
dev_dbg(&slave->dev, "starting command\n");
|
||||
|
||||
if (cmd == 0) {
|
||||
const struct firmware *fw;
|
||||
|
||||
ret = request_firmware(&fw, firmware_file, &slave->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(&slave->dev, "firmware %s not found\n", firmware_file);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (fw->size != num_bytes) {
|
||||
if (fw->size < num_bytes) {
|
||||
dev_err(&slave->dev,
|
||||
"firmware %s: unexpected size %zd, desired %zd\n",
|
||||
"firmware %s: firmware size %zd, desired %zd\n",
|
||||
firmware_file, fw->size, num_bytes);
|
||||
release_firmware(fw);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = sdw_nwrite_no_pm(slave, start_addr, num_bytes, fw->data);
|
||||
release_firmware(fw);
|
||||
} else {
|
||||
ret = sdw_nread_no_pm(slave, start_addr, num_bytes, read_buffer);
|
||||
}
|
||||
|
||||
dev_dbg(&slave->dev, "command completed %d\n", ret);
|
||||
/* Userspace changed the hardware state behind the kernel's back */
|
||||
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
|
||||
|
||||
dev_dbg(&slave->dev, "starting command\n");
|
||||
start_t = ktime_get();
|
||||
|
||||
if (cmd == 0) {
|
||||
if (cmd_type)
|
||||
ret = do_bpt_sequence(slave, true, (u8 *)fw->data);
|
||||
else
|
||||
ret = sdw_nwrite_no_pm(slave, start_addr, num_bytes, fw->data);
|
||||
} else {
|
||||
memset(read_buffer, 0, sizeof(read_buffer));
|
||||
|
||||
if (cmd_type)
|
||||
ret = do_bpt_sequence(slave, false, read_buffer);
|
||||
else
|
||||
ret = sdw_nread_no_pm(slave, start_addr, num_bytes, read_buffer);
|
||||
}
|
||||
|
||||
finish_t = ktime_get();
|
||||
|
||||
out:
|
||||
if (fw)
|
||||
release_firmware(fw);
|
||||
|
||||
pm_runtime_mark_last_busy(&slave->dev);
|
||||
pm_runtime_put(&slave->dev);
|
||||
|
||||
dev_dbg(&slave->dev, "command completed, num_byte %zu status %d, time %lld ms\n",
|
||||
num_bytes, ret, div_u64(finish_t - start_t, NSEC_PER_MSEC));
|
||||
|
||||
return ret;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(cmd_go_fops, NULL,
|
||||
@@ -290,6 +342,7 @@ void sdw_slave_debugfs_init(struct sdw_slave *slave)
|
||||
|
||||
/* interface to send arbitrary commands */
|
||||
debugfs_create_file("command", 0200, d, slave, &set_command_fops);
|
||||
debugfs_create_file("command_type", 0200, d, slave, &set_command_type_fops);
|
||||
debugfs_create_file("start_address", 0200, d, slave, &set_start_address_fops);
|
||||
debugfs_create_file("num_bytes", 0200, d, slave, &set_num_bytes_fops);
|
||||
debugfs_create_file("go", 0200, d, slave, &cmd_go_fops);
|
||||
|
||||
@@ -86,6 +86,49 @@ void sdw_compute_slave_ports(struct sdw_master_runtime *m_rt,
|
||||
}
|
||||
EXPORT_SYMBOL(sdw_compute_slave_ports);
|
||||
|
||||
static void sdw_compute_dp0_slave_ports(struct sdw_master_runtime *m_rt)
|
||||
{
|
||||
struct sdw_bus *bus = m_rt->bus;
|
||||
struct sdw_slave_runtime *s_rt;
|
||||
struct sdw_port_runtime *p_rt;
|
||||
|
||||
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) {
|
||||
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, false,
|
||||
SDW_BLK_GRP_CNT_1, bus->params.col, 0, 0, 1,
|
||||
bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0);
|
||||
|
||||
sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1,
|
||||
SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sdw_compute_dp0_master_ports(struct sdw_master_runtime *m_rt)
|
||||
{
|
||||
struct sdw_port_runtime *p_rt;
|
||||
struct sdw_bus *bus = m_rt->bus;
|
||||
|
||||
list_for_each_entry(p_rt, &m_rt->port_list, port_node) {
|
||||
sdw_fill_xport_params(&p_rt->transport_params, p_rt->num, false,
|
||||
SDW_BLK_GRP_CNT_1, bus->params.col, 0, 0, 1,
|
||||
bus->params.col - 1, SDW_BLK_PKG_PER_PORT, 0x0);
|
||||
|
||||
sdw_fill_port_params(&p_rt->port_params, p_rt->num, bus->params.col - 1,
|
||||
SDW_PORT_FLOW_MODE_ISOCH, SDW_PORT_DATA_MODE_NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdw_compute_dp0_port_params(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_master_runtime *m_rt;
|
||||
|
||||
list_for_each_entry(m_rt, &bus->m_rt_list, bus_node) {
|
||||
sdw_compute_dp0_master_ports(m_rt);
|
||||
sdw_compute_dp0_slave_ports(m_rt);
|
||||
}
|
||||
}
|
||||
|
||||
static void sdw_compute_master_ports(struct sdw_master_runtime *m_rt,
|
||||
struct sdw_group_params *params,
|
||||
int *port_bo, int hstop)
|
||||
@@ -194,10 +237,11 @@ static int sdw_compute_group_params(struct sdw_bus *bus,
|
||||
continue;
|
||||
} else {
|
||||
/*
|
||||
* Include runtimes with running (ENABLED state) and paused (DISABLED state)
|
||||
* streams
|
||||
* Include runtimes with running (ENABLED/PREPARED state) and
|
||||
* paused (DISABLED state) streams
|
||||
*/
|
||||
if (m_rt->stream->state != SDW_STREAM_ENABLED &&
|
||||
m_rt->stream->state != SDW_STREAM_PREPARED &&
|
||||
m_rt->stream->state != SDW_STREAM_DISABLED)
|
||||
continue;
|
||||
}
|
||||
@@ -618,6 +662,11 @@ int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream)
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (stream->type == SDW_STREAM_BPT) {
|
||||
sdw_compute_dp0_port_params(bus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Compute transport and port params */
|
||||
ret = sdw_compute_port_params(bus, stream);
|
||||
if (ret < 0) {
|
||||
|
||||
@@ -48,11 +48,34 @@ struct sdw_intel_link_res {
|
||||
struct hdac_bus *hbus;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sdw_intel_bpt - SoundWire Intel BPT context
|
||||
* @bpt_tx_stream: BPT TX stream
|
||||
* @dmab_tx_bdl: BPT TX buffer descriptor list
|
||||
* @bpt_rx_stream: BPT RX stream
|
||||
* @dmab_rx_bdl: BPT RX buffer descriptor list
|
||||
* @pdi0_buffer_size: PDI0 buffer size
|
||||
* @pdi1_buffer_size: PDI1 buffer size
|
||||
* @num_frames: number of frames
|
||||
* @data_per_frame: data per frame
|
||||
*/
|
||||
struct sdw_intel_bpt {
|
||||
struct hdac_ext_stream *bpt_tx_stream;
|
||||
struct snd_dma_buffer dmab_tx_bdl;
|
||||
struct hdac_ext_stream *bpt_rx_stream;
|
||||
struct snd_dma_buffer dmab_rx_bdl;
|
||||
unsigned int pdi0_buffer_size;
|
||||
unsigned int pdi1_buffer_size;
|
||||
unsigned int num_frames;
|
||||
unsigned int data_per_frame;
|
||||
};
|
||||
|
||||
struct sdw_intel {
|
||||
struct sdw_cdns cdns;
|
||||
int instance;
|
||||
struct sdw_intel_link_res *link_res;
|
||||
bool startup_done;
|
||||
struct sdw_intel_bpt bpt_ctx;
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
struct dentry *debugfs;
|
||||
#endif
|
||||
|
||||
@@ -13,12 +13,320 @@
|
||||
#include <linux/soundwire/sdw_intel.h>
|
||||
#include <sound/hdaudio.h>
|
||||
#include <sound/hda-mlink.h>
|
||||
#include <sound/hda-sdw-bpt.h>
|
||||
#include <sound/hda_register.h>
|
||||
#include <sound/pcm_params.h>
|
||||
#include "cadence_master.h"
|
||||
#include "bus.h"
|
||||
#include "intel.h"
|
||||
|
||||
static int sdw_slave_bpt_stream_add(struct sdw_slave *slave, struct sdw_stream_runtime *stream)
|
||||
{
|
||||
struct sdw_stream_config sconfig = {0};
|
||||
struct sdw_port_config pconfig = {0};
|
||||
int ret;
|
||||
|
||||
/* arbitrary configuration */
|
||||
sconfig.frame_rate = 16000;
|
||||
sconfig.ch_count = 1;
|
||||
sconfig.bps = 32; /* this is required for BPT/BRA */
|
||||
sconfig.direction = SDW_DATA_DIR_RX;
|
||||
sconfig.type = SDW_STREAM_BPT;
|
||||
|
||||
pconfig.num = 0;
|
||||
pconfig.ch_mask = BIT(0);
|
||||
|
||||
ret = sdw_stream_add_slave(slave, &sconfig, &pconfig, 1, stream);
|
||||
if (ret)
|
||||
dev_err(&slave->dev, "%s: failed: %d\n", __func__, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int intel_ace2x_bpt_open_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
struct sdw_bus *bus = &cdns->bus;
|
||||
struct sdw_master_prop *prop = &bus->prop;
|
||||
struct sdw_stream_runtime *stream;
|
||||
struct sdw_stream_config sconfig;
|
||||
struct sdw_port_config *pconfig;
|
||||
unsigned int pdi0_buffer_size;
|
||||
unsigned int tx_dma_bandwidth;
|
||||
unsigned int pdi1_buffer_size;
|
||||
unsigned int rx_dma_bandwidth;
|
||||
unsigned int data_per_frame;
|
||||
unsigned int tx_total_bytes;
|
||||
struct sdw_cdns_pdi *pdi0;
|
||||
struct sdw_cdns_pdi *pdi1;
|
||||
unsigned int num_frames;
|
||||
int command;
|
||||
int ret1;
|
||||
int ret;
|
||||
int dir;
|
||||
int i;
|
||||
|
||||
stream = sdw_alloc_stream("BPT", SDW_STREAM_BPT);
|
||||
if (!stream)
|
||||
return -ENOMEM;
|
||||
|
||||
cdns->bus.bpt_stream = stream;
|
||||
|
||||
ret = sdw_slave_bpt_stream_add(slave, stream);
|
||||
if (ret < 0)
|
||||
goto release_stream;
|
||||
|
||||
/* handle PDI0 first */
|
||||
dir = SDW_DATA_DIR_TX;
|
||||
|
||||
pdi0 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 0);
|
||||
if (!pdi0) {
|
||||
dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi0 failed\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto remove_slave;
|
||||
}
|
||||
|
||||
sdw_cdns_config_stream(cdns, 1, dir, pdi0);
|
||||
|
||||
/* handle PDI1 */
|
||||
dir = SDW_DATA_DIR_RX;
|
||||
|
||||
pdi1 = sdw_cdns_alloc_pdi(cdns, &cdns->pcm, 1, dir, 1);
|
||||
if (!pdi1) {
|
||||
dev_err(cdns->dev, "%s: sdw_cdns_alloc_pdi1 failed\n", __func__);
|
||||
ret = -EINVAL;
|
||||
goto remove_slave;
|
||||
}
|
||||
|
||||
sdw_cdns_config_stream(cdns, 1, dir, pdi1);
|
||||
|
||||
/*
|
||||
* the port config direction, number of channels and frame
|
||||
* rate is totally arbitrary
|
||||
*/
|
||||
sconfig.direction = dir;
|
||||
sconfig.ch_count = 1;
|
||||
sconfig.frame_rate = 16000;
|
||||
sconfig.type = SDW_STREAM_BPT;
|
||||
sconfig.bps = 32; /* this is required for BPT/BRA */
|
||||
|
||||
/* Port configuration */
|
||||
pconfig = kcalloc(2, sizeof(*pconfig), GFP_KERNEL);
|
||||
if (!pconfig) {
|
||||
ret = -ENOMEM;
|
||||
goto remove_slave;
|
||||
}
|
||||
|
||||
for (i = 0; i < 2 /* num_pdi */; i++) {
|
||||
pconfig[i].num = i;
|
||||
pconfig[i].ch_mask = 1;
|
||||
}
|
||||
|
||||
ret = sdw_stream_add_master(&cdns->bus, &sconfig, pconfig, 2, stream);
|
||||
kfree(pconfig);
|
||||
|
||||
if (ret < 0) {
|
||||
dev_err(cdns->dev, "add master to stream failed:%d\n", ret);
|
||||
goto remove_slave;
|
||||
}
|
||||
|
||||
ret = sdw_prepare_stream(cdns->bus.bpt_stream);
|
||||
if (ret < 0)
|
||||
goto remove_master;
|
||||
|
||||
command = (msg->flags & SDW_MSG_FLAG_WRITE) ? 0 : 1;
|
||||
|
||||
ret = sdw_cdns_bpt_find_buffer_sizes(command, cdns->bus.params.row, cdns->bus.params.col,
|
||||
msg->len, SDW_BPT_MSG_MAX_BYTES, &data_per_frame,
|
||||
&pdi0_buffer_size, &pdi1_buffer_size, &num_frames);
|
||||
if (ret < 0)
|
||||
goto deprepare_stream;
|
||||
|
||||
sdw->bpt_ctx.pdi0_buffer_size = pdi0_buffer_size;
|
||||
sdw->bpt_ctx.pdi1_buffer_size = pdi1_buffer_size;
|
||||
sdw->bpt_ctx.num_frames = num_frames;
|
||||
sdw->bpt_ctx.data_per_frame = data_per_frame;
|
||||
tx_dma_bandwidth = div_u64((u64)pdi0_buffer_size * 8 * (u64)prop->default_frame_rate,
|
||||
num_frames);
|
||||
rx_dma_bandwidth = div_u64((u64)pdi1_buffer_size * 8 * (u64)prop->default_frame_rate,
|
||||
num_frames);
|
||||
|
||||
dev_dbg(cdns->dev, "Message len %d transferred in %d frames (%d per frame)\n",
|
||||
msg->len, num_frames, data_per_frame);
|
||||
dev_dbg(cdns->dev, "sizes pdi0 %d pdi1 %d tx_bandwidth %d rx_bandwidth %d\n",
|
||||
pdi0_buffer_size, pdi1_buffer_size, tx_dma_bandwidth, rx_dma_bandwidth);
|
||||
|
||||
ret = hda_sdw_bpt_open(cdns->dev->parent, /* PCI device */
|
||||
sdw->instance, &sdw->bpt_ctx.bpt_tx_stream,
|
||||
&sdw->bpt_ctx.dmab_tx_bdl, pdi0_buffer_size, tx_dma_bandwidth,
|
||||
&sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl,
|
||||
pdi1_buffer_size, rx_dma_bandwidth);
|
||||
if (ret < 0) {
|
||||
dev_err(cdns->dev, "%s: hda_sdw_bpt_open failed %d\n", __func__, ret);
|
||||
goto deprepare_stream;
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
ret = sdw_cdns_prepare_write_dma_buffer(msg->dev_num, msg->addr, msg->buf,
|
||||
msg->len, data_per_frame,
|
||||
sdw->bpt_ctx.dmab_tx_bdl.area,
|
||||
pdi0_buffer_size, &tx_total_bytes);
|
||||
} else {
|
||||
ret = sdw_cdns_prepare_read_dma_buffer(msg->dev_num, msg->addr, msg->len,
|
||||
data_per_frame,
|
||||
sdw->bpt_ctx.dmab_tx_bdl.area,
|
||||
pdi0_buffer_size, &tx_total_bytes);
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
dev_err(cdns->dev, "%s: sdw_prepare_%s_dma_buffer failed %d\n",
|
||||
__func__, command ? "read" : "write", ret);
|
||||
|
||||
ret1 = hda_sdw_bpt_close(cdns->dev->parent, /* PCI device */
|
||||
sdw->bpt_ctx.bpt_tx_stream, &sdw->bpt_ctx.dmab_tx_bdl,
|
||||
sdw->bpt_ctx.bpt_rx_stream, &sdw->bpt_ctx.dmab_rx_bdl);
|
||||
if (ret1 < 0)
|
||||
dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n",
|
||||
__func__, ret1);
|
||||
|
||||
deprepare_stream:
|
||||
sdw_deprepare_stream(cdns->bus.bpt_stream);
|
||||
|
||||
remove_master:
|
||||
ret1 = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
|
||||
if (ret1 < 0)
|
||||
dev_err(cdns->dev, "%s: remove master failed: %d\n",
|
||||
__func__, ret1);
|
||||
|
||||
remove_slave:
|
||||
ret1 = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
|
||||
if (ret1 < 0)
|
||||
dev_err(cdns->dev, "%s: remove slave failed: %d\n",
|
||||
__func__, ret1);
|
||||
|
||||
release_stream:
|
||||
sdw_release_stream(cdns->bus.bpt_stream);
|
||||
cdns->bus.bpt_stream = NULL;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void intel_ace2x_bpt_close_stream(struct sdw_intel *sdw, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
int ret;
|
||||
|
||||
ret = hda_sdw_bpt_close(cdns->dev->parent /* PCI device */, sdw->bpt_ctx.bpt_tx_stream,
|
||||
&sdw->bpt_ctx.dmab_tx_bdl, sdw->bpt_ctx.bpt_rx_stream,
|
||||
&sdw->bpt_ctx.dmab_rx_bdl);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: hda_sdw_bpt_close failed: ret %d\n",
|
||||
__func__, ret);
|
||||
|
||||
ret = sdw_deprepare_stream(cdns->bus.bpt_stream);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: sdw_deprepare_stream failed: ret %d\n",
|
||||
__func__, ret);
|
||||
|
||||
ret = sdw_stream_remove_master(&cdns->bus, cdns->bus.bpt_stream);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: remove master failed: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
ret = sdw_stream_remove_slave(slave, cdns->bus.bpt_stream);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: remove slave failed: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
cdns->bus.bpt_stream = NULL;
|
||||
}
|
||||
|
||||
#define INTEL_BPT_MSG_BYTE_ALIGNMENT 32
|
||||
|
||||
static int intel_ace2x_bpt_send_async(struct sdw_intel *sdw, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
int ret;
|
||||
|
||||
if (msg->len % INTEL_BPT_MSG_BYTE_ALIGNMENT) {
|
||||
dev_err(cdns->dev, "BPT message length %d is not a multiple of %d bytes\n",
|
||||
msg->len, INTEL_BPT_MSG_BYTE_ALIGNMENT);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(cdns->dev, "BPT Transfer start\n");
|
||||
|
||||
ret = intel_ace2x_bpt_open_stream(sdw, slave, msg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = hda_sdw_bpt_send_async(cdns->dev->parent, /* PCI device */
|
||||
sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(cdns->dev, "%s: hda_sdw_bpt_send_async failed: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
intel_ace2x_bpt_close_stream(sdw, slave, msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = sdw_enable_stream(cdns->bus.bpt_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
|
||||
__func__, ret);
|
||||
intel_ace2x_bpt_close_stream(sdw, slave, msg);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int intel_ace2x_bpt_wait(struct sdw_intel *sdw, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = &sdw->cdns;
|
||||
int ret;
|
||||
|
||||
dev_dbg(cdns->dev, "BPT Transfer wait\n");
|
||||
|
||||
ret = hda_sdw_bpt_wait(cdns->dev->parent, /* PCI device */
|
||||
sdw->bpt_ctx.bpt_tx_stream, sdw->bpt_ctx.bpt_rx_stream);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: hda_sdw_bpt_wait failed: %d\n", __func__, ret);
|
||||
|
||||
ret = sdw_disable_stream(cdns->bus.bpt_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(cdns->dev, "%s: sdw_stream_enable failed: %d\n",
|
||||
__func__, ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (msg->flags & SDW_MSG_FLAG_WRITE) {
|
||||
ret = sdw_cdns_check_write_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
|
||||
sdw->bpt_ctx.pdi1_buffer_size,
|
||||
sdw->bpt_ctx.num_frames);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: BPT Write failed %d\n", __func__, ret);
|
||||
} else {
|
||||
ret = sdw_cdns_check_read_response(cdns->dev, sdw->bpt_ctx.dmab_rx_bdl.area,
|
||||
sdw->bpt_ctx.pdi1_buffer_size,
|
||||
msg->buf, msg->len, sdw->bpt_ctx.num_frames,
|
||||
sdw->bpt_ctx.data_per_frame);
|
||||
if (ret < 0)
|
||||
dev_err(cdns->dev, "%s: BPT Read failed %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
err:
|
||||
intel_ace2x_bpt_close_stream(sdw, slave, msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* shim vendor-specific (vs) ops
|
||||
*/
|
||||
@@ -753,7 +1061,11 @@ const struct sdw_intel_hw_ops sdw_intel_lnl_hw_ops = {
|
||||
.sync_check_cmdsync_unlocked = intel_check_cmdsync_unlocked,
|
||||
|
||||
.program_sdi = intel_program_sdi,
|
||||
|
||||
.bpt_send_async = intel_ace2x_bpt_send_async,
|
||||
.bpt_wait = intel_ace2x_bpt_wait,
|
||||
};
|
||||
EXPORT_SYMBOL_NS(sdw_intel_lnl_hw_ops, "SOUNDWIRE_INTEL");
|
||||
|
||||
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
|
||||
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_SDW_BPT");
|
||||
|
||||
@@ -79,6 +79,27 @@ static bool is_wake_capable(struct sdw_slave *slave)
|
||||
return false;
|
||||
}
|
||||
|
||||
static int generic_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
|
||||
if (sdw->link_res->hw_ops->bpt_send_async)
|
||||
return sdw->link_res->hw_ops->bpt_send_async(sdw, slave, msg);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int generic_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
struct sdw_intel *sdw = cdns_to_intel(cdns);
|
||||
|
||||
if (sdw->link_res->hw_ops->bpt_wait)
|
||||
return sdw->link_res->hw_ops->bpt_wait(sdw, slave, msg);
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int generic_pre_bank_switch(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_cdns *cdns = bus_to_cdns(bus);
|
||||
@@ -222,30 +243,9 @@ static int sdw_master_read_intel_prop(struct sdw_bus *bus)
|
||||
|
||||
static int intel_prop_read(struct sdw_bus *bus)
|
||||
{
|
||||
struct sdw_master_prop *prop;
|
||||
|
||||
/* Initialize with default handler to read all DisCo properties */
|
||||
sdw_master_read_prop(bus);
|
||||
|
||||
/*
|
||||
* Only one bus frequency is supported so far, filter
|
||||
* frequencies reported in the DSDT
|
||||
*/
|
||||
prop = &bus->prop;
|
||||
if (prop->clk_freq && prop->num_clk_freq > 1) {
|
||||
unsigned int default_bus_frequency;
|
||||
|
||||
default_bus_frequency =
|
||||
prop->default_frame_rate *
|
||||
prop->default_row *
|
||||
prop->default_col /
|
||||
SDW_DOUBLE_RATE_FACTOR;
|
||||
|
||||
prop->num_clk_freq = 1;
|
||||
prop->clk_freq[0] = default_bus_frequency;
|
||||
prop->max_clk_freq = default_bus_frequency;
|
||||
}
|
||||
|
||||
/* read Intel-specific properties */
|
||||
sdw_master_read_intel_prop(bus);
|
||||
|
||||
@@ -288,6 +288,9 @@ static struct sdw_master_ops sdw_intel_ops = {
|
||||
.get_device_num = intel_get_device_num_ida,
|
||||
.put_device_num = intel_put_device_num_ida,
|
||||
.new_peripheral_assigned = generic_new_peripheral_assigned,
|
||||
|
||||
.bpt_send_async = generic_bpt_send_async,
|
||||
.bpt_wait = generic_bpt_wait,
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -13,6 +13,7 @@ static void sdw_slave_release(struct device *dev)
|
||||
{
|
||||
struct sdw_slave *slave = dev_to_sdw_dev(dev);
|
||||
|
||||
of_node_put(slave->dev.of_node);
|
||||
mutex_destroy(&slave->sdw_dev_lock);
|
||||
kfree(slave);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <linux/soundwire/sdw_registers.h>
|
||||
#include <linux/soundwire/sdw.h>
|
||||
#include <linux/soundwire/sdw_type.h>
|
||||
#include <linux/string_choices.h>
|
||||
#include <sound/soc.h>
|
||||
#include "bus.h"
|
||||
|
||||
@@ -87,11 +88,14 @@ static int _sdw_program_slave_port_params(struct sdw_bus *bus,
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Program DPN_BlockCtrl3 register */
|
||||
ret = sdw_write_no_pm(slave, addr2, t_params->blk_pkg_mode);
|
||||
if (ret < 0) {
|
||||
dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n");
|
||||
return ret;
|
||||
/* DP0 does not implement BlockCtrl3 */
|
||||
if (t_params->port_num) {
|
||||
/* Program DPN_BlockCtrl3 register */
|
||||
ret = sdw_write_no_pm(slave, addr2, t_params->blk_pkg_mode);
|
||||
if (ret < 0) {
|
||||
dev_err(bus->dev, "DPN_BlockCtrl3 register write failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -130,18 +134,28 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
|
||||
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;
|
||||
enum sdw_dpn_type port_type;
|
||||
bool read_only_wordlength;
|
||||
int ret;
|
||||
u8 wbuf;
|
||||
|
||||
if (s_rt->slave->is_mockup_device)
|
||||
return 0;
|
||||
|
||||
dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave,
|
||||
s_rt->direction,
|
||||
t_params->port_num);
|
||||
if (!dpn_prop)
|
||||
return -EINVAL;
|
||||
if (t_params->port_num) {
|
||||
struct sdw_dpn_prop *dpn_prop;
|
||||
|
||||
dpn_prop = sdw_get_slave_dpn_prop(s_rt->slave, s_rt->direction,
|
||||
t_params->port_num);
|
||||
if (!dpn_prop)
|
||||
return -EINVAL;
|
||||
|
||||
read_only_wordlength = dpn_prop->read_only_wordlength;
|
||||
port_type = dpn_prop->type;
|
||||
} else {
|
||||
read_only_wordlength = false;
|
||||
port_type = SDW_DPN_FULL;
|
||||
}
|
||||
|
||||
addr1 = SDW_DPN_PORTCTRL(t_params->port_num);
|
||||
addr2 = SDW_DPN_BLOCKCTRL1(t_params->port_num);
|
||||
@@ -171,7 +185,7 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!dpn_prop->read_only_wordlength) {
|
||||
if (!read_only_wordlength) {
|
||||
/* Program DPN_BlockCtrl1 register */
|
||||
ret = sdw_write_no_pm(s_rt->slave, addr2, (p_params->bps - 1));
|
||||
if (ret < 0) {
|
||||
@@ -223,9 +237,9 @@ static int sdw_program_slave_port_params(struct sdw_bus *bus,
|
||||
}
|
||||
}
|
||||
|
||||
if (dpn_prop->type != SDW_DPN_SIMPLE) {
|
||||
if (port_type != SDW_DPN_SIMPLE) {
|
||||
ret = _sdw_program_slave_port_params(bus, s_rt->slave,
|
||||
t_params, dpn_prop->type);
|
||||
t_params, port_type);
|
||||
if (ret < 0)
|
||||
dev_err(&s_rt->slave->dev,
|
||||
"Transport reg write failed for port: %d\n",
|
||||
@@ -358,7 +372,7 @@ static int sdw_enable_disable_master_ports(struct sdw_master_runtime *m_rt,
|
||||
} else {
|
||||
dev_err(bus->dev,
|
||||
"dpn_port_enable_ch not supported, %s failed\n",
|
||||
en ? "enable" : "disable");
|
||||
str_enable_disable(en));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
@@ -432,6 +446,9 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
struct completion *port_ready;
|
||||
struct sdw_dpn_prop *dpn_prop;
|
||||
struct sdw_prepare_ch prep_ch;
|
||||
u32 imp_def_interrupts;
|
||||
bool simple_ch_prep_sm;
|
||||
u32 ch_prep_timeout;
|
||||
bool intr = false;
|
||||
int ret = 0, val;
|
||||
u32 addr;
|
||||
@@ -439,20 +456,35 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
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\n", prep_ch.num);
|
||||
return -EINVAL;
|
||||
if (p_rt->num) {
|
||||
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\n", prep_ch.num);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
imp_def_interrupts = dpn_prop->imp_def_interrupts;
|
||||
simple_ch_prep_sm = dpn_prop->simple_ch_prep_sm;
|
||||
ch_prep_timeout = dpn_prop->ch_prep_timeout;
|
||||
} else {
|
||||
struct sdw_dp0_prop *dp0_prop = s_rt->slave->prop.dp0_prop;
|
||||
|
||||
if (!dp0_prop) {
|
||||
dev_err(bus->dev,
|
||||
"Slave DP0 properties not found\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
imp_def_interrupts = dp0_prop->imp_def_interrupts;
|
||||
simple_ch_prep_sm = dp0_prop->simple_ch_prep_sm;
|
||||
ch_prep_timeout = dp0_prop->ch_prep_timeout;
|
||||
}
|
||||
|
||||
prep_ch.prepare = prep;
|
||||
|
||||
prep_ch.bank = bus->params.next_bank;
|
||||
|
||||
if (dpn_prop->imp_def_interrupts || !dpn_prop->simple_ch_prep_sm ||
|
||||
if (imp_def_interrupts || !simple_ch_prep_sm ||
|
||||
bus->params.s_data_mode != SDW_PORT_DATA_MODE_NORMAL)
|
||||
intr = true;
|
||||
|
||||
@@ -463,7 +495,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
*/
|
||||
if (prep && intr) {
|
||||
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
|
||||
dpn_prop->imp_def_interrupts);
|
||||
imp_def_interrupts);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
@@ -472,7 +504,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
sdw_do_port_prep(s_rt, prep_ch, prep ? SDW_OPS_PORT_PRE_PREP : SDW_OPS_PORT_PRE_DEPREP);
|
||||
|
||||
/* Prepare Slave port implementing CP_SM */
|
||||
if (!dpn_prop->simple_ch_prep_sm) {
|
||||
if (!simple_ch_prep_sm) {
|
||||
addr = SDW_DPN_PREPARECTRL(p_rt->num);
|
||||
|
||||
if (prep)
|
||||
@@ -489,7 +521,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
/* Wait for completion on port ready */
|
||||
port_ready = &s_rt->slave->port_ready[prep_ch.num];
|
||||
wait_for_completion_timeout(port_ready,
|
||||
msecs_to_jiffies(dpn_prop->ch_prep_timeout));
|
||||
msecs_to_jiffies(ch_prep_timeout));
|
||||
|
||||
val = sdw_read_no_pm(s_rt->slave, SDW_DPN_PREPARESTATUS(p_rt->num));
|
||||
if ((val < 0) || (val & p_rt->ch_mask)) {
|
||||
@@ -506,7 +538,7 @@ static int sdw_prep_deprep_slave_ports(struct sdw_bus *bus,
|
||||
/* Disable interrupt after Port de-prepare */
|
||||
if (!prep && intr)
|
||||
ret = sdw_configure_dpn_intr(s_rt->slave, p_rt->num, prep,
|
||||
dpn_prop->imp_def_interrupts);
|
||||
imp_def_interrupts);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -1007,7 +1039,8 @@ static int sdw_slave_port_is_valid_range(struct device *dev, int num)
|
||||
|
||||
static int sdw_slave_port_config(struct sdw_slave *slave,
|
||||
struct sdw_slave_runtime *s_rt,
|
||||
const struct sdw_port_config *port_config)
|
||||
const struct sdw_port_config *port_config,
|
||||
bool is_bpt_stream)
|
||||
{
|
||||
struct sdw_port_runtime *p_rt;
|
||||
int ret;
|
||||
@@ -1019,9 +1052,13 @@ static int sdw_slave_port_config(struct sdw_slave *slave,
|
||||
* TODO: Check valid port range as defined by DisCo/
|
||||
* slave
|
||||
*/
|
||||
ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!is_bpt_stream) {
|
||||
ret = sdw_slave_port_is_valid_range(&slave->dev, port_config[i].num);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else if (port_config[i].num) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = sdw_port_config(p_rt, port_config, i);
|
||||
if (ret < 0)
|
||||
@@ -1190,6 +1227,20 @@ static struct sdw_master_runtime
|
||||
struct sdw_master_runtime *m_rt, *walk_m_rt;
|
||||
struct list_head *insert_after;
|
||||
|
||||
if (stream->type == SDW_STREAM_BPT) {
|
||||
if (bus->stream_refcount > 0 || bus->bpt_stream_refcount > 0) {
|
||||
dev_err(bus->dev, "%s: %d/%d audio/BPT stream already allocated\n",
|
||||
__func__, bus->stream_refcount, bus->bpt_stream_refcount);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
} else {
|
||||
if (bus->bpt_stream_refcount > 0) {
|
||||
dev_err(bus->dev, "%s: BPT stream already allocated\n",
|
||||
__func__);
|
||||
return ERR_PTR(-EAGAIN);
|
||||
}
|
||||
}
|
||||
|
||||
m_rt = kzalloc(sizeof(*m_rt), GFP_KERNEL);
|
||||
if (!m_rt)
|
||||
return NULL;
|
||||
@@ -1218,6 +1269,8 @@ static struct sdw_master_runtime
|
||||
m_rt->stream = stream;
|
||||
|
||||
bus->stream_refcount++;
|
||||
if (stream->type == SDW_STREAM_BPT)
|
||||
bus->bpt_stream_refcount++;
|
||||
|
||||
return m_rt;
|
||||
}
|
||||
@@ -1266,6 +1319,8 @@ static void sdw_master_rt_free(struct sdw_master_runtime *m_rt,
|
||||
list_del(&m_rt->bus_node);
|
||||
kfree(m_rt);
|
||||
|
||||
if (stream->type == SDW_STREAM_BPT)
|
||||
bus->bpt_stream_refcount--;
|
||||
bus->stream_refcount--;
|
||||
}
|
||||
|
||||
@@ -1330,6 +1385,11 @@ struct sdw_dpn_prop *sdw_get_slave_dpn_prop(struct sdw_slave *slave,
|
||||
u8 num_ports;
|
||||
int i;
|
||||
|
||||
if (!port_num) {
|
||||
dev_err(&slave->dev, "%s: port_num is zero\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (direction == SDW_DATA_DIR_TX) {
|
||||
num_ports = hweight32(slave->prop.source_ports);
|
||||
dpn_prop = slave->prop.src_dpn_prop;
|
||||
@@ -1805,12 +1865,13 @@ static int set_stream(struct snd_pcm_substream *substream,
|
||||
* sdw_alloc_stream() - Allocate and return stream runtime
|
||||
*
|
||||
* @stream_name: SoundWire stream name
|
||||
* @type: stream type (could be PCM ,PDM or BPT)
|
||||
*
|
||||
* Allocates a SoundWire stream runtime instance.
|
||||
* sdw_alloc_stream should be called only once per stream. Typically
|
||||
* invoked from ALSA/ASoC machine/platform driver.
|
||||
*/
|
||||
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name)
|
||||
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type)
|
||||
{
|
||||
struct sdw_stream_runtime *stream;
|
||||
|
||||
@@ -1822,6 +1883,7 @@ struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name)
|
||||
INIT_LIST_HEAD(&stream->master_list);
|
||||
stream->state = SDW_STREAM_ALLOCATED;
|
||||
stream->m_rt_count = 0;
|
||||
stream->type = type;
|
||||
|
||||
return stream;
|
||||
}
|
||||
@@ -1850,7 +1912,7 @@ int sdw_startup_stream(void *sdw_substream)
|
||||
if (!name)
|
||||
return -ENOMEM;
|
||||
|
||||
sdw_stream = sdw_alloc_stream(name);
|
||||
sdw_stream = sdw_alloc_stream(name, SDW_STREAM_PCM);
|
||||
if (!sdw_stream) {
|
||||
dev_err(rtd->dev, "alloc stream failed for substream DAI %s\n", substream->name);
|
||||
ret = -ENOMEM;
|
||||
@@ -1957,6 +2019,12 @@ int sdw_stream_add_master(struct sdw_bus *bus,
|
||||
m_rt = sdw_master_rt_find(bus, stream);
|
||||
if (!m_rt) {
|
||||
m_rt = sdw_master_rt_alloc(bus, stream);
|
||||
if (IS_ERR(m_rt)) {
|
||||
ret = PTR_ERR(m_rt);
|
||||
dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s: %d\n",
|
||||
__func__, stream->name, ret);
|
||||
goto unlock;
|
||||
}
|
||||
if (!m_rt) {
|
||||
dev_err(bus->dev, "%s: Master runtime alloc failed for stream:%s\n",
|
||||
__func__, stream->name);
|
||||
@@ -2072,6 +2140,12 @@ int sdw_stream_add_slave(struct sdw_slave *slave,
|
||||
* So, allocate m_rt and add Slave to it.
|
||||
*/
|
||||
m_rt = sdw_master_rt_alloc(slave->bus, stream);
|
||||
if (IS_ERR(m_rt)) {
|
||||
ret = PTR_ERR(m_rt);
|
||||
dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s: %d\n",
|
||||
__func__, stream->name, ret);
|
||||
goto unlock;
|
||||
}
|
||||
if (!m_rt) {
|
||||
dev_err(&slave->dev, "%s: Master runtime alloc failed for stream:%s\n",
|
||||
__func__, stream->name);
|
||||
@@ -2113,7 +2187,8 @@ int sdw_stream_add_slave(struct sdw_slave *slave,
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = sdw_slave_port_config(slave, s_rt, port_config);
|
||||
ret = sdw_slave_port_config(slave, s_rt, port_config,
|
||||
stream->type == SDW_STREAM_BPT);
|
||||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
|
||||
@@ -150,12 +150,14 @@ enum sdw_dpn_pkg_mode {
|
||||
*
|
||||
* @SDW_STREAM_PCM: PCM data stream
|
||||
* @SDW_STREAM_PDM: PDM data stream
|
||||
* @SDW_STREAM_BPT: BPT data stream
|
||||
*
|
||||
* spec doesn't define this, but is used in implementation
|
||||
*/
|
||||
enum sdw_stream_type {
|
||||
SDW_STREAM_PCM = 0,
|
||||
SDW_STREAM_PDM = 1,
|
||||
SDW_STREAM_BPT = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -822,6 +824,15 @@ struct sdw_defer {
|
||||
struct completion complete;
|
||||
};
|
||||
|
||||
/*
|
||||
* Add a practical limit to BPT transfer sizes. BPT is typically used
|
||||
* to transfer firmware, and larger firmware transfers will increase
|
||||
* the cold latency beyond typical OS or user requirements.
|
||||
*/
|
||||
#define SDW_BPT_MSG_MAX_BYTES (1024 * 1024)
|
||||
|
||||
struct sdw_bpt_msg;
|
||||
|
||||
/**
|
||||
* struct sdw_master_ops - Master driver ops
|
||||
* @read_prop: Read Master properties
|
||||
@@ -837,6 +848,10 @@ struct sdw_defer {
|
||||
* @get_device_num: Callback for vendor-specific device_number allocation
|
||||
* @put_device_num: Callback for vendor-specific device_number release
|
||||
* @new_peripheral_assigned: Callback to handle enumeration of new peripheral.
|
||||
* @bpt_send_async: reserve resources for BPT stream and send message
|
||||
* using BTP protocol
|
||||
* @bpt_wait: wait for message completion using BTP protocol
|
||||
* and release resources
|
||||
*/
|
||||
struct sdw_master_ops {
|
||||
int (*read_prop)(struct sdw_bus *bus);
|
||||
@@ -853,6 +868,9 @@ struct sdw_master_ops {
|
||||
void (*new_peripheral_assigned)(struct sdw_bus *bus,
|
||||
struct sdw_slave *slave,
|
||||
int dev_num);
|
||||
int (*bpt_send_async)(struct sdw_bus *bus, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg);
|
||||
int (*bpt_wait)(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg);
|
||||
};
|
||||
|
||||
int sdw_bus_master_add(struct sdw_bus *bus, struct device *parent,
|
||||
@@ -879,7 +897,7 @@ struct sdw_port_config {
|
||||
* @ch_count: Channel count of the stream
|
||||
* @bps: Number of bits per audio sample
|
||||
* @direction: Data direction
|
||||
* @type: Stream type PCM or PDM
|
||||
* @type: Stream type PCM, PDM or BPT
|
||||
*/
|
||||
struct sdw_stream_config {
|
||||
unsigned int frame_rate;
|
||||
@@ -929,7 +947,7 @@ struct sdw_stream_params {
|
||||
* @name: SoundWire stream name
|
||||
* @params: Stream parameters
|
||||
* @state: Current state of the stream
|
||||
* @type: Stream type PCM or PDM
|
||||
* @type: Stream type PCM, PDM or BPT
|
||||
* @m_rt_count: Count of Master runtime(s) in this stream
|
||||
* @master_list: List of Master runtime(s) in this stream.
|
||||
* master_list can contain only one m_rt per Master instance
|
||||
@@ -959,6 +977,9 @@ struct sdw_stream_runtime {
|
||||
* @defer_msg: Defer message
|
||||
* @params: Current bus parameters
|
||||
* @stream_refcount: number of streams currently using this bus
|
||||
* @btp_stream_refcount: number of BTP streams currently using this bus (should
|
||||
* be zero or one, multiple streams per link is not supported).
|
||||
* @bpt_stream: pointer stored to handle BTP streams.
|
||||
* @ops: Master callback ops
|
||||
* @port_ops: Master port callback ops
|
||||
* @prop: Master properties
|
||||
@@ -996,6 +1017,8 @@ struct sdw_bus {
|
||||
struct sdw_defer defer_msg;
|
||||
struct sdw_bus_params params;
|
||||
int stream_refcount;
|
||||
int bpt_stream_refcount;
|
||||
struct sdw_stream_runtime *bpt_stream;
|
||||
const struct sdw_master_ops *ops;
|
||||
const struct sdw_master_port_ops *port_ops;
|
||||
struct sdw_master_prop prop;
|
||||
@@ -1017,7 +1040,7 @@ struct sdw_bus {
|
||||
unsigned int lane_used_bandwidth[SDW_MAX_LANES];
|
||||
};
|
||||
|
||||
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name);
|
||||
struct sdw_stream_runtime *sdw_alloc_stream(const char *stream_name, enum sdw_stream_type type);
|
||||
void sdw_release_stream(struct sdw_stream_runtime *stream);
|
||||
|
||||
int sdw_compute_params(struct sdw_bus *bus, struct sdw_stream_runtime *stream);
|
||||
@@ -1043,6 +1066,10 @@ int sdw_compare_devid(struct sdw_slave *slave, struct sdw_slave_id id);
|
||||
void sdw_extract_slave_id(struct sdw_bus *bus, u64 addr, struct sdw_slave_id *id);
|
||||
bool is_clock_scaling_supported_by_slave(struct sdw_slave *slave);
|
||||
|
||||
int sdw_bpt_send_async(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg);
|
||||
int sdw_bpt_wait(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg);
|
||||
int sdw_bpt_send_sync(struct sdw_bus *bus, struct sdw_slave *slave, struct sdw_bpt_msg *msg);
|
||||
|
||||
#if IS_ENABLED(CONFIG_SOUNDWIRE)
|
||||
|
||||
int sdw_stream_add_slave(struct sdw_slave *slave,
|
||||
|
||||
@@ -28,6 +28,8 @@
|
||||
#define ACP_SDW1 1
|
||||
#define AMD_SDW_MAX_MANAGER_COUNT 2
|
||||
#define ACP63_PCI_REV_ID 0x63
|
||||
#define ACP70_PCI_REV_ID 0x70
|
||||
#define ACP71_PCI_REV_ID 0x71
|
||||
|
||||
struct acp_sdw_pdata {
|
||||
u16 instance;
|
||||
|
||||
@@ -436,6 +436,10 @@ struct sdw_intel_hw_ops {
|
||||
bool (*sync_check_cmdsync_unlocked)(struct sdw_intel *sdw);
|
||||
|
||||
void (*program_sdi)(struct sdw_intel *sdw, int dev_num);
|
||||
|
||||
int (*bpt_send_async)(struct sdw_intel *sdw, struct sdw_slave *slave,
|
||||
struct sdw_bpt_msg *msg);
|
||||
int (*bpt_wait)(struct sdw_intel *sdw, struct sdw_slave *slave, struct sdw_bpt_msg *msg);
|
||||
};
|
||||
|
||||
extern const struct sdw_intel_hw_ops sdw_intel_cnl_hw_ops;
|
||||
|
||||
69
include/sound/hda-sdw-bpt.h
Normal file
69
include/sound/hda-sdw-bpt.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
|
||||
/*
|
||||
* This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
* redistributing this file, you may do so under either license.
|
||||
*
|
||||
* Copyright(c) 2025 Intel Corporation.
|
||||
*/
|
||||
|
||||
#ifndef __HDA_SDW_BPT_H
|
||||
#define __HDA_SDW_BPT_H
|
||||
|
||||
#include <linux/device.h>
|
||||
|
||||
struct hdac_ext_stream;
|
||||
struct snd_dma_buffer;
|
||||
|
||||
#if IS_ENABLED(CONFIG_SND_SOF_SOF_HDA_SDW_BPT)
|
||||
int hda_sdw_bpt_open(struct device *dev, int link_id, struct hdac_ext_stream **bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl, u32 bpt_tx_num_bytes,
|
||||
u32 tx_dma_bandwidth, struct hdac_ext_stream **bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl, u32 bpt_rx_num_bytes,
|
||||
u32 rx_dma_bandwidth);
|
||||
|
||||
int hda_sdw_bpt_send_async(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream);
|
||||
|
||||
int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream);
|
||||
|
||||
int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl);
|
||||
#else
|
||||
static inline int hda_sdw_bpt_open(struct device *dev, int link_id,
|
||||
struct hdac_ext_stream **bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl, u32 bpt_tx_num_bytes,
|
||||
u32 tx_dma_bandwidth, struct hdac_ext_stream **bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl, u32 bpt_rx_num_bytes,
|
||||
u32 rx_dma_bandwidth)
|
||||
{
|
||||
WARN_ONCE(1, "SoundWire BPT is disabled");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int hda_sdw_bpt_send_async(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream)
|
||||
{
|
||||
WARN_ONCE(1, "SoundWire BPT is disabled");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream)
|
||||
{
|
||||
WARN_ONCE(1, "SoundWire BPT is disabled");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static inline int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl,
|
||||
struct hdac_ext_stream *bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl)
|
||||
{
|
||||
WARN_ONCE(1, "SoundWire BPT is disabled");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __HDA_SDW_BPT_H */
|
||||
@@ -225,6 +225,14 @@ static int rt711_sdca_read_prop(struct sdw_slave *slave)
|
||||
j++;
|
||||
}
|
||||
|
||||
prop->dp0_prop = devm_kzalloc(&slave->dev, sizeof(*prop->dp0_prop),
|
||||
GFP_KERNEL);
|
||||
if (!prop->dp0_prop)
|
||||
return -ENOMEM;
|
||||
|
||||
prop->dp0_prop->simple_ch_prep_sm = true;
|
||||
prop->dp0_prop->ch_prep_timeout = 10;
|
||||
|
||||
/* set the timeout values */
|
||||
prop->clk_stop_timeout = 700;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ int qcom_snd_sdw_startup(struct snd_pcm_substream *substream)
|
||||
u32 rx_ch_cnt = 0, tx_ch_cnt = 0;
|
||||
int ret, i, j;
|
||||
|
||||
sruntime = sdw_alloc_stream(cpu_dai->name);
|
||||
sruntime = sdw_alloc_stream(cpu_dai->name, SDW_STREAM_PCM);
|
||||
if (!sruntime)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -268,6 +268,7 @@ config SND_SOC_SOF_INTEL_LNL
|
||||
tristate
|
||||
select SND_SOC_SOF_HDA_GENERIC
|
||||
select SND_SOC_SOF_INTEL_SOUNDWIRE_LINK_BASELINE
|
||||
select SND_SOF_SOF_HDA_SDW_BPT if SND_SOC_SOF_INTEL_SOUNDWIRE
|
||||
select SND_SOC_SOF_IPC4
|
||||
select SND_SOC_SOF_INTEL_MTL
|
||||
|
||||
@@ -342,6 +343,12 @@ config SND_SOC_SOF_HDA_AUDIO_CODEC
|
||||
|
||||
endif ## SND_SOC_SOF_HDA_GENERIC
|
||||
|
||||
config SND_SOF_SOF_HDA_SDW_BPT
|
||||
tristate
|
||||
help
|
||||
This option is not user-selectable but automagically handled by
|
||||
'select' statements at a higher level.
|
||||
|
||||
config SND_SOC_SOF_HDA_LINK_BASELINE
|
||||
tristate
|
||||
select SND_SOC_SOF_HDA if SND_SOC_SOF_HDA_LINK
|
||||
|
||||
@@ -12,6 +12,8 @@ snd-sof-intel-hda-generic-y := hda.o hda-common-ops.o
|
||||
|
||||
snd-sof-intel-hda-mlink-y := hda-mlink.o
|
||||
|
||||
snd-sof-intel-hda-sdw-bpt-objs := hda-sdw-bpt.o
|
||||
|
||||
snd-sof-intel-hda-common-$(CONFIG_SND_SOC_SOF_HDA_PROBES) += hda-probes.o
|
||||
|
||||
snd-sof-intel-hda-y := hda-codec.o
|
||||
@@ -26,6 +28,8 @@ obj-$(CONFIG_SND_SOC_SOF_HDA_GENERIC) += snd-sof-intel-hda-generic.o
|
||||
obj-$(CONFIG_SND_SOC_SOF_HDA_MLINK) += snd-sof-intel-hda-mlink.o
|
||||
obj-$(CONFIG_SND_SOC_SOF_HDA) += snd-sof-intel-hda.o
|
||||
|
||||
obj-$(CONFIG_SND_SOF_SOF_HDA_SDW_BPT) += snd-sof-intel-hda-sdw-bpt.o
|
||||
|
||||
snd-sof-pci-intel-tng-y := pci-tng.o
|
||||
snd-sof-pci-intel-skl-y := pci-skl.o skl.o hda-loader-skl.o
|
||||
snd-sof-pci-intel-apl-y := pci-apl.o apl.o
|
||||
|
||||
445
sound/soc/sof/intel/hda-sdw-bpt.c
Normal file
445
sound/soc/sof/intel/hda-sdw-bpt.c
Normal file
@@ -0,0 +1,445 @@
|
||||
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
|
||||
//
|
||||
// This file is provided under a dual BSD/GPLv2 license. When using or
|
||||
// redistributing this file, you may do so under either license.
|
||||
//
|
||||
// Copyright(c) 2025 Intel Corporation.
|
||||
//
|
||||
|
||||
/*
|
||||
* Hardware interface for SoundWire BPT support with HDA DMA
|
||||
*/
|
||||
|
||||
#include <sound/hdaudio_ext.h>
|
||||
#include <sound/hda-mlink.h>
|
||||
#include <sound/hda-sdw-bpt.h>
|
||||
#include <sound/sof.h>
|
||||
#include <sound/sof/ipc4/header.h>
|
||||
#include "../ops.h"
|
||||
#include "../sof-priv.h"
|
||||
#include "../ipc4-priv.h"
|
||||
#include "hda.h"
|
||||
|
||||
#define BPT_FREQUENCY 192000 /* The max rate defined in rate_bits[] hdac_device.c */
|
||||
#define BPT_MULTIPLIER ((BPT_FREQUENCY / 48000) - 1)
|
||||
#define BPT_CHAIN_DMA_FIFO_MS 10
|
||||
/*
|
||||
* This routine is directly inspired by sof_ipc4_chain_dma_trigger(),
|
||||
* with major simplifications since there are no pipelines defined
|
||||
* and no dependency on ALSA hw_params
|
||||
*/
|
||||
static int chain_dma_trigger(struct snd_sof_dev *sdev, unsigned int stream_tag,
|
||||
int direction, int state)
|
||||
{
|
||||
struct sof_ipc4_fw_data *ipc4_data = sdev->private;
|
||||
bool allocate, enable, set_fifo_size;
|
||||
struct sof_ipc4_msg msg = {{ 0 }};
|
||||
int dma_id;
|
||||
|
||||
if (sdev->pdata->ipc_type != SOF_IPC_TYPE_4)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
switch (state) {
|
||||
case SOF_IPC4_PIPE_RUNNING: /* Allocate and start the chain */
|
||||
allocate = true;
|
||||
enable = true;
|
||||
set_fifo_size = true;
|
||||
break;
|
||||
case SOF_IPC4_PIPE_PAUSED: /* Stop the chain */
|
||||
allocate = true;
|
||||
enable = false;
|
||||
set_fifo_size = false;
|
||||
break;
|
||||
case SOF_IPC4_PIPE_RESET: /* Deallocate chain resources and remove the chain */
|
||||
allocate = false;
|
||||
enable = false;
|
||||
set_fifo_size = false;
|
||||
break;
|
||||
default:
|
||||
dev_err(sdev->dev, "Unexpected state %d", state);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg.primary = SOF_IPC4_MSG_TYPE_SET(SOF_IPC4_GLB_CHAIN_DMA);
|
||||
msg.primary |= SOF_IPC4_MSG_DIR(SOF_IPC4_MSG_REQUEST);
|
||||
msg.primary |= SOF_IPC4_MSG_TARGET(SOF_IPC4_FW_GEN_MSG);
|
||||
|
||||
/* for BPT/BRA we can use the same stream tag for host and link */
|
||||
dma_id = stream_tag - 1;
|
||||
if (direction == SNDRV_PCM_STREAM_CAPTURE)
|
||||
dma_id += ipc4_data->num_playback_streams;
|
||||
|
||||
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_HOST_ID(dma_id);
|
||||
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_LINK_ID(dma_id);
|
||||
|
||||
/* For BPT/BRA we use 32 bits so SCS is not set */
|
||||
|
||||
/* CHAIN DMA needs at least 2ms */
|
||||
if (set_fifo_size)
|
||||
msg.extension |= SOF_IPC4_GLB_EXT_CHAIN_DMA_FIFO_SIZE(BPT_FREQUENCY / 1000 *
|
||||
BPT_CHAIN_DMA_FIFO_MS *
|
||||
sizeof(u32));
|
||||
|
||||
if (allocate)
|
||||
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ALLOCATE_MASK;
|
||||
|
||||
if (enable)
|
||||
msg.primary |= SOF_IPC4_GLB_CHAIN_DMA_ENABLE_MASK;
|
||||
|
||||
return sof_ipc_tx_message_no_reply(sdev->ipc, &msg, 0);
|
||||
}
|
||||
|
||||
static int hda_sdw_bpt_dma_prepare(struct device *dev, struct hdac_ext_stream **sdw_bpt_stream,
|
||||
struct snd_dma_buffer *dmab_bdl, u32 bpt_num_bytes,
|
||||
unsigned int num_channels, int direction)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
struct hdac_ext_stream *bpt_stream;
|
||||
unsigned int format = HDA_CL_STREAM_FORMAT;
|
||||
|
||||
/*
|
||||
* the baseline format needs to be adjusted to
|
||||
* bandwidth requirements
|
||||
*/
|
||||
format |= (num_channels - 1);
|
||||
format |= BPT_MULTIPLIER << AC_FMT_MULT_SHIFT;
|
||||
|
||||
dev_dbg(dev, "direction %d format_val %#x\n", direction, format);
|
||||
|
||||
bpt_stream = hda_cl_prepare(dev, format, bpt_num_bytes, dmab_bdl, false, direction, false);
|
||||
if (IS_ERR(bpt_stream)) {
|
||||
dev_err(sdev->dev, "%s: SDW BPT DMA prepare failed: dir %d\n",
|
||||
__func__, direction);
|
||||
return PTR_ERR(bpt_stream);
|
||||
}
|
||||
*sdw_bpt_stream = bpt_stream;
|
||||
|
||||
if (!sdev->dspless_mode_selected) {
|
||||
struct hdac_stream *hstream;
|
||||
u32 mask;
|
||||
|
||||
/* decouple host and link DMA if the DSP is used */
|
||||
hstream = &bpt_stream->hstream;
|
||||
mask = BIT(hstream->index);
|
||||
|
||||
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, mask);
|
||||
|
||||
snd_hdac_ext_stream_reset(bpt_stream);
|
||||
|
||||
snd_hdac_ext_stream_setup(bpt_stream, format);
|
||||
}
|
||||
|
||||
if (hdac_stream(bpt_stream)->direction == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
struct hdac_bus *bus = sof_to_bus(sdev);
|
||||
struct hdac_ext_link *hlink;
|
||||
int stream_tag;
|
||||
|
||||
stream_tag = hdac_stream(bpt_stream)->stream_tag;
|
||||
hlink = hdac_bus_eml_sdw_get_hlink(bus);
|
||||
|
||||
snd_hdac_ext_bus_link_set_stream_id(hlink, stream_tag);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hda_sdw_bpt_dma_deprepare(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream,
|
||||
struct snd_dma_buffer *dmab_bdl)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
struct hdac_stream *hstream;
|
||||
u32 mask;
|
||||
int ret;
|
||||
|
||||
ret = hda_cl_cleanup(sdev->dev, dmab_bdl, true, sdw_bpt_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "%s: SDW BPT DMA cleanup failed\n",
|
||||
__func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (hdac_stream(sdw_bpt_stream)->direction == SNDRV_PCM_STREAM_PLAYBACK) {
|
||||
struct hdac_bus *bus = sof_to_bus(sdev);
|
||||
struct hdac_ext_link *hlink;
|
||||
int stream_tag;
|
||||
|
||||
stream_tag = hdac_stream(sdw_bpt_stream)->stream_tag;
|
||||
hlink = hdac_bus_eml_sdw_get_hlink(bus);
|
||||
|
||||
snd_hdac_ext_bus_link_clear_stream_id(hlink, stream_tag);
|
||||
}
|
||||
|
||||
if (!sdev->dspless_mode_selected) {
|
||||
/* Release CHAIN_DMA resources */
|
||||
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
|
||||
hdac_stream(sdw_bpt_stream)->direction,
|
||||
SOF_IPC4_PIPE_RESET);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_RESET failed: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
/* couple host and link DMA */
|
||||
hstream = &sdw_bpt_stream->hstream;
|
||||
mask = BIT(hstream->index);
|
||||
|
||||
snd_sof_dsp_update_bits(sdev, HDA_DSP_PP_BAR, SOF_HDA_REG_PP_PPCTL, mask, 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hda_sdw_bpt_dma_enable(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_START);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: SDW BPT DMA trigger start failed\n", __func__);
|
||||
|
||||
if (!sdev->dspless_mode_selected) {
|
||||
/* the chain DMA needs to be programmed before the DMAs */
|
||||
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
|
||||
hdac_stream(sdw_bpt_stream)->direction,
|
||||
SOF_IPC4_PIPE_RUNNING);
|
||||
if (ret < 0) {
|
||||
dev_err(sdev->dev, "%s: chain_dma_trigger failed: %d\n",
|
||||
__func__, ret);
|
||||
hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
|
||||
return ret;
|
||||
}
|
||||
snd_hdac_ext_stream_start(sdw_bpt_stream);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hda_sdw_bpt_dma_disable(struct device *dev, struct hdac_ext_stream *sdw_bpt_stream)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
if (!sdev->dspless_mode_selected) {
|
||||
snd_hdac_ext_stream_clear(sdw_bpt_stream);
|
||||
|
||||
ret = chain_dma_trigger(sdev, hdac_stream(sdw_bpt_stream)->stream_tag,
|
||||
hdac_stream(sdw_bpt_stream)->direction,
|
||||
SOF_IPC4_PIPE_PAUSED);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: chain_dma_trigger PIPE_PAUSED failed: %d\n",
|
||||
__func__, ret);
|
||||
}
|
||||
|
||||
ret = hda_cl_trigger(sdev->dev, sdw_bpt_stream, SNDRV_PCM_TRIGGER_STOP);
|
||||
if (ret < 0)
|
||||
dev_err(sdev->dev, "%s: SDW BPT DMA trigger stop failed\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int hda_sdw_bpt_open(struct device *dev, int link_id, struct hdac_ext_stream **bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl, u32 bpt_tx_num_bytes,
|
||||
u32 tx_dma_bandwidth, struct hdac_ext_stream **bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl, u32 bpt_rx_num_bytes,
|
||||
u32 rx_dma_bandwidth)
|
||||
{
|
||||
struct snd_sof_dev *sdev = dev_get_drvdata(dev);
|
||||
unsigned int num_channels_tx;
|
||||
unsigned int num_channels_rx;
|
||||
int ret1;
|
||||
int ret;
|
||||
|
||||
num_channels_tx = DIV_ROUND_UP(tx_dma_bandwidth, BPT_FREQUENCY * 32);
|
||||
|
||||
ret = hda_sdw_bpt_dma_prepare(dev, bpt_tx_stream, dmab_tx_bdl, bpt_tx_num_bytes,
|
||||
num_channels_tx, SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_prepare failed for TX: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
num_channels_rx = DIV_ROUND_UP(rx_dma_bandwidth, BPT_FREQUENCY * 32);
|
||||
|
||||
ret = hda_sdw_bpt_dma_prepare(dev, bpt_rx_stream, dmab_rx_bdl, bpt_rx_num_bytes,
|
||||
num_channels_rx, SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_prepare failed for RX: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
ret1 = hda_sdw_bpt_dma_deprepare(dev, *bpt_tx_stream, dmab_tx_bdl);
|
||||
if (ret1 < 0)
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_deprepare failed for TX: %d\n",
|
||||
__func__, ret1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* we need to map the channels in PCMSyCM registers */
|
||||
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
|
||||
0, /* cpu_dai->id -> PDI0 */
|
||||
GENMASK(num_channels_tx - 1, 0),
|
||||
hdac_stream(*bpt_tx_stream)->stream_tag,
|
||||
SNDRV_PCM_STREAM_PLAYBACK);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed for TX: %d\n",
|
||||
__func__, ret);
|
||||
goto close;
|
||||
}
|
||||
|
||||
ret = hdac_bus_eml_sdw_map_stream_ch(sof_to_bus(sdev), link_id,
|
||||
1, /* cpu_dai->id -> PDI1 */
|
||||
GENMASK(num_channels_rx - 1, 0),
|
||||
hdac_stream(*bpt_rx_stream)->stream_tag,
|
||||
SNDRV_PCM_STREAM_CAPTURE);
|
||||
if (!ret)
|
||||
return 0;
|
||||
|
||||
dev_err(dev, "%s: hdac_bus_eml_sdw_map_stream_ch failed for RX: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
close:
|
||||
ret1 = hda_sdw_bpt_close(dev, *bpt_tx_stream, dmab_tx_bdl, *bpt_rx_stream, dmab_rx_bdl);
|
||||
if (ret1 < 0)
|
||||
dev_err(dev, "%s: hda_sdw_bpt_close failed: %d\n",
|
||||
__func__, ret1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS(hda_sdw_bpt_open, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
|
||||
|
||||
int hda_sdw_bpt_send_async(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream)
|
||||
{
|
||||
int ret1;
|
||||
int ret;
|
||||
|
||||
ret = hda_sdw_bpt_dma_enable(dev, bpt_tx_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_enable failed for TX: %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = hda_sdw_bpt_dma_enable(dev, bpt_rx_stream);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_enable failed for RX: %d\n",
|
||||
__func__, ret);
|
||||
|
||||
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_tx_stream);
|
||||
if (ret1 < 0)
|
||||
dev_err(dev, "%s: hda_sdw_bpt_dma_disable failed for TX: %d\n",
|
||||
__func__, ret1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS(hda_sdw_bpt_send_async, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
|
||||
|
||||
/*
|
||||
* 3s is several orders of magnitude larger than what is needed for a
|
||||
* typical firmware download.
|
||||
*/
|
||||
#define HDA_BPT_IOC_TIMEOUT_MS 3000
|
||||
|
||||
int hda_sdw_bpt_wait(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct hdac_ext_stream *bpt_rx_stream)
|
||||
{
|
||||
struct sof_intel_hda_stream *hda_tx_stream;
|
||||
struct sof_intel_hda_stream *hda_rx_stream;
|
||||
snd_pcm_uframes_t tx_position;
|
||||
snd_pcm_uframes_t rx_position;
|
||||
unsigned long time_tx_left;
|
||||
unsigned long time_rx_left;
|
||||
int ret = 0;
|
||||
int ret1;
|
||||
int i;
|
||||
|
||||
hda_tx_stream = container_of(bpt_tx_stream, struct sof_intel_hda_stream, hext_stream);
|
||||
hda_rx_stream = container_of(bpt_rx_stream, struct sof_intel_hda_stream, hext_stream);
|
||||
|
||||
time_tx_left = wait_for_completion_timeout(&hda_tx_stream->ioc,
|
||||
msecs_to_jiffies(HDA_BPT_IOC_TIMEOUT_MS));
|
||||
if (!time_tx_left) {
|
||||
tx_position = hda_dsp_stream_get_position(hdac_stream(bpt_tx_stream),
|
||||
SNDRV_PCM_STREAM_PLAYBACK, false);
|
||||
dev_err(dev, "%s: SDW BPT TX DMA did not complete: %ld\n",
|
||||
__func__, tx_position);
|
||||
ret = -ETIMEDOUT;
|
||||
goto dma_disable;
|
||||
}
|
||||
|
||||
/* Make sure the DMA is flushed */
|
||||
i = 0;
|
||||
do {
|
||||
tx_position = hda_dsp_stream_get_position(hdac_stream(bpt_tx_stream),
|
||||
SNDRV_PCM_STREAM_PLAYBACK, false);
|
||||
usleep_range(1000, 1010);
|
||||
i++;
|
||||
} while (tx_position && i < HDA_BPT_IOC_TIMEOUT_MS);
|
||||
if (tx_position) {
|
||||
dev_err(dev, "%s: SDW BPT TX DMA position %ld was not cleared\n",
|
||||
__func__, tx_position);
|
||||
ret = -ETIMEDOUT;
|
||||
goto dma_disable;
|
||||
}
|
||||
|
||||
/* the wait should be minimal here */
|
||||
time_rx_left = wait_for_completion_timeout(&hda_rx_stream->ioc,
|
||||
msecs_to_jiffies(HDA_BPT_IOC_TIMEOUT_MS));
|
||||
if (!time_rx_left) {
|
||||
rx_position = hda_dsp_stream_get_position(hdac_stream(bpt_rx_stream),
|
||||
SNDRV_PCM_STREAM_CAPTURE, false);
|
||||
dev_err(dev, "%s: SDW BPT RX DMA did not complete: %ld\n",
|
||||
__func__, rx_position);
|
||||
ret = -ETIMEDOUT;
|
||||
goto dma_disable;
|
||||
}
|
||||
|
||||
/* Make sure the DMA is flushed */
|
||||
i = 0;
|
||||
do {
|
||||
rx_position = hda_dsp_stream_get_position(hdac_stream(bpt_rx_stream),
|
||||
SNDRV_PCM_STREAM_CAPTURE, false);
|
||||
usleep_range(1000, 1010);
|
||||
i++;
|
||||
} while (rx_position && i < HDA_BPT_IOC_TIMEOUT_MS);
|
||||
if (rx_position) {
|
||||
dev_err(dev, "%s: SDW BPT RX DMA position %ld was not cleared\n",
|
||||
__func__, rx_position);
|
||||
ret = -ETIMEDOUT;
|
||||
goto dma_disable;
|
||||
}
|
||||
|
||||
dma_disable:
|
||||
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_rx_stream);
|
||||
if (!ret)
|
||||
ret = ret1;
|
||||
|
||||
ret1 = hda_sdw_bpt_dma_disable(dev, bpt_tx_stream);
|
||||
if (!ret)
|
||||
ret = ret1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS(hda_sdw_bpt_wait, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
|
||||
|
||||
int hda_sdw_bpt_close(struct device *dev, struct hdac_ext_stream *bpt_tx_stream,
|
||||
struct snd_dma_buffer *dmab_tx_bdl, struct hdac_ext_stream *bpt_rx_stream,
|
||||
struct snd_dma_buffer *dmab_rx_bdl)
|
||||
{
|
||||
int ret;
|
||||
int ret1;
|
||||
|
||||
ret = hda_sdw_bpt_dma_deprepare(dev, bpt_rx_stream, dmab_rx_bdl);
|
||||
|
||||
ret1 = hda_sdw_bpt_dma_deprepare(dev, bpt_tx_stream, dmab_tx_bdl);
|
||||
if (!ret)
|
||||
ret = ret1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_NS(hda_sdw_bpt_close, "SND_SOC_SOF_INTEL_HDA_SDW_BPT");
|
||||
|
||||
MODULE_LICENSE("Dual BSD/GPL");
|
||||
MODULE_DESCRIPTION("SOF helpers for HDaudio SoundWire BPT");
|
||||
MODULE_IMPORT_NS("SND_SOC_SOF_INTEL_HDA_COMMON");
|
||||
MODULE_IMPORT_NS("SND_SOC_SOF_HDA_MLINK");
|
||||
Reference in New Issue
Block a user