diff --git a/docs/api/audio.rst b/docs/api/audio.rst
index c7e87072b28382a9205a4ae44c98f59bf5a2d4a5..c96ebffd57fedf49417d80d7281f970a5d5d8c7d 100644
--- a/docs/api/audio.rst
+++ b/docs/api/audio.rst
@@ -3,7 +3,9 @@
 ``audio`` module
 ================
 
-(placeholder docs, to be replaced with autodocs when that's a thing)
+Many of these functions are available in three variants: headphone volume,
+speaker volume, and volume. If :code:`headphones_are_connected()` returns 1
+the "headphone" variant is chosen, else the "speaker" variant is chosen.
 
 .. py:function:: headset_is_connected() -> bool
 
@@ -37,35 +39,101 @@
    Note: This function uses a hardware PGA for the coarse value and software
    for the fine value. These two methods are as of yet not synced so that
    there may be a transient volume "hiccup". "p1" badges only use software
-   volume. The unspecified variant automatically chooses the adequate channel
-   (**).
-
-.. py:function:: headphones_adjust_volume_dB
-.. py:function:: speaker_adjust_volume_dB
-.. py:function:: adjust_volume_dB
-
-.. py:function:: headphones_get_volume_dB
-.. py:function:: speaker_get_volume_dB
-.. py:function:: get_volume_dB
-
-.. py:function:: headphones_get_mute
-.. py:function:: speaker_get_mute
-.. py:function:: get_mute
-
-.. py:function:: headphones_set_mute
-.. py:function:: speaker_set_mute
-.. py:function:: set_mute
-
-.. py:function:: headphones_set_minimum_volume_dB
-.. py:function:: speaker_set_minimum_volume_dB
-.. py:function:: headphones_set_maximum_volume_dB
-.. py:function:: speaker_set_maximum_volume_dB
-
-.. py:function:: headphones_get_minimum_volume_dB
-.. py:function:: speaker_get_minimum_volume_dB
-.. py:function:: headphones_get_maximum_volume_dB
-.. py:function:: speaker_get_maximum_volume_dB
-
-.. py:function:: headphones_get_volume_relative
-.. py:function:: speaker_get_volume_relative
-.. py:function:: get_volume_relative
+   volume.
+
+.. py:function:: headphones_adjust_volume_dB(vol_dB : float) -> float
+.. py:function:: speaker_adjust_volume_dB(vol_dB : float) -> float
+.. py:function:: adjust_volume_dB(vol_dB : float) -> float
+
+   Like the :code:`audio_{headphones_/speaker_/}set_volume` family but changes
+   relative to last volume value.
+
+.. py:function:: headphones_get_volume_dB() -> float
+.. py:function:: speaker_get_volume_dB() -> float
+.. py:function:: get_volume_dB() -> float
+
+   Returns volume as set with :code:`audio_{headphones/speaker}_set_volume_dB`.
+
+.. py:function:: headphones_get_mute() -> int
+.. py:function:: speaker_get_mute() -> int
+.. py:function:: get_mute() -> int
+
+   Returns 1 if channel is muted, 0 if channel is unmuted.
+
+.. py:function:: headphones_set_mute(mute : int)
+.. py:function:: speaker_set_mute(mute : int)
+.. py:function:: set_mute(mute : int)
+
+   Mutes (mute = 1) or unmutes (mute = 0) the specified channel.
+
+   Note: Even if a channel is unmuted it might not play sound depending on
+   the return value of audio_headphone_are_connected. There is no override for
+   this (see HEADPHONE PORT POLICY below).
+
+.. py:function:: headphones_set_minimum_volume_dB(vol_dB : float) -> float
+.. py:function:: speaker_set_minimum_volume_dB(vol_dB : float) -> float
+.. py:function:: headphones_set_maximum_volume_dB(vol_dB : float) -> float
+.. py:function:: speaker_set_maximum_volume_dB(vol_dB : float) -> float
+
+   Set the minimum and maximum allowed volume levels for speakers and headphones
+   respectively. Clamps with hardware limitations. Maximum clamps below the minimum
+   value, minimum clamps above the maximum. Returns clamped value.
+
+.. py:function:: headphones_get_minimum_volume_dB() -> float
+.. py:function:: speaker_get_minimum_volume_dB() -> float
+.. py:function:: headphones_get_maximum_volume_dB() -> float
+.. py:function:: speaker_get_maximum_volume_dB() -> float
+
+   Returns the minimum and maximum allowed volume levels for speakers and headphones
+   respectively. Change with
+   :code:`audio_{headphones/speaker}_set_{minimum/maximum}_volume_dB`.
+
+.. py:function:: headphones_get_volume_relative() -> float
+.. py:function:: speaker_get_volume_relative() -> float
+.. py:function:: get_volume_relative() -> float
+
+   Syntactic sugar for drawing UI: Returns channel volume in a 0..1 range,
+   scaled into a 0.01..1 range according to the values set with
+   :code:`audio_{headphones_/speaker_/}set_{maximum/minimum}_volume_` and 0 if
+   in a fake mute condition.
+
+
+Headphone port policy
+---------------------
+
+Under normal circumstances it is an important feature to have a reliable speaker
+mute when plugging in headphones. However, since the headphone port on the badge
+can also be used for badge link, there are legimate cases where it is desirable to
+have the speakers unmuted while a cable is plugged into the jack.
+
+As a person who plugs in the headphones on the tram, doesn't put them on, turns on
+music to check if it's not accidentially playing on speakers and then finally puts
+on headphones (temporarily, of course, intermittent checks if the speakers didn't
+magically turn on are scheduled according to our general anxiety level) we wish to
+make it difficult to accidentially have sound coming from the speakers.
+
+Our proposed logic is as follows (excluding boot conditions):
+
+1) Badge link TX cannot be enabled for any of the headphone jack pins without a
+   cable detected in the jack. This is to protect users from plugging in headphones
+   while badge link is active and receiving a short but potentially very loud burst
+   of digital data before the software can react to the state change.
+
+2) If the software detects that the headphone jack has changed from unplugged to
+   plugged it *always* turns off speakers, no exceptions.
+
+3) If a user wishes to TX on headphone badge link, they must confirm a warning that
+   having headphones plugged in may potentially cause hearing damage *every time*.
+
+4) If a user wishes to RX or TX on headphone badge link while playing sound on the
+   onboard speakers, they must confirm a warning *every time*.
+
+We understand that these means seem extreme, but we find them to be a sensible
+default configuration to make sure people can safely operate the device without
+needing to refer to a manual.
+
+(TX here means any state that is not constantly ~GND with whatever impedance.
+While there are current limiting resistors (value TBD at the time of writing, but
+presumably 100R-470R) in series with the GPIOs, they still can generate quite some
+volume with standard 40Ohm-ish headphones. Ideally the analog switch will never
+switch to the GPIOs without a cable plugged in.)
diff --git a/docs/api/badge_link.rst b/docs/api/badge_link.rst
index 565a7952a04e166e9648df75fca7ca94deb0e620..a65d296720aa85b02f26081d0d85301dd961621a 100644
--- a/docs/api/badge_link.rst
+++ b/docs/api/badge_link.rst
@@ -3,9 +3,36 @@
 ``badge_link`` module
 =====================
 
