
#########################
lib_spdif: S/PDIF library
#########################

************
Introduction
************

S/PDIF (Sony/Philips Digital Interface) is a standard for transmitting digital audio signals over
relatively short distances between devices. It was developed by Sony and Philips and is used to
carry high-quality digital audio without the need for analog conversion, maintaining the integrity
of the audio signal.

S/PDIF can carry two channels of uncompressed PCM (Pulse Code Modulation) audio or over Optical
(TOSLINK) or Coaxial transmission mediums.

``lib_spdif`` provides software defined S/PDIF implementation that allows transmission and reception
of S/PDIF data via `xcore` ports.

Using ``lib_spdif``
===================

``lib_spdif`` is intended to be used with the `XCommon CMake <https://www.xmos.com/file/xcommon-cmake-documentation/?version=latest>`_
, the `XMOS` application build and dependency management system.

To use this library, include ``lib_spdif`` in the application's ``APP_DEPENDENT_MODULES`` list, for example::

    set(APP_DEPENDENT_MODULES "lib_spdif")

Applications should then include the ``spdif.h`` header file.

***************************
External signal description
***************************

The library implements the S/PDIF (Sony/Philips Digital Interface
Format) protocol for transporting uncompressed stereo PCM data of up to 24bits.

.. note:: The S/PDIF connections shown in the diagrams below are digital representations of S/PDIF and not an actual signal
   suitable for external devices (which is 0.5V pk-pk etc). External circuitry is required to interface with the chosen
   medium (optical or electrical).

Connecting to the `xcore` as transmitter
========================================

The precise transmission frequencies supported depend on the availability
of an external clock (e.g. a PLL or a crystal oscillator) that runs at a
frequency of *channels* * *sampleRate* * *64* or a power-of-2
multiple. For example, for 2 channels at 192 KHz the
external clock has to run at a frequency of 24.576 MHz. This same frequency
also supports 2 channels at 48 KHz (which requires a minimum frequency of
6.144 MHz). If both 44,1 and 48 KHz frequencies are to be supported, both a
24.576 MHz and a 22.579 MHz master clock is required.

When using an `xcore.ai` based device these frequencies can be generated by the on-chip
application/secondary PLL.

The connection of an S/PDIF transmit line to the `xcore` is shown in
:ref:`spdif_connect_tx`.

.. _spdif_connect_tx:

.. figure:: ../images/spdif_tx_connect.*
   :width: 60%

   Connecting S/PDIF transmit

The output signal will contain jitter at the level of +/-1 core clock (<2ns for a 500 MHz xcore)
this is typically inconsequential but if lower jitter levels are desired the signal can be re-clocked
by the external master clock to reduce the jitter to that of the external master clock.
A simple D-type flip flop can be used for this purpose.

The incoming clock signal is used to drive an internal clock and can be shared with other software
functions using the same master clock (e.g. ADAT transmit or I2S).

.. note:: The transmit stream user bits are set to 0. The validity bits are set to 0 (i.e. valid).

Connecting to the `xcore` as receiver
=====================================

The receiver can receive stereo PCM signals up to 192 KHz.

The connection of an S/PDIF receiver line to the xCORE is shown in
:ref:`spdif_connect_rx`.

.. _spdif_connect_rx:

.. figure:: ../images/spdif_rx_connect.png
   :width: 45%

   Connecting S/PDIF receiver

.. note:: Only a single wire is connected - the clock is recovered from the incoming data stream.

*****
Usage
*****

All S/PDIF functions can be accessed via the ``spdif.h`` header::

  #include <spdif.h>

``lib_spdif`` should also be added to the ``APP_DEPENDENT_MODULES`` application `XCommon-CMake`
`CMakeLists.txt` file.

.. note::
    The receiver and transmitter tasks each require a minimum of 62.5MHz to operate correctly.

S/PDIF transmitter
==================

S/PDIF components are instantiated as parallel tasks that run in a
``par`` statement. The application can connect via a channel
connection.

.. _spdif_tx_task_diag:

.. figure:: ../images/spdif_tx_task_diag.png
   :width: 60%

   S/PDIF transmit task diagram

For example, the following code instantiates an S/PDIF transmitter component
and connects to it:

.. literalinclude:: ../../examples/app_spdif_tx_example/src/main.xc
   :start-at: on tile[1]: out buffered
   :end-at: on tile[1]: clock

