# Audio Usage Instructions

## Overview

The K230 audio subsystem provides three capture paths and one playback path:

| Path | Signal Source | Pins Used | Sound Card |
| --- | --- | --- | --- |
| Built-in codec capture | Onboard analog microphone | Internal to the chip; no pins used | `K230_I2S_INNO` |
| External I2S digital microphone capture | I2S slave device (digital microphone) | IO32, IO33 + IO34 or IO36 | `K230_I2S_INNO` |
| PDM digital microphone capture | PDM microphone, up to 4 data lines and 8 channels | IO26 + IO27 (or IO37), IO28 (or IO35), IO36, IO34 | `K230_PDM` |
| Playback | Built-in codec or external I2S DAC | Headphone jack / IO35 | `K230_I2S_INNO` |

The default image enables only the built-in codec. **Both the external I2S digital microphone and the PDM digital microphone require board-level device tree modifications**,
as described in the following two sections.

View the sound cards in the system:

```bash
[root@canaan ~ ]#cat /proc/asound/cards
 0 [K230I2SINNO    ]: K230_I2S_INNO - K230_I2S_INNO
                      K230_I2S_INNO
 1 [K230PDM        ]: K230_PDM - K230_PDM
                      K230_PDM
```

The sound card numbering is determined by driver registration order. When PDM is not configured, only card 0 is present. The
`hw:0,0`, `hw:1,0`, and `amixer -c0` values in this document use the numbering from the actual test results above. Refer to
`cat /proc/asound/cards` for the actual numbering.

## Built-in codec

The `sound` node in the board-level device tree (present by default on each board and requires no modifications):

```dts
sound {
    status                      = "okay";
    compatible                  = "canaan,k230-audio-inno";
    canaan,model                = "K230_I2S_INNO";
    canaan,k230-i2s-controller  = <&i2s>;
    canaan,k230-audio-codec     = <&inno_codec>;
};
```

Recording and playback:

```bash
# Onboard analog microphone recording
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 -d 10 test.wav

# Playback
aplay -D hw:0,0 test.wav
```

## External I2S Digital Microphone

The external digital microphone connects to the K230 as an I2S slave device, with the K230 providing the clock. Two wiring options are supported,
and the option is selected in the device tree.

### Pins

| Pin | function | Feature | Purpose |
| --- | --- | --- | --- |
| io32 | alt2 | `IIS_CLK` | Bit clock, required for both wiring options |
| io33 | alt2 | `IIS_WS` | Frame clock, required for both wiring options |
| io34 | alt2 | `IIS_D_IN0` | Data input for wiring option A, corresponding to channels 0/1 |
| io36 | alt2 | `IIS_D_IN1` | Data input for wiring option B, corresponding to channels 2/3 |
| io35 | alt2 | `IIS_D_OUT0` | Data output, required only when connecting an external I2S DAC |

### Device Tree Properties

Add the following to the `sound` node. Both properties are optional, and when neither is specified, the behavior is the same as that of the default image:

| Property | Value | Default | Purpose |
| --- | --- | --- | --- |
| `canaan,i2s-route` | `internal-codec` / `external-i2s` | `internal-codec` | Source of channels 0/1: built-in codec or IO34 |
| `canaan,rx1-source` | `none` / `external-i2s` | `none` | Whether to enable IO36 as channels 2/3 |

`canaan,i2s-route` is only the default value at startup and can be switched at runtime using the `Codec Route` control.
`canaan,rx1-source` determines the maximum number of capture channels (2 or 4) and **can only be determined at startup**.

### Wiring Option A: External Digital Microphone on IO34, 2 Channels

Channels 0/1 come from IO34, and the built-in codec is not involved in capture.

```dts
/ {
    sound {
        status                      = "okay";
        compatible                  = "canaan,k230-audio-inno";
        canaan,model                = "K230_I2S_INNO";
        canaan,k230-i2s-controller  = <&i2s>;
        canaan,k230-audio-codec     = <&inno_codec>;

        canaan,i2s-route = "external-i2s";
    };
};

&iomux {
    i2s_pins: i2s_pins {
        pin_clk_ws {
            pins = "io32", "io33";  /* IIS_CLK, IIS_WS */
            function = "alt2";
            output-enable;
            input-disable;
            bias-disable;
            drive-strength = <7>;
        };
        pin_din0 {
            pins = "io34";
            function = "alt2";      /* IIS_D_IN0 */
            input-enable;
            output-disable;
            bias-disable;
        };
        pin_dout0 {
            pins = "io35";
            function = "alt2";      /* IIS_D_OUT0, can be omitted for recording only */
            output-enable;
            input-disable;
            bias-disable;
            drive-strength = <7>;
        };
    };
};

&i2s {
    pinctrl-names = "default";
    pinctrl-0 = <&i2s_pins>;
};
```

