Page MenuHomeFreeBSD

D57484.diff
No OneTemporary

D57484.diff

diff --git a/sys/arm/broadcom/bcm2835/bcm2835_clkman.h b/sys/arm/broadcom/bcm2835/bcm2835_clkman.h
--- a/sys/arm/broadcom/bcm2835/bcm2835_clkman.h
+++ b/sys/arm/broadcom/bcm2835/bcm2835_clkman.h
@@ -28,11 +28,12 @@
#ifndef _BCM2835_CLKMAN_H_
#define _BCM2835_CLKMAN_H_
-// Offset into BAR0 for unit
+/* Offset into BAR0 for unit (CM_*CTL register address) */
enum bcm2835_clksrc {
BCM_GPIO0_CLKSRC = 0x70,
BCM_GPIO1_CLKSRC = 0x78,
BCM_GPIO2_CLKSRC = 0x80,
+ BCM_PCM_CLKSRC = 0x98,
BCM_PWM_CLKSRC = 0xa0,
};
diff --git a/sys/arm/broadcom/bcm2835/bcm2835_clkman.c b/sys/arm/broadcom/bcm2835/bcm2835_clkman.c
--- a/sys/arm/broadcom/bcm2835/bcm2835_clkman.c
+++ b/sys/arm/broadcom/bcm2835/bcm2835_clkman.c
@@ -47,19 +47,14 @@
#include <arm/broadcom/bcm2835/bcm2835_clkman.h>
-static struct ofw_compat_data compat_data[] = {
- {"brcm,bcm2711-cprman", 1},
- {"brcm,bcm2835-cprman", 1},
- {"broadcom,bcm2835-cprman", 1},
- {NULL, 0}
-};
-
struct bcm2835_clkman_softc {
device_t sc_dev;
struct resource * sc_m_res;
bus_space_tag_t sc_m_bst;
bus_space_handle_t sc_m_bsh;
+
+ uint32_t sc_plld_freq;
};
#define BCM_CLKMAN_WRITE(_sc, _off, _val) \
@@ -72,6 +67,14 @@
#define W_CMDIV(_sc, unit, _val) BCM_CLKMAN_WRITE(_sc, (unit) + 4, 0x5a000000 | (_val))
#define R_CMDIV(_sc, unit) BCM_CLKMAN_READ(_sc, (unit) + 4)
+/* ocd_data holds PLLD_per in Hz: 750 MHz for BCM2711, 500 MHz for BCM2835. */
+static struct ofw_compat_data compat_data[] = {
+ {"brcm,bcm2711-cprman", 750000000},
+ {"brcm,bcm2835-cprman", 500000000},
+ {"broadcom,bcm2835-cprman", 500000000},
+ {NULL, 0}
+};
+
static int
bcm2835_clkman_probe(device_t dev)
{
@@ -101,6 +104,9 @@
sc = device_get_softc(dev);
sc->sc_dev = dev;
+ sc->sc_plld_freq = (uint32_t)
+ ofw_bus_search_compatible(dev, compat_data)->ocd_data;
+
rid = 0;
sc->sc_m_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
@@ -112,6 +118,13 @@
sc->sc_m_bst = rman_get_bustag(sc->sc_m_res);
sc->sc_m_bsh = rman_get_bushandle(sc->sc_m_res);
+ device_printf(dev, "PLLD_per frequency: %u Hz\n", sc->sc_plld_freq);
+
+ SYSCTL_ADD_UINT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+ OID_AUTO, "plld_freq", CTLFLAG_RD, &sc->sc_plld_freq, 0,
+ "PLLD_per source frequency in Hz");
+
bus_attach_children(dev);
return (0);
}
@@ -125,7 +138,7 @@
sc = device_get_softc(dev);
- if (unit != BCM_PWM_CLKSRC) {
+ if (unit != BCM_PWM_CLKSRC && unit != BCM_PCM_CLKSRC) {
device_printf(sc->sc_dev,
"Unsupported unit 0x%x", unit);
return (0);
@@ -146,20 +159,20 @@
if (hz == 0)
return (0);
- u = 500000000/hz;
+ u = sc->sc_plld_freq / hz;
if (u < 4) {
device_printf(sc->sc_dev,
- "Frequency too high for unit 0x%x (max: 125 MHz)",
- unit);
+ "Frequency too high for unit 0x%x (max: %u MHz)",
+ unit, sc->sc_plld_freq / 4 / 1000000);
return (0);
}
if (u > 0xfff) {
device_printf(sc->sc_dev,
- "Frequency too low for unit 0x%x (min: 123 kHz)",
- unit);
+ "Frequency too low for unit 0x%x (min: %u kHz)",
+ unit, sc->sc_plld_freq / 0xfff / 1000);
return (0);
}
- hz = 500000000/u;
+ hz = sc->sc_plld_freq / u;
W_CMDIV(sc, unit, u << 12);
W_CMCLK(sc, unit, 0x16);
diff --git a/sys/arm/broadcom/bcm2835/bcm2835_i2s.h b/sys/arm/broadcom/bcm2835/bcm2835_i2s.h
new file mode 100644
--- /dev/null
+++ b/sys/arm/broadcom/bcm2835/bcm2835_i2s.h
@@ -0,0 +1,181 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2026 Brisk4t
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _BCM2835_I2S_H_
+#define _BCM2835_I2S_H_
+
+/* Register map (base 0x7e203000, size 0x24) */
+
+#define BCM_I2S_CS_A 0x00 /* Control and status */
+#define BCM_I2S_FIFO_A 0x04 /* FIFO data */
+#define BCM_I2S_CS_A 0x00 /* Control and status */
+#define BCM_I2S_FIFO_A 0x04 /* FIFO data */
+
+#define BCM_I2S_MODE_A 0x08 /* Mode control */
+#define BCM_I2S_RXC_A 0x0c /* RX channel control */
+#define BCM_I2S_MODE_A 0x08 /* Mode control */
+#define BCM_I2S_RXC_A 0x0c /* RX channel control */
+
+#define BCM_I2S_TXC_A 0x10 /* TX channel control */
+#define BCM_I2S_DREQ_A 0x14 /* DMA request level */
+#define BCM_I2S_TXC_A 0x10 /* TX channel control */
+#define BCM_I2S_DREQ_A 0x14 /* DMA request level */
+
+#define BCM_I2S_INTEN_A 0x18 /* Interrupt enable */
+#define BCM_I2S_INTSTC_A 0x1c /* Interrupt status and clear */
+#define BCM_I2S_GRAY 0x20 /* PCM Gray mode control */
+#define BCM_I2S_INTEN_A 0x18 /* Interrupt enable */
+#define BCM_I2S_INTSTC_A 0x1c /* Interrupt status and clear */
+#define BCM_I2S_GRAY 0x20 /* PCM Gray mode control */
+
+/* CS_A */
+#define CS_A_STBY (1u << 25) /* RAM Standby Flag */
+#define CS_A_SYNC (1u << 24) /* Clock sync helper, echos after 2 PCM clocks */
+#define CS_A_RXSEX (1u << 23) /* Sign extend RX samples to 32 bits (RW) */
+#define CS_A_RXF (1u << 22) /* RX FIFO full (RO) */
+#define CS_A_TXE (1u << 21) /* TX FIFO empty (RO) */
+#define CS_A_RXD (1u << 20) /* RX FIFO has data (RO) */
+#define CS_A_TXD (1u << 19) /* TX FIFO has space (RO) */
+#define CS_A_RXR (1u << 18) /* RX needs reading (RO) */
+#define CS_A_TXW (1u << 17) /* TX needs writing (RO) */
+#define CS_A_RXERR (1u << 16) /* RX error (RO) */
+#define CS_A_TXERR (1u << 15) /* TX error (RO) */
+#define CS_A_RXSYNC (1u << 14) /* RX FIFO Sync (0 = Out of Sync) */
+#define CS_A_TXSYNC (1u << 13) /* TX FIFO Sync (0 = Out of Sync) */
+#define CS_A_DMAEN (1u << 9) /* DMA DREQ Enable */
+#define CS_A_RXTHR(n) (((n) & 3u) << 7) /* RX FIFO threshold for RXR (RW) */
+#define CS_A_TXTHR(n) (((n) & 3u) << 5) /* TX FIFO threshold for TXW (RW) */
+
+/* Shared threshold field values for CS_A_TXTHR(n) and CS_A_RXTHR(n) */
+#define CS_A_THR_EMPTY 0u /* TX: empty; RX: at least one sample */
+#define CS_A_THR_QTR 1u /* TX: < ¼ full; RX: ¼ full */
+#define CS_A_THR_3QTR 2u /* TX: < ¾ full; RX: ¾ full */
+#define CS_A_THR_FULL 3u /* TX: full minus one sample; RX: full */
+
+#define CS_A_RXCLR (1u << 4) /* Clear RX FIFO (WO, self-cleared) */
+#define CS_A_TXCLR (1u << 3) /* Clear TX FIFO (WO, self-cleared) */
+#define CS_A_TXON (1u << 2) /* Enable transmission (RW) */
+#define CS_A_RXON (1u << 1) /* Enable reception (RW) */
+#define CS_A_EN (1u << 0) /* Enable PCM Audio Interface (RW) */
+
+/* MODE_A */
+#define MODE_A_CLK_DIS (1u << 28) /* 1 = disable PCM clock */
+#define MODE_A_FRXP \
+ (1u << 25) /* RX packed mode: both channels in one FIFO word */
+#define MODE_A_FTXP \
+ (1u << 24) /* TX packed mode: one FIFO word fills both channels */
+#define MODE_A_CLKM (1u << 23) /* 1 = PCM_CLK slave (input) */
+#define MODE_A_CLKI (1u << 22) /* invert PCM_CLK */
+#define MODE_A_FSM (1u << 21) /* 1 = frame sync slave (input) */
+#define MODE_A_FSI (1u << 20) /* invert frame sync */
+#define MODE_A_FLEN(n) (((n) & 0x3ffu) << 10) /* frame len - 1 */
+#define MODE_A_FSLEN(n) (((n) & 0x3ffu) << 0) /* Frame sync length */
+#define MODE_A_CLK_DIS (1u << 28) /* 1 = disable PCM clock */
+#define MODE_A_FRXP \
+ (1u << 25) /* RX packed mode: both channels in one FIFO word */
+#define MODE_A_FTXP \
+ (1u << 24) /* TX packed mode: one FIFO word fills both channels */
+#define MODE_A_CLKM (1u << 23) /* 1 = PCM_CLK slave (input) */
+#define MODE_A_CLKI (1u << 22) /* invert PCM_CLK */
+#define MODE_A_FSM (1u << 21) /* 1 = frame sync slave (input) */
+#define MODE_A_FSI (1u << 20) /* invert frame sync */
+#define MODE_A_FLEN(n) (((n) & 0x3ffu) << 10) /* frame len - 1 */
+#define MODE_A_FSLEN(n) (((n) & 0x3ffu) << 0) /* Frame sync length */
+
+/* TXC_A / RXC_A — identical layout */
+#define CHxC_CH1WEX (1u << 31)
+#define CHxC_CH1EN (1u << 30)
+#define CHxC_CH1POS(n) (((n) & 0x3ffu) << 20)
+#define CHxC_CH1WID(n) (((n) & 0xfu) << 16) /* actual_width - 8 */
+#define CHxC_CH2WEX (1u << 15)
+#define CHxC_CH2EN (1u << 14)
+#define CHxC_CH2POS(n) (((n) & 0x3ffu) << 4)
+#define CHxC_CH2WID(n) (((n) & 0xfu) << 0) /* actual_width - 8 */
+#define CHxC_CH1WEX (1u << 31)
+#define CHxC_CH1EN (1u << 30)
+#define CHxC_CH1POS(n) (((n) & 0x3ffu) << 20)
+#define CHxC_CH1WID(n) (((n) & 0xfu) << 16) /* actual_width - 8 */
+#define CHxC_CH2WEX (1u << 15)
+#define CHxC_CH2EN (1u << 14)
+#define CHxC_CH2POS(n) (((n) & 0x3ffu) << 4)
+#define CHxC_CH2WID(n) (((n) & 0xfu) << 0) /* actual_width - 8 */
+
+/* INTEN_A / INTSTC_A — write 1 to INTSTC_A to clear */
+#define INTx_A_RXERR (1u << 3) /* RX Fifo error occured */
+#define INTx_A_TXERR (1u << 2) /* TX Fifo error occured */
+#define INTx_A_RXR (1u << 1) /* RX Read interrupt occured */
+#define INTx_A_TXW (1u << 0) /* TX Write interrupt occured */
+#define INTx_A_ALL (INTx_A_RXERR | INTx_A_TXERR | INTx_A_RXR | INTx_A_TXW)
+
+/* BCM2835 PCM/I2S peripheral DREQ assignments (ARM Peripherals Table 4-3) */
+#define BCM2835_I2S_DREQ_TX 2
+#define BCM2835_I2S_DREQ_RX 3
+#define BCM2835_I2S_DREQ_TX 2
+#define BCM2835_I2S_DREQ_RX 3
+
+/*
+ * Default frame geometry: 2 × ch_width BCLK per stereo frame (tight packing).
+ * BCLK = sample_rate × frame_len. frame_len and ch_width are updated by
+ * set_chanformat when the PCM format changes.
+ */
+#define BCM2835_I2S_FRAME_LEN 32 /* default: 2 × 16-bit slots */
+#define BCM2835_I2S_CHWIDTH 16 /* default bits per sample */
+#define BCM2835_I2S_FIFO_SIZE 64 /* 32-bit words */
+#define BCM2835_I2S_RATE_MIN 8000
+#define BCM2835_I2S_RATE_DEFAULT 48000
+#define BCM2835_I2S_RATE_MAX 192000 /* BCM2711 sustains 192 kHz */
+#define BCM2835_I2S_FRAME_LEN 32 /* default: 2 × 16-bit slots */
+#define BCM2835_I2S_CHWIDTH 16 /* default bits per sample */
+#define BCM2835_I2S_FIFO_SIZE 64 /* 32-bit words */
+#define BCM2835_I2S_RATE_MIN 8000
+#define BCM2835_I2S_RATE_DEFAULT 48000
+#define BCM2835_I2S_RATE_MAX 192000 /* BCM2711 sustains 192 kHz */
+
+struct bcm2835_i2s_softc {
+ device_t dev;
+ device_t clkman; /* bcm2835_clkman device */
+ struct resource *res[2];
+ struct mtx mtx;
+ void *intrhand;
+ uint32_t play_ptr;
+ uint32_t rec_ptr;
+ uint32_t sample_rate; /* current rate set by set_chanspeed */
+ uint32_t txthr; /* CS_A_TXTHR value for current rate */
+ uint32_t rxthr; /* CS_A_RXTHR value for current rate */
+ uint32_t frame_len; /* BCLK per stereo frame (2 × ch_width) */
+ uint32_t ch_width; /* hardware bits per sample */
+ uint32_t bytes_per_sample; /* PCM buffer bytes per sample */
+ uint32_t num_channels; /* 1 = mono, 2 = stereo */
+ int dai_fmt; /* DAI format from dai_init (AUDIO_DAI_FORMAT_*) */
+
+ /* true when FTXP/FRXP: one FIFO word per stereo frame */
+ bool packed_mode;
+};
+
+#endif /* _BCM2835_I2S_H_ */
diff --git a/sys/arm/broadcom/bcm2835/bcm2835_i2s.c b/sys/arm/broadcom/bcm2835/bcm2835_i2s.c
new file mode 100644
--- /dev/null
+++ b/sys/arm/broadcom/bcm2835/bcm2835_i2s.c
@@ -0,0 +1,795 @@
+/*
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2026 Brisk4t
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the author nor the names of its contributors may
+ * be used to endorse or promote products derived from this software
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_snd.h"
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/sound/fdt/audio_dai.h>
+#include <dev/sound/pcm/sound.h>
+
+#include <arm/broadcom/bcm2835/bcm2835_clkman.h>
+
+#include "audio_dai_if.h"
+#include "bcm2835_i2s.h"
+
+static struct ofw_compat_data compat_data[] = {
+ { "brcm,bcm2835-i2s", 1 },
+ { "brcm,bcm2711-i2s", 1 },
+ { NULL, 0 },
+};
+
+static struct resource_spec bcm2835_i2s_spec[] = {
+ { SYS_RES_MEMORY, 0, RF_ACTIVE },
+ { SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE },
+ { -1, 0 },
+};
+
+#define BCM2835_I2S_LOCK(sc) mtx_lock(&(sc)->mtx)
+#define BCM2835_I2S_UNLOCK(sc) mtx_unlock(&(sc)->mtx)
+#define BCM2835_I2S_READ_4(sc, reg) bus_read_4((sc)->res[0], (reg))
+#define BCM2835_I2S_WRITE_4(sc, reg, val) \
+ bus_write_4((sc)->res[0], (reg), (val))
+
+/*
+ * Wait for at least `cycles` PCM clocks using the CS_A SYNC echo mechanism.
+ * Each toggle-and-poll iteration consumes exactly 2 PCM clocks, so odd values
+ * are rounded up to the next even number.
+ */
+static void
+bcm2835_i2s_sync_wait(struct bcm2835_i2s_softc *sc, int cycles)
+{
+ uint32_t cs, target;
+ int i, n;
+
+ n = (cycles + 1) / 2;
+ for (i = 0; i < n; i++) {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ target = (cs ^ CS_A_SYNC) & CS_A_SYNC;
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ (cs & ~CS_A_SYNC) | target);
+ do {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ } while ((cs & CS_A_SYNC) != target);
+ }
+}
+
+static uint32_t sc_fmt[] = {
+ /* Supported sample formats */
+ SND_FORMAT(AFMT_S8, 1, 0),
+ SND_FORMAT(AFMT_S8, 2, 0),
+ SND_FORMAT(AFMT_S16_LE, 1, 0),
+ SND_FORMAT(AFMT_S16_LE, 2, 0),
+ SND_FORMAT(AFMT_S24_LE, 1, 0),
+ SND_FORMAT(AFMT_S24_LE, 2, 0),
+ SND_FORMAT(AFMT_S32_LE, 1, 0),
+ SND_FORMAT(AFMT_S32_LE, 2, 0),
+ 0,
+};
+
+static struct pcmchan_caps bcm2835_i2s_caps = {
+ BCM2835_I2S_RATE_MIN,
+ BCM2835_I2S_RATE_MAX,
+ sc_fmt,
+ 0,
+};
+
+/*
+ * Assemble a 32-bit word from PCM data in the buffer
+ */
+static inline uint32_t
+bcm2835_i2s_assemble_sample(const uint8_t *buf, uint32_t pos, uint32_t size,
+ int bps)
+{
+ int i;
+ uint32_t v = 0;
+
+ for (i = 0; i < bps; i++)
+ v |= (uint32_t)buf[(pos + i) % size] << (i * 8);
+ return (v);
+}
+
+/*
+ * Disassemble a 32-bit word from the FIFO into PCM data in the buffer
+ */
+static inline void
+bcm2835_i2s_disassemble_sample(uint8_t *buf, uint32_t pos, uint32_t size,
+ uint32_t val, int bps)
+{
+ int i;
+
+ for (i = 0; i < bps; i++)
+ buf[(pos + i) % size] = (val >> (i * 8)) & 0xff;
+}
+
+static int
+bcm2835_i2s_probe(device_t dev)
+{
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+
+ if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data)
+ return (ENXIO);
+
+ device_set_desc(dev, "BCM2835 I2S");
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+bcm2835_i2s_detach(device_t dev)
+{
+ struct bcm2835_i2s_softc *sc;
+
+ sc = device_get_softc(dev);
+
+ /* Disable the PCM block on detach */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, 0);
+
+ if (sc->intrhand != NULL)
+ bus_teardown_intr(dev, sc->res[1], sc->intrhand);
+
+ bus_release_resources(dev, bcm2835_i2s_spec, sc->res);
+ mtx_destroy(&sc->mtx);
+
+ return (0);
+}
+
+static int
+bcm2835_i2s_attach(device_t dev)
+{
+ struct bcm2835_i2s_softc *sc;
+ int error;
+ phandle_t node;
+
+ sc = device_get_softc(dev);
+ sc->dev = dev;
+
+ mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF);
+
+ /* Allocate resources */
+ if (bus_alloc_resources(dev, bcm2835_i2s_spec, sc->res) != 0) {
+ device_printf(dev, "cannot allocate resources\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ /* Find the clock manager */
+ sc->clkman = devclass_get_device(devclass_find("bcm2835_clkman"), 0);
+ if (sc->clkman == NULL) {
+ device_printf(dev, "cannot find bcm2835_clkman\n");
+ error = ENXIO;
+ goto fail;
+ }
+
+ sc->sample_rate = BCM2835_I2S_RATE_DEFAULT;
+ sc->frame_len = BCM2835_I2S_FRAME_LEN;
+ sc->ch_width = BCM2835_I2S_CHWIDTH;
+ sc->bytes_per_sample = BCM2835_I2S_CHWIDTH / 8;
+ sc->num_channels = 2;
+ sc->txthr = CS_A_THR_3QTR;
+ sc->rxthr = CS_A_THR_QTR;
+ sc->dai_fmt = AUDIO_DAI_FORMAT_I2S;
+ sc->packed_mode = (BCM2835_I2S_CHWIDTH <= 16);
+
+ node = ofw_bus_get_node(dev);
+ OF_device_register_xref(OF_xref_from_node(node), dev);
+
+ return (0);
+
+fail:
+ bcm2835_i2s_detach(dev);
+ return (error);
+}
+
+static int
+bcm2835_i2s_dai_init(device_t dev, uint32_t format)
+{
+ struct bcm2835_i2s_softc *sc;
+ int ch1pos, ch2pos, flen, fslen;
+ uint32_t chc, mode;
+ int clk, fmt, pol;
+
+ sc = device_get_softc(dev);
+
+ fmt = AUDIO_DAI_FORMAT_FORMAT(format);
+ pol = AUDIO_DAI_FORMAT_POLARITY(format);
+ clk = AUDIO_DAI_FORMAT_CLOCK(format);
+
+ mode = 0;
+ flen = 2 * sc->ch_width;
+
+ switch (clk) {
+ case AUDIO_DAI_CLOCK_CBM_CFM:
+ break;
+ case AUDIO_DAI_CLOCK_CBS_CFM:
+ mode |= MODE_A_CLKM;
+ break;
+ case AUDIO_DAI_CLOCK_CBM_CFS:
+ mode |= MODE_A_FSM;
+ break;
+ case AUDIO_DAI_CLOCK_CBS_CFS:
+ mode |= MODE_A_CLKM | MODE_A_FSM;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ switch (fmt) {
+ case AUDIO_DAI_FORMAT_I2S:
+ /* CH1 data begins one BCLK after the FS edge (I2S delay). */
+ fslen = sc->ch_width;
+ ch1pos = 1;
+ ch2pos = sc->ch_width + 1;
+ break;
+ case AUDIO_DAI_FORMAT_LJ:
+ case AUDIO_DAI_FORMAT_RJ:
+ /* RJ == LJ with tight slots (slot_width == sample_width). */
+ fslen = sc->ch_width;
+ ch1pos = 0;
+ ch2pos = sc->ch_width;
+ break;
+ case AUDIO_DAI_FORMAT_DSPA:
+ mode |= MODE_A_FSI;
+ fslen = 1;
+ ch1pos = 1;
+ ch2pos = sc->ch_width + 1;
+ break;
+ case AUDIO_DAI_FORMAT_DSPB:
+ fslen = 1;
+ ch1pos = 0;
+ ch2pos = sc->ch_width;
+ break;
+ default:
+ return (EINVAL);
+ }
+
+ /* Polarity: invert BCLK and/or FS if requested. */
+ if (AUDIO_DAI_POLARITY_INVERTED_BCLK(pol))
+ mode ^= MODE_A_CLKI;
+ if (AUDIO_DAI_POLARITY_INVERTED_FRAME(pol))
+ mode ^= MODE_A_FSI;
+
+ mode |= MODE_A_FLEN(flen - 1) | MODE_A_FSLEN(fslen);
+
+ sc->dai_fmt = fmt;
+ sc->frame_len = flen;
+
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, CS_A_EN); /* Enable PCM Block */
+
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_MODE_A, mode); /* Set the mode */
+
+ /* Channel Settings */
+ chc = CHxC_CH1EN | CHxC_CH1POS(ch1pos) | CHxC_CH1WID(sc->ch_width - 8) |
+ CHxC_CH2EN | CHxC_CH2POS(ch2pos) | CHxC_CH2WID(sc->ch_width - 8);
+
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_TXC_A, chc);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_RXC_A, chc);
+
+ /* Enable Interrupt driven mode */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTEN_A, 0);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTSTC_A, INTx_A_ALL);
+
+ /*
+ * Disable RAM standby unconditionally, I couldn't find any reference
+ * for proper usage, datasheet says "This may or may not be implemented,
+ * depending upon the RAM libraries being used"
+ */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, CS_A_EN | CS_A_STBY);
+
+ /* Step 6: flush FIFOs; RXSEX sign-extends narrow RX samples to 32 b. */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ CS_A_EN | CS_A_STBY | CS_A_TXCLR | CS_A_RXCLR | CS_A_RXSEX);
+
+ return (0);
+}
+
+/* Handle transmit interrupt */
+static void
+bcm2835_i2s_tx_intr(struct bcm2835_i2s_softc *sc, struct snd_dbuf *play_buf)
+{
+ uint8_t *samples;
+ uint32_t ch1, ch2, count, cs, readyptr, size, written;
+ int bps, frame_bytes;
+
+ bps = sc->bytes_per_sample;
+ frame_bytes = bps * sc->num_channels;
+ size = play_buf->bufsize;
+ samples = play_buf->buf;
+ written = 0;
+
+ count = sndbuf_getready(play_buf);
+ readyptr = sndbuf_getreadyptr(play_buf);
+
+ if (sc->packed_mode) {
+ while (count >= (uint32_t)frame_bytes) {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ if (!(cs & CS_A_TXD))
+ break;
+
+ ch1 = bcm2835_i2s_assemble_sample(samples, readyptr,
+ size, bps);
+ ch2 = bcm2835_i2s_assemble_sample(samples,
+ readyptr + bps, size, bps);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_FIFO_A,
+ ch1 | (ch2 << 16));
+
+ readyptr += frame_bytes;
+ written += frame_bytes;
+ count -= frame_bytes;
+ }
+ } else {
+ while (count >= (uint32_t)frame_bytes) {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ if (!(cs & CS_A_TXD))
+ break;
+
+ ch1 = bcm2835_i2s_assemble_sample(samples, readyptr,
+ size, bps);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_FIFO_A, ch1);
+ readyptr += bps;
+
+ if (sc->num_channels == 2) {
+ ch2 = bcm2835_i2s_assemble_sample(samples,
+ readyptr, size, bps);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_FIFO_A, ch2);
+ readyptr += bps;
+ }
+
+ written += frame_bytes;
+ count -= frame_bytes;
+ }
+ }
+
+ sc->play_ptr += written;
+ sc->play_ptr %= size;
+}
+
+/* Handle receive interrupt */
+static void
+bcm2835_i2s_rx_intr(struct bcm2835_i2s_softc *sc, struct snd_dbuf *rec_buf)
+{
+ uint8_t *samples;
+ uint32_t count, cs, freeptr, recorded, size, val;
+ int bps, frame_bytes;
+
+ bps = sc->bytes_per_sample;
+ frame_bytes = bps * sc->num_channels;
+ size = rec_buf->bufsize;
+ samples = rec_buf->buf;
+ recorded = 0;
+
+ count = sndbuf_getfree(rec_buf);
+ freeptr = sndbuf_getfreeptr(rec_buf);
+
+ if (sc->packed_mode) {
+ while (count >= (uint32_t)frame_bytes) {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ if (!(cs & CS_A_RXD))
+ break;
+
+ val = BCM2835_I2S_READ_4(sc, BCM_I2S_FIFO_A);
+
+ bcm2835_i2s_disassemble_sample(samples, freeptr, size,
+ val, bps);
+ bcm2835_i2s_disassemble_sample(samples, freeptr + bps,
+ size, val >> 16, bps);
+ freeptr += frame_bytes;
+ recorded += frame_bytes;
+ count -= frame_bytes;
+ }
+ } else {
+ while (count >= (uint32_t)frame_bytes) {
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ if (!(cs & CS_A_RXD))
+ break;
+
+ val = BCM2835_I2S_READ_4(sc, BCM_I2S_FIFO_A);
+
+ bcm2835_i2s_disassemble_sample(samples, freeptr, size,
+ val, bps);
+ freeptr += bps;
+
+ if (sc->num_channels == 2) {
+ val = BCM2835_I2S_READ_4(sc, BCM_I2S_FIFO_A);
+
+ bcm2835_i2s_disassemble_sample(samples, freeptr,
+ size, val, bps);
+ freeptr += bps;
+ }
+
+ recorded += frame_bytes;
+ count -= frame_bytes;
+ }
+ }
+
+ sc->rec_ptr += recorded;
+ sc->rec_ptr %= size;
+}
+
+static int
+bcm2835_i2s_dai_intr(device_t dev, struct snd_dbuf *play_buf,
+ struct snd_dbuf *rec_buf)
+{
+ struct bcm2835_i2s_softc *sc;
+ uint32_t intstc;
+ int ret = 0;
+
+ sc = device_get_softc(dev);
+
+ BCM2835_I2S_LOCK(sc);
+
+ intstc = BCM2835_I2S_READ_4(sc, BCM_I2S_INTSTC_A);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTSTC_A, intstc);
+
+ /* Handle errors */
+ if (intstc & (INTx_A_TXERR | INTx_A_RXERR)) {
+ uint32_t cs_err;
+
+ cs_err = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ device_printf(dev, "I2S error: intstc=0x%08x\n", intstc);
+
+ if (intstc & INTx_A_TXERR)
+ cs_err |= CS_A_TXCLR;
+
+ if (intstc & INTx_A_RXERR)
+ cs_err |= CS_A_RXCLR;
+
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, cs_err);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTSTC_A,
+ intstc & (INTx_A_TXERR | INTx_A_RXERR));
+ }
+
+ /* Handle TX interrupt */
+ if (intstc & INTx_A_TXW) {
+ bcm2835_i2s_tx_intr(sc, play_buf);
+ ret |= AUDIO_DAI_PLAY_INTR;
+ }
+
+ /* Handle RX interrupt */
+ if (intstc & INTx_A_RXR) {
+ bcm2835_i2s_rx_intr(sc, rec_buf);
+ ret |= AUDIO_DAI_REC_INTR;
+ }
+
+ BCM2835_I2S_UNLOCK(sc);
+
+ return (ret);
+}
+
+static struct pcmchan_caps *
+bcm2835_i2s_dai_get_caps(device_t dev)
+{
+ return (&bcm2835_i2s_caps);
+}
+
+static int
+bcm2835_i2s_dai_trigger(device_t dev, int go, int pcm_dir)
+{
+ struct bcm2835_i2s_softc *sc;
+ uint32_t cs, inten;
+
+ sc = device_get_softc(dev);
+
+ /* Return early on invalid / vchannel playback */
+ if ((pcm_dir != PCMDIR_PLAY) && (pcm_dir != PCMDIR_REC))
+ return (EINVAL);
+
+ switch (go) {
+ case PCMTRIG_START:
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+
+ /* Start Playback */
+ if (pcm_dir == PCMDIR_PLAY) {
+ /* Clear TX FIFO and wait for 2 PCM cycles */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, cs | CS_A_TXCLR);
+ bcm2835_i2s_sync_wait(sc, 2);
+
+ /* Set TXTHR */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ cs | CS_A_TXTHR(sc->txthr));
+
+ /* Clear any pending TX interrupts */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTSTC_A, INTx_A_TXW);
+
+ /* Enable TX interrupts */
+ inten = BCM2835_I2S_READ_4(sc, BCM_I2S_INTEN_A);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTEN_A,
+ inten | INTx_A_TXW);
+
+ /* Start TX */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ cs | CS_A_TXON | CS_A_TXTHR(sc->txthr));
+
+ } else if (pcm_dir == PCMDIR_REC) { /* Start recording */
+ /* Clear RX FIFO, wait for 2 PCM cycles */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, cs | CS_A_RXCLR);
+ bcm2835_i2s_sync_wait(sc, 2);
+
+ /* Set RXTHR */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ cs | CS_A_RXTHR(sc->rxthr));
+
+ /* Clear any pending RX interrupts */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTSTC_A, INTx_A_RXR);
+
+ /* Enable RX interrupts */
+ inten = BCM2835_I2S_READ_4(sc, BCM_I2S_INTEN_A);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTEN_A,
+ inten | INTx_A_RXR);
+
+ /* Start RX */
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A,
+ cs | CS_A_RXON | CS_A_RXTHR(sc->rxthr));
+ }
+ break;
+
+ case PCMTRIG_STOP:
+ case PCMTRIG_ABORT:
+ /* Stop PCM first; INTEN_A must not be written while running. */
+ cs = BCM2835_I2S_READ_4(sc, BCM_I2S_CS_A);
+ if (pcm_dir == PCMDIR_PLAY) {
+ cs &= ~CS_A_TXON;
+ cs |= CS_A_TXCLR;
+ } else {
+ cs &= ~CS_A_RXON;
+ cs |= CS_A_RXCLR;
+ }
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_CS_A, cs);
+
+ /* Disable interrupts */
+ inten = BCM2835_I2S_READ_4(sc, BCM_I2S_INTEN_A);
+ if (pcm_dir == PCMDIR_PLAY)
+ inten &= ~INTx_A_TXW;
+ else
+ inten &= ~INTx_A_RXR;
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_INTEN_A, inten);
+
+ /* Reset the rx/tx pointers */
+ BCM2835_I2S_LOCK(sc);
+ if (pcm_dir == PCMDIR_PLAY)
+ sc->play_ptr = 0;
+ else
+ sc->rec_ptr = 0;
+ BCM2835_I2S_UNLOCK(sc);
+ break;
+ }
+
+ return (0);
+}
+
+static uint32_t
+bcm2835_i2s_dai_get_ptr(device_t dev, int pcm_dir)
+{
+ struct bcm2835_i2s_softc *sc;
+ uint32_t ptr;
+
+ sc = device_get_softc(dev);
+
+ BCM2835_I2S_LOCK(sc);
+ ptr = (pcm_dir == PCMDIR_PLAY) ? sc->play_ptr : sc->rec_ptr;
+ BCM2835_I2S_UNLOCK(sc);
+
+ return (ptr);
+}
+
+static int
+bcm2835_i2s_dai_setup_intr(device_t dev, driver_intr_t intr_handler,
+ void *intr_arg)
+{
+ struct bcm2835_i2s_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (bus_setup_intr(dev, sc->res[1], INTR_TYPE_AV | INTR_MPSAFE, NULL,
+ intr_handler, intr_arg, &sc->intrhand)) {
+ device_printf(dev, "cannot setup interrupt handler\n");
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static uint32_t
+bcm2835_i2s_dai_set_chanformat(device_t dev, uint32_t format)
+{
+ struct bcm2835_i2s_softc *sc;
+ uint32_t chc_rx, chc_tx, mode;
+ int bps, ch1pos, ch2pos, ch_width, frame_len, fslen, num_ch, wid;
+ bool packed, wex;
+
+ sc = device_get_softc(dev);
+
+ ch_width = AFMT_BIT(format);
+ bps = AFMT_BPS(format);
+ num_ch = AFMT_CHANNEL(format); /* 1 = mono, 2 = stereo */
+
+ frame_len = 2 * ch_width;
+ wid = ch_width - 8;
+ wex = (ch_width > 23);
+
+ switch (sc->dai_fmt) {
+ case AUDIO_DAI_FORMAT_I2S:
+ fslen = ch_width;
+ ch1pos = 1;
+ ch2pos = ch_width + 1;
+ break;
+ case AUDIO_DAI_FORMAT_LJ:
+ case AUDIO_DAI_FORMAT_RJ:
+ fslen = ch_width;
+ ch1pos = 0;
+ ch2pos = ch_width;
+ break;
+ case AUDIO_DAI_FORMAT_DSPA:
+ fslen = 1;
+ ch1pos = 1;
+ ch2pos = ch_width + 1;
+ break;
+ case AUDIO_DAI_FORMAT_DSPB:
+ fslen = 1;
+ ch1pos = 0;
+ ch2pos = ch_width;
+ break;
+ default:
+ return (0);
+ }
+
+ /*
+ * Packed mode (FTXP/FRXP): Only valid
+ * for stereo ≤16-bit; mono uses one unpacked FIFO word per frame.
+ */
+ packed = (ch_width <= 16) && (num_ch == 2);
+
+ mode = BCM2835_I2S_READ_4(sc, BCM_I2S_MODE_A);
+ mode &= ~(MODE_A_FLEN(0x3ff) | MODE_A_FSLEN(0x3ff) | MODE_A_FTXP |
+ MODE_A_FRXP);
+ mode |= MODE_A_FLEN(frame_len - 1) | MODE_A_FSLEN(fslen);
+ if (packed)
+ mode |= MODE_A_FTXP | MODE_A_FRXP;
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_MODE_A, mode);
+
+ /*
+ * For stereo enable both channels; for mono enable CH1 only.
+ * CH2 disabled in mono: hardware outputs silence on the right slot
+ * (TX) and discards right-slot data (RX).
+ */
+ chc_tx = (wex ? CHxC_CH1WEX : 0) | CHxC_CH1EN | CHxC_CH1POS(ch1pos) |
+ CHxC_CH1WID(wid);
+ chc_rx = chc_tx;
+
+ if (num_ch == 2) {
+ chc_tx |= (wex ? CHxC_CH2WEX : 0) | CHxC_CH2EN |
+ CHxC_CH2POS(ch2pos) | CHxC_CH2WID(wid);
+ chc_rx = chc_tx;
+ }
+
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_TXC_A, chc_tx);
+ BCM2835_I2S_WRITE_4(sc, BCM_I2S_RXC_A, chc_rx);
+
+ BCM2835_I2S_LOCK(sc);
+ sc->frame_len = frame_len;
+ sc->ch_width = ch_width;
+ sc->bytes_per_sample = bps;
+ sc->num_channels = num_ch;
+ sc->packed_mode = packed;
+ BCM2835_I2S_UNLOCK(sc);
+
+ return (0);
+}
+
+static int
+bcm2835_i2s_dai_set_sysclk(device_t dev, unsigned int rate,
+ int dai_dir __unused)
+{
+ struct bcm2835_i2s_softc *sc;
+
+ sc = device_get_softc(dev);
+ if (sc->clkman == NULL)
+ return (ENXIO);
+
+ if (bcm2835_clkman_set_frequency(sc->clkman, BCM_PCM_CLKSRC, rate) ==
+ 0) {
+ device_printf(dev, "cannot set PCM clock to %u Hz\n", rate);
+ return (ENXIO);
+ }
+
+ return (0);
+}
+
+static uint32_t
+bcm2835_i2s_dai_set_chanspeed(device_t dev, uint32_t speed)
+{
+ struct bcm2835_i2s_softc *sc;
+ uint32_t actual_bclk, actual_rate, mode;
+
+ sc = device_get_softc(dev);
+ if (sc->clkman == NULL)
+ return (speed);
+
+ sc->txthr = (speed > 16000) ? 2 : 1;
+ sc->rxthr = 1;
+
+ mode = BCM2835_I2S_READ_4(sc, BCM_I2S_MODE_A);
+ if (mode & MODE_A_CLKM) {
+ /* Clock slave: external BCLK drives rate */
+ sc->sample_rate = speed;
+ return (speed);
+ }
+
+ actual_bclk = bcm2835_clkman_set_frequency(sc->clkman, BCM_PCM_CLKSRC,
+ speed * sc->frame_len);
+ if (actual_bclk == 0) {
+ device_printf(sc->dev, "could not set PCM clock for %u Hz\n",
+ speed);
+ return (speed);
+ }
+
+ actual_rate = actual_bclk / sc->frame_len;
+ sc->sample_rate = actual_rate;
+ return (actual_rate);
+}
+
+static device_method_t bcm2835_i2s_methods[] = {
+ /* Device interface */
+ DEVMETHOD(device_probe, bcm2835_i2s_probe),
+ DEVMETHOD(device_attach, bcm2835_i2s_attach),
+ DEVMETHOD(device_detach, bcm2835_i2s_detach),
+
+ DEVMETHOD(audio_dai_init, bcm2835_i2s_dai_init),
+ DEVMETHOD(audio_dai_setup_intr, bcm2835_i2s_dai_setup_intr),
+ DEVMETHOD(audio_dai_set_sysclk, bcm2835_i2s_dai_set_sysclk),
+ DEVMETHOD(audio_dai_set_chanspeed, bcm2835_i2s_dai_set_chanspeed),
+ DEVMETHOD(audio_dai_set_chanformat, bcm2835_i2s_dai_set_chanformat),
+ DEVMETHOD(audio_dai_intr, bcm2835_i2s_dai_intr),
+ DEVMETHOD(audio_dai_get_caps, bcm2835_i2s_dai_get_caps),
+ DEVMETHOD(audio_dai_trigger, bcm2835_i2s_dai_trigger),
+ DEVMETHOD(audio_dai_get_ptr, bcm2835_i2s_dai_get_ptr),
+
+ DEVMETHOD_END
+};
+
+static driver_t bcm2835_i2s_driver = {
+ "i2s",
+ bcm2835_i2s_methods,
+ sizeof(struct bcm2835_i2s_softc),
+};
+
+DRIVER_MODULE(bcm2835_i2s, simplebus, bcm2835_i2s_driver, 0, 0);
+MODULE_DEPEND(bcm2835_i2s, bcm2835_clkman, 1, 1, 1);
+SIMPLEBUS_PNP_INFO(compat_data);
diff --git a/sys/dts/arm64/overlays/bcm2711-i2s.dtso b/sys/dts/arm64/overlays/bcm2711-i2s.dtso
new file mode 100644
--- /dev/null
+++ b/sys/dts/arm64/overlays/bcm2711-i2s.dtso
@@ -0,0 +1,14 @@
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+/ {
+ compatible = "brcm,bcm2711";
+};
+
+&i2s {
+ status = "okay";
+ #sound-dai-cells = <0>;
+ interrupts = <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>;
+};
diff --git a/sys/dts/arm64/overlays/bcm2835-i2s.dtso b/sys/dts/arm64/overlays/bcm2835-i2s.dtso
new file mode 100644
--- /dev/null
+++ b/sys/dts/arm64/overlays/bcm2835-i2s.dtso
@@ -0,0 +1,12 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "brcm,bcm2837", "brcm,bcm2836", "brcm,bcm2835";
+};
+
+&i2s {
+ status = "okay";
+ #sound-dai-cells = <0>;
+ interrupts = <2 23>;
+};
diff --git a/sys/dts/arm64/overlays/bcm2835-pcm5102a.dtso b/sys/dts/arm64/overlays/bcm2835-pcm5102a.dtso
new file mode 100644
--- /dev/null
+++ b/sys/dts/arm64/overlays/bcm2835-pcm5102a.dtso
@@ -0,0 +1,36 @@
+/dts-v1/;
+/plugin/;
+
+/ {
+ compatible = "brcm,bcm2711", "brcm,bcm2837", "brcm,bcm2836", "brcm,bcm2835";
+};
+
+/*
+ * dummy-codec and simple-audio-card must live under the soc simplebus
+ * so their drivers (registered with simplebus) are probed.
+ */
+&{/soc} {
+ dummy_codec: dummy-codec {
+ compatible = "dummy-codec";
+ #sound-dai-cells = <0>;
+ };
+
+ sound {
+ compatible = "simple-audio-card";
+ simple-audio-card,name = "RPi-PCM5102A";
+ simple-audio-card,format = "i2s";
+ /*
+ * No bitclock-master/frame-master → audio_soc defaults to CPU
+ * as master (CBM_CFM). BCM2835 drives BCK and LRCK; PCM5102A
+ * receives them (its SCK pin derives MCLK from BCK internally).
+ */
+ status = "okay";
+ cpu_dai: simple-audio-card,cpu {
+ sound-dai = <&i2s>;
+ };
+
+ simple-audio-card,codec {
+ sound-dai = <&dummy_codec>;
+ };
+ };
+};
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -62,6 +62,7 @@
${_bce} \
${_bcm283x_clkman} \
${_bcm283x_pwm} \
+ ${_bcm2835_i2s} \
bfe \
bge \
bhnd \
@@ -944,6 +945,7 @@
.if ${MACHINE_CPUARCH} == "arm" || ${MACHINE_CPUARCH} == "aarch64"
_bcm283x_clkman= bcm283x_clkman
_bcm283x_pwm= bcm283x_pwm
+_bcm2835_i2s= bcm2835_i2s
_neta= neta
.endif
diff --git a/sys/modules/bcm2835_i2s/makefile b/sys/modules/bcm2835_i2s/makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/bcm2835_i2s/makefile
@@ -0,0 +1,11 @@
+.PATH: ${SRCTOP}/sys/arm/broadcom/bcm2835 \
+ ${SRCTOP}/sys/dev/sound/fdt
+
+KMOD= bcm2835_i2s
+SRCS= bcm2835_i2s.c
+SRCS+= device_if.h bus_if.h ofw_bus_if.h
+SRCS+= audio_dai_if.h
+SRCS+= opt_platform.h opt_sound.h opt_snd.h
+SRCS+= channel_if.h
+
+.include <bsd.kmod.mk>
diff --git a/sys/modules/dtb/rpi/Makefile b/sys/modules/dtb/rpi/Makefile
--- a/sys/modules/dtb/rpi/Makefile
+++ b/sys/modules/dtb/rpi/Makefile
@@ -5,7 +5,10 @@
.elif ${MACHINE_CPUARCH} == "aarch64"
DTSO= \
spigen-rpi3.dtso \
- spigen-rpi4.dtso
+ spigen-rpi4.dtso \
+ bcm2835-i2s.dtso \
+ bcm2711-i2s.dtso \
+ bcm2835-pcm5102a.dtso
.endif
.include <bsd.dtb.mk>

File Metadata

Mime Type
text/plain
Expires
Wed, Jun 17, 8:21 PM (3 h, 55 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
34034078
Default Alt Text
D57484.diff (35 KB)

Event Timeline