.. literalinclude:: ../../examples/app_spdif_tx_example/src/main.xc
   :start-at: int main(void)
   :end-at: } // end


The helper function ``spdif_tx_port_config()`` clocks the clock-block from the master clock
port and, in turn, clocks the S/PDIF transmit port from this clock-block.

The application can communicate with the components via API functions
that take the channel end as arguments e.g.:

.. literalinclude:: ../../examples/app_spdif_tx_example/src/main.xc
   :start-at: void generate_samples(chanend c)
   :end-before: void board_setup(void)

Configuring the underlying clock
--------------------------------

When using the transmit component, the internal clock needs to be
configured to run off the incoming signal e.g.::

    spdif_tx_port_config(p_spdif_tx, clk_audio, p_mclk_in, 7);

This function needs to be called before the ``spdif_tx()`` function in
the programs ``par`` statement.


In this function the ``configure_clock_src()`` is used configure a clock to run off an
incoming port - see the `XMOS Programming Guide <https://www.xmos.com/file/xmos-programming-guide>`_ for more information.

The last parameter is used with the ``set_clock_fall_delay()`` function to configure an
internal delay from the incoming clock signal to the internal clock's falling edge.
This is done to allow for the correct alignment of outgoing data with
the master clock at the external D-type flip-flop.

Note, the delay value shown above is a typical example and may need to be
tuned for the specific hardware being used.

S/PDIF receiver
===============

S/PDIF components are instantiated as parallel tasks that run in a
``par`` statement. The application can connect via a channel
connection.

.. _spdif_rx_task_diag:

.. figure:: ../images/spdif_rx_task_diag.png
   :width: 60%

   S/PDIF receiver task diagram

For example, the following code instantiates an S/PDIF receiver component
and connects to it:

.. literalinclude:: ../../examples/app_spdif_rx_example/src/main.xc
   :start-at: on tile[0]: in              port    p_coax_rx
   :end-at: on tile[0]:                 clock   audio_clk

.. literalinclude:: ../../examples/app_spdif_rx_example/src/main.xc
   :start-at: int main(void)
   :end-at: } // end

The application can communicate with the components via API functions
that take the channel end as arguments e.g.:

.. literalinclude:: ../../examples/app_spdif_rx_example/src/main.xc
   :start-at: void handle_samples(streaming chanend c)
   :end-before: void board_setup(void)

Note that a program can react to incoming samples using a
``select`` statement. More information on using ``par`` and ``select``
statements can be found in the `XMOS Programming Guide <https://www.xmos.com/file/xmos-programming-guide>`_.

Each 32-bit word received from the receive component via the channel has the following format:

+----------+----------------+
| Bit(s)   | Field          |
+==========+================+
| 3:0      | Preamble       |
+----------+----------------+
| 7:4      | Auxiliary data |
+----------+----------------+
| 27:8     | Audio sample   |
+----------+----------------+
| 28       | Validity       |
+----------+----------------+
| 29       | User           |
+----------+----------------+
| 30       | Control        |
+----------+----------------+
| 31       | Parity         |
+----------+----------------+

.. note:: The four auxilary data bits are typically used to extend the audio sample from 20 to 24 bits.

The ``spdif_rx_sample()`` helper function strips away all fields other than the Audio Sample and Auxiliary data and returns
this audio sample data in the upper 24 bits of the ``sample`` variable.

Should other fields be desired - for parity checking, for instance, regular channel communication syntax can be used. For example::

 void my_application(streaming chanend c)
 {
    int32_t sample;
    size_t count = 0;

    while(1)
    {
      c :> spdif_data;

      // Check parity
      int parity_error = spdif_rx_check_parity(spdif_data);

      if (parity_error == 0)
        count++;
    }
    ...


|newpage|

***
API
***

Creating an S/PDIF transmitter instance
========================================

.. doxygenfunction:: spdif_tx_port_config
.. doxygenfunction:: spdif_tx

|newpage|

S/PDIF transmitter API
======================

.. doxygenfunction:: spdif_tx_reconfigure_sample_rate
.. doxygenfunction:: spdif_tx_output
.. doxygenfunction:: spdif_tx_shutdown

|newpage|

Creating an S/PDIF receiver instance
====================================

.. doxygenfunction:: spdif_rx

|newpage|

S/PDIF receiver API
===================

.. doxygenfunction:: spdif_rx_sample
.. doxygenfunction:: spdif_rx_shutdown
.. doxygenfunction:: spdif_rx_check_parity