Capture:

```bash
amixer -c0 cset name='Codec Route' 'External I2S'   # Switch to IO34
arecord -D hw:0,0 -f S16_LE -r 44100 -c 2 -d 10 test.wav

amixer -c0 cset name='Codec Route' 'Internal Codec' # Switch back to the onboard analog microphone
```

```{attention}
`Codec Route` 是双向的：切到 `External I2S` 后，采集来自 IO34，播放数据从
**IO35** 输出，**板载喇叭无声**。要听声音需要在 IO35 接外置 I2S DAC，或者先切回
`Internal Codec` 再 `aplay`。

录音或播放进行中不能切换，此时 `amixer` 会返回 `Device or resource busy`。

要让选择在重启后保留，推荐用设备树属性 `canaan,i2s-route` 设定开机默认值。
默认镜像开机**不执行** `alsactl restore`，所以只跑 `alsactl store` 是不生效的，
要用它得自己补一个开机恢复脚本：

    alsactl store                       # 存到 /var/lib/alsa/asound.state
    cat > /etc/init.d/S60alsa <<'EOF'
    #!/bin/sh
    [ "$1" = start ] && alsactl restore
    EOF
    chmod +x /etc/init.d/S60alsa
```

### Wiring Option B: Built-in Codec + External Digital Microphone on IO36, 4 Channels

Channels 0/1 are from the onboard analog microphone, and channels 2/3 are from the external digital microphone on IO36. Both are captured simultaneously.
Add **only `canaan,rx1-source`** to the `sound` node:

```dts
/ {
    sound {
        status                      = "okay";
        compatible                  = "canaan,k230-audio-inno";
        canaan,model                = "K230_I2S_INNO";
        canaan,k230-i2s-controller  = <&i2s>;
        canaan,k230-audio-codec     = <&inno_codec>;

        canaan,rx1-source = "external-i2s";
    };
};

&iomux {
    i2s_pins: i2s_pins {
        pin_clk_ws {
            pins = "io32", "io33";  /* IIS_CLK, IIS_WS */
            function = "alt2";
            output-enable;
            input-disable;
            bias-disable;
            drive-strength = <7>;
        };
        pin_din1 {
            pins = "io36";
            function = "alt2";      /* IIS_D_IN1 → channels 2/3 */
            input-enable;
            output-disable;
            bias-disable;
        };
    };
};

&i2s {
    pinctrl-names = "default";
    pinctrl-0 = <&i2s_pins>;
};
```

With this wiring option, IO34 is not involved in capture and requires no configuration. Leave `Codec Route` at its default value
`Internal Codec`, and playback will output normally through the onboard speaker.

Capture and split into two files:

```bash
arecord -D hw:0,0 -f S16_LE -r 44100 -c 4 -d 10 test_4.wav

ffmpeg -i test_4.wav -af "pan=stereo|c0=c0|c1=c1" Built-in.wav
ffmpeg -i test_4.wav -af "pan=stereo|c0=c2|c1=c3" External.wav
```

The interleaving order of the 4 channels is `[Built-in L][Built-in R][External L][External R]`.

### Verification

```bash
# Wiring option B should print external-i2s. The device tree string has a trailing '\0'; remove it with tr for easy script comparison
cat /proc/device-tree/sound/canaan,rx1-source | tr -d '\0'

arecord -D hw:0,0 --dump-hw-params              # Wiring option B should display CHANNELS: [2 4]
amixer -c0 cget name='Codec Route'              # View the current path
```

When this property is not specified, `/proc/device-tree/sound/canaan,rx1-source` **does not exist**. It is normal for `cat` to report
`No such file or directory`; this indicates that the default value `none` is being used.

## PDM Digital Microphone

PDM is an independent capture path that does not occupy the I2S controller. It is registered as a separate sound card, `K230_PDM`,
**capture only, no playback**, and can be used simultaneously with recording and playback on the built-in codec.

### Wiring and Pins

A PDM microphone only requires a clock, data, ground, and power supply. Two microphones can be connected to one data line,
with the `L/R` pins of the two microphones connected to GND and VDD respectively, each occupying a different clock edge:

