Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F159665324
D57484.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
35 KB
Referenced Files
None
Subscribers
None
D57484.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D57484: Added interrupt-driven I2S driver for Raspberry Pi 4 and prior
Attached
Detach File
Event Timeline
Log In to Comment