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:
Linus Torvalds
2025-04-01 12:43:13 -07:00
28 changed files with 2487 additions and 103 deletions

View 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.

View 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 |
+---+---+--------+---------------+---------------+

View File

@@ -9,6 +9,8 @@ SoundWire Documentation
stream
error_handling
locking
bra
bra_cadence
.. only:: subproject and html

View File

@@ -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

View File

@@ -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
=====

View File

@@ -31,6 +31,7 @@ config SOUNDWIRE_AMD
config SOUNDWIRE_CADENCE
tristate
select CRC8
config SOUNDWIRE_INTEL
tristate "Intel SoundWire Master driver"

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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");

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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

View File

@@ -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");

View File

@@ -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,
};
/*

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;

View 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 */

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View 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");