```text
K230 IO26 ─── CLK ──┬──▶ MIC A   L/R connected to GND → rising edge
                    └──▶ MIC B   L/R connected to VDD → falling edge

K230 IO27 ◀── DATA ─┬─── MIC A
                    └─── MIC B   Both microphones share this data line
```

The chip has four data lines, with two microphones on each line, supporting up to 8 channels:

| Data Line | Pin | Function | Corresponding Channels |
| --- | --- | --- | --- |
| PDM_IN0 | io27 (or io37) | alt3 (io37 is alt2) | 0, 1 |
| PDM_IN1 | io28 (or io35) | alt3 (io35 is alt2) | 2, 3 |
| PDM_IN2 | io36 | alt2 | 4, 5 |
| PDM_IN3 | io34 | alt2 | 6, 7 |

The clock always uses io26 (alt3, `PDM_CLK`).

### Device Tree Configuration

Add a `sound_pdm` node under the root node of the board device tree and configure the pins. The following is the
**minimum configuration for one data line and 2 channels**:

```dts
/ {
    sound_pdm: sound_pdm {
        status        = "okay";
        compatible    = "canaan,k230-audio-pdm";
        canaan,model  = "K230_PDM";
        clocks        = <&pdm_clk>;
        clock-names   = "pdm";
        dmas          = <&pdma 1 0xfff 0 0x21>;
        dma-names     = "rx";
        pinctrl-names = "default";
        pinctrl-0     = <&pdm_pins>;
    };
};

&iomux {
    pdm_pins: pdm_pins {
        pin_clk {
            pins = "io26";
            function = "alt3";      /* PDM_CLK */
            output-enable;
            input-disable;
            bias-disable;
            drive-strength = <7>;
            input-schmitt = <0>;
        };
        pin_data0 {
            pins = "io27";
            function = "alt3";      /* PDM_IN0 */
            input-enable;
            output-disable;
            bias-disable;
            drive-strength = <4>;
            input-schmitt = <0>;
        };
    };
};
```

The `clocks` and `dmas` lines use a fixed format and can be copied as-is.

### Optional Properties

All four properties are optional. If omitted, the default values shown below are used (one data line, both edges, 2 channels):

| Property | Value | Default | Purpose |
| --- | --- | --- | --- |
| `canaan,pdm-data-lines` | `1` ~ `4` | `1` | Number of connected data lines, counted starting from PDM_IN0 |
| `canaan,pdm-edge` | `both` / `rising` / `falling` | `both` | Clock edge used for sampling; `both` means two microphones on one line |
| `canaan,pdm-oversample` | `32` / `64` / `128` | `64` | Oversampling ratio, which determines the PDM clock frequency together with the sample rate |
| `canaan,pdm-io-group` | `io27-28` / `io35-37` | `io27-28` | Pin group to which the first two data lines are connected |

**Number of channels = number of data lines × (`both` ? 2 : 1)**, up to 8.

```{attention}
`canaan,pdm-io-group` 必须和实际接线一致。前两根数据线接在 io35/io37 上的板卡
要写 `canaan,pdm-io-group = "io35-37"`，配错的表现是全程静音。

选了 `io35-37` 之后，**io35 和 io37 就不能再作 I2S 数据输出**，也就是
io35（`IIS_D_OUT0`）上接不了外置 I2S DAC 播放；同时 io27、io28 无论 pinctrl
怎么配都收不到 PDM 数据了。第 3、4 根数据线（io36、io34）不受这个属性影响，
按 pinctrl 配置即可。
```

### Adding Data Lines

`canaan,pdm-data-lines` and the pins in `pdm_pins` must be changed together. List as many pins in the `pins` array as the number of data lines being used. When all 4 lines are connected (8 channels):

```dts
    sound_pdm: sound_pdm {
        /* Other properties are the same as above */
        canaan,pdm-data-lines = <4>;    /* 4 lines × both edges = 8 channels */
    };
```

```dts
&iomux {
    pdm_pins: pdm_pins {
        pin_clk {
            pins = "io26";
            function = "alt3";      /* PDM_CLK */
            output-enable;
            input-disable;
            bias-disable;
            drive-strength = <7>;
            input-schmitt = <0>;
        };

        pin_data01 {
            pins = "io27", "io28";  /* PDM_IN0、PDM_IN1 */
            function = "alt3";
            input-enable;
            output-disable;
            bias-disable;
            drive-strength = <4>;
            input-schmitt = <0>;
        };

        pin_data23 {
            pins = "io36", "io34";  /* PDM_IN2、PDM_IN3 */
            function = "alt2";
            input-enable;
            output-disable;
            bias-disable;
            drive-strength = <4>;
            input-schmitt = <0>;
        };
    };
};
```