-.. py:function:: get_active(pin_mask : int)
-.. py:function:: enable(pin_mask : int)
-.. py:function:: disable(pin_mask : int)
+Functions that take a :code:`pin_mask` take a combination of constants from
+:code:`badge_link.PIN_MASK_LINE_{IN/OUT}_{TIP/RING}`
+
+The GPIO indices to be used with :code:`machine.UART` are listed in
+:code:`badge_link.PIN_INDEX_LINE_{OUT/IN}_{TIP/RING}`.
+
+Functions
+---------
+
+.. py:function:: get_active(pin_mask : int) -> int
+
+   Gets active badge link ports. Returns a pin mask.
+
+.. py:function:: enable(pin_mask : int) -> int
+
+   Enables badge link ports.
+
+   Returns the output of :code:`badge_link.get_active()` after execution.
+
+   Do NOT connect headphones to a badge link port. You might hear a ringing for
+   a while. Warn user.
+
+.. py:function:: disable(pin_mask : int) -> int
+
+   Disables badge link ports.
+
+   Returns the output of spio_badge_link_get_active after execution.
+
+Pin masks
+---------
 
 .. py:data:: PIN_MASK_LINE_IN_TIP
    :type: int
@@ -21,6 +48,12 @@
    :type: int
 .. py:data:: PIN_MASK_ALL
    :type: int
+
+Pin indices
+-----------
+
+These values may vary across prototype revisions.
+
 .. py:data:: PIN_INDEX_LINE_IN_TIP
    :type: int
 .. py:data:: PIN_INDEX_LINE_IN_RING