All pins in a child node share the same `function` and configuration, so the data pins are divided into two child nodes according to the mux setting: **io27 and io28 are alt3, while io36 and io34 are alt2**. Pins with different settings cannot be placed together.

For 6 channels only, use `canaan,pdm-data-lines = <3>` and change the `pins` of `pin_data23` to include only `"io36"`. For 4 channels only, use `<2>` and omit the entire `pin_data23` node.

```{attention}
**When using the 4th line (io34), `Codec Route` must be switched to `External I2S`**,
otherwise channels 6 and 7 will always contain constant full-scale values:

    amixer -c0 cset name='Codec Route' 'External I2S'

After switching, capture and playback on the built-in codec are unavailable (onboard analog microphone and onboard speaker).
Using only 3 lines (io27 + io28 + io36, 6 channels) does not have this restriction.
```

### Capture

```bash
# First confirm that the configuration detected by the driver matches the actual wiring
dmesg | grep -i pdm
# PDM capture: 1 data line(s) on IO27/IO28, both edge, 64x oversampling, up to 2 channels

# Record
arecord -D hw:1,0 -f S16_LE -r 48000 -c 2 -d 10 pdm.wav

# Play back through the onboard speaker for listening
aplay -D hw:0,0 pdm.wav
```

The hardware outputs 24-bit data, and the driver supports three formats:

| `-f` | Resulting Data | Use Case |
| --- | --- | --- |
| `S16_LE` | Upper 16 bits of 24-bit data | General recording and speech recognition; recommended |
| `S24_LE` | Full 24-bit data stored in the low bits of a 4-byte container | When full precision is required |
| `S32_LE` | 24-bit data shifted left by 8 bits | When the subsequent processing chain requires 32-bit alignment |

For multichannel capture, the channel count must be selected continuously from 1 to the maximum and cannot be specified with gaps:

```bash
arecord -D hw:1,0 --dump-hw-params 2>&1 | grep -i channels
# 根线时应为 CHANNELS: [1 8]

amixer -c0 cset name='Codec Route' 'External I2S'   # 需要声道 6、7 时才执行
arecord -D hw:1,0 -f S16_LE -r 48000 -c 8 -d 5 八声道.wav

# 拆成单声道文件
for i in 0 1 2 3 4 5 6 7; do
    ffmpeg -y -i 八声道.wav -af "pan=mono|c0=c$i" ch$i.wav
done
```

Channel mapping when `canaan,pdm-edge = "both"`:

| Channel | Source | Channel | Source |
| --- | --- | --- | --- |
| 0 | io27, L/R connected to GND | 4 | io36, L/R connected to GND |
| 1 | io27, L/R connected to VDD | 5 | io36, L/R connected to VDD |
| 2 | io28, L/R connected to GND | 6 | io34, L/R connected to GND |
| 3 | io28, L/R connected to VDD | 7 | io34, L/R connected to VDD |

PDM and the built-in codec can be used simultaneously:

```bash
arecord -D hw:1,0 -f S16_LE -r 48000 -c 2 -d 20 pdm.wav &
arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -d 20 onboard-mic.wav &
aplay   -D hw:0,0 test.wav
wait
```

## Resource Conflicts

io34 and io36 serve as both I2S data inputs and PDM data inputs, and can only be used by one path at a time:

| Requirement | PDM Data Lines | Available External I2S Digital Mics |
| --- | --- | --- |
| 2-Channel PDM | io27 | Both io34 and io36 are available |
| 4-Channel PDM | io27 + io28 | Both io34 and io36 are available |
| 6-Channel PDM | + io36 | Only io34 remains available |
| 8-Channel PDM | + io36 + io34 | Neither is available, and `Codec Route` must be switched |

The onboard analog microphone (built-in codec capture) and speaker playback are unaffected under the above combinations,
but are unavailable after `Codec Route` is switched to `External I2S`.

The table above applies when the first two data lines are connected to io27 and io28 (the default `canaan,pdm-io-group = "io27-28"`).
When they are connected to io35 and io37 instead (`canaan,pdm-io-group = "io35-37"`), there is an additional conflict: io35 is
`IIS_D_OUT0`, so once it is occupied by PDM, **external I2S DAC playback is unavailable**, while onboard speaker playback remains unaffected.
