Skip to content

Commit 1c0197c

Browse files
committed
WIP: Add support for audio output on NRF5340-AUDIO-DK
Signed-off-by: Titouan Christophe <[email protected]>
1 parent f23b1f5 commit 1c0197c

File tree

5 files changed

+504
-0
lines changed

5 files changed

+504
-0
lines changed

drivers/audio/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ zephyr_library_sources_ifdef(CONFIG_AUDIO_DMIC_MCUX dmic_mcux.c)
1313
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_WM8904 wm8904.c)
1414
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_WM8962 wm8962.c)
1515
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_CS43L22 cs43l22.c)
16+
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_CS47L63 cs47l63.c)
1617
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_PCM1681 pcm1681.c)
1718
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_MAX98091 max98091.c)
1819
zephyr_library_sources_ifdef(CONFIG_AUDIO_DMIC_AMBIQ_PDM dmic_ambiq_pdm.c)

drivers/audio/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ module-str = audio codec
3636
source "subsys/logging/Kconfig.template.log_config"
3737

3838
source "drivers/audio/Kconfig.cs43l22"
39+
source "drivers/audio/Kconfig.cs47l63"
3940
source "drivers/audio/Kconfig.max98091"
4041
source "drivers/audio/Kconfig.pcm1681"
4142
source "drivers/audio/Kconfig.tas6422dac"

drivers/audio/cs47l63.c

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
/*
2+
* Copyright (c) 2025 Titouan Christophe
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#include <zephyr/audio/codec.h>
7+
#include <zephyr/drivers/gpio.h>
8+
#include <zephyr/drivers/spi.h>
9+
#include <zephyr/sys/byteorder.h>
10+
11+
#include "cs47l63.h"
12+
13+
#include <zephyr/logging/log.h>
14+
LOG_MODULE_REGISTER(cirrus_cs47l63);
15+
16+
#define DT_DRV_COMPAT cirrus_cs47l63
17+
18+
struct cs47l63_config {
19+
struct spi_dt_spec spi;
20+
struct gpio_dt_spec reset_gpio;
21+
struct gpio_dt_spec irq_gpio;
22+
};
23+
24+
#define LOG_REG(spi, reg) \
25+
do {uint32_t v; \
26+
cs47l63_read_spi_regs(spi, reg, &v, 1); \
27+
LOG_INF("%20s (%4X) = %08X", &(#reg)[12], reg, v);} while (0)
28+
29+
/**
30+
* @brief Read 32b registers from the CS47L63
31+
* @param[in] spi The spi spec onto which the transfer happens
32+
* @param[in] start_reg The address of the first register to read from
33+
* @param values Where to store the register values.
34+
* The resulting values are in native CPU endianness
35+
* @param[in] count The number of 32b registers to read
36+
* @return same as spi_transceive
37+
*/
38+
static int cs47l63_read_spi_regs(const struct spi_dt_spec *spi,
39+
uint32_t start_reg, uint32_t *values, size_t count)
40+
{
41+
int ret;
42+
/* Register address + READ bit set */
43+
uint32_t addr = sys_cpu_to_be32(BIT(31) | start_reg);
44+
struct spi_buf tx_buf = {.buf = (void*) &addr, .len = sizeof(addr)};
45+
struct spi_buf_set tx_bufset = {.buffers = &tx_buf, .count = 1};
46+
uint32_t rx_padbuf[2];
47+
struct spi_buf rx_bufs[2] = {
48+
/* 1 empty rx word while we write out the address
49+
* + 1 padding word (see datasheet 4.13.1)
50+
*/
51+
{.buf = rx_padbuf, .len = sizeof(rx_padbuf)},
52+
/* The actual reading result */
53+
{.buf = values, .len = count * sizeof(uint32_t)},
54+
};
55+
56+
struct spi_buf_set rx_bufset = {.buffers = rx_bufs, .count = 2};
57+
58+
ret = spi_transceive_dt(spi, &tx_bufset, &rx_bufset);
59+
if (ret != 0) {
60+
LOG_ERR("Unable to read register: %d", ret);
61+
return ret;
62+
}
63+
64+
for (size_t i=0; i<count; i++) {
65+
values[i] = sys_be32_to_cpu(values[i]);
66+
}
67+
68+
return 0;
69+
}
70+
71+
static int cs47l63_write_spi_blob(const struct spi_dt_spec *spi,
72+
uint32_t addr, const uint8_t *data, size_t len)
73+
{
74+
int ret;
75+
uint32_t setup_buf[2] = {
76+
sys_cpu_to_be32(addr & 0x7FFFFFFF),
77+
0, /* Padding (see datasheet 4.13.1) */
78+
};
79+
struct spi_buf tx_bufs[2] = {
80+
{.buf = setup_buf, .len = sizeof(setup_buf)},
81+
{.buf = (void *) data, .len = len},
82+
};
83+
struct spi_buf_set bufset = {.buffers = tx_bufs, .count = 2};
84+
85+
ret = spi_write_dt(spi, &bufset);
86+
if (ret != 0) {
87+
LOG_ERR("Unable to write blob[%d]: %d", len, ret);
88+
}
89+
90+
return ret;
91+
}
92+
93+
static inline int cs47l63_write_spi_reg(const struct spi_dt_spec *spi,
94+
uint32_t reg, uint32_t value)
95+
{
96+
uint32_t encoded_value = sys_cpu_to_be32(value);
97+
return cs47l63_write_spi_blob(spi, reg, (const uint8_t *) &encoded_value, 4);
98+
}
99+
100+
static int cs47l63_configure(const struct device *dev, struct audio_codec_cfg *audiocfg)
101+
{
102+
int ret;
103+
uint8_t format, wordsize = audiocfg->dai_cfg.i2s.word_size;
104+
const struct cs47l63_config *cfg = dev->config;
105+
106+
if (audiocfg->dai_route != AUDIO_ROUTE_PLAYBACK) {
107+
return -ENOTSUP;
108+
}
109+
110+
if (wordsize < 16 || wordsize > 32) {
111+
return -ENOTSUP;
112+
}
113+
114+
switch (audiocfg->dai_type) {
115+
case AUDIO_DAI_TYPE_LEFT_JUSTIFIED:
116+
format = ASP_FMT_LEFTJUST;
117+
break;
118+
case AUDIO_DAI_TYPE_I2S:
119+
format = ASP_FMT_I2S;
120+
break;
121+
default:
122+
return -ENOTSUP;
123+
}
124+
125+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_ASP1_CONTROL2,
126+
(wordsize << 24) | (format << 8));
127+
if (ret != 0) {
128+
return -EIO;
129+
}
130+
131+
return 0;
132+
}
133+
134+
static void cs47l63_start_output(const struct device *dev)
135+
{
136+
}
137+
138+
static void cs47l63_stop_output(const struct device *dev)
139+
{
140+
}
141+
142+
static int cs47l63_set_property(const struct device *dev, audio_property_t property,
143+
audio_channel_t channel, audio_property_value_t val)
144+
{
145+
return 0;
146+
}
147+
148+
static int cs47l63_apply_properties(const struct device *dev)
149+
{
150+
return 0;
151+
}
152+
153+
static const struct audio_codec_api cs47l63_driver_api = {
154+
.configure = cs47l63_configure,
155+
.start_output = cs47l63_start_output,
156+
.stop_output = cs47l63_stop_output,
157+
.set_property = cs47l63_set_property,
158+
.apply_properties = cs47l63_apply_properties,
159+
};
160+
161+
static int apply_titou_config(const struct spi_dt_spec *spi)
162+
{
163+
// Set GPIO1-4 to pin-specific alternate function (ASP1)
164+
uint8_t zeros[16];
165+
memset(zeros, 0, sizeof(zeros));
166+
cs47l63_write_spi_blob(spi, CS47L63_REG_GPIO1_CTRL1, zeros, sizeof(zeros));
167+
168+
// Set GPIO10 to alternate function "Output signal path status"
169+
cs47l63_write_spi_reg(spi, CS47L63_REG_GPIO10_CTRL1, 0x1FA);
170+
171+
// 16 BCLK cycles per slot, I2S format
172+
cs47l63_write_spi_reg(spi, CS47L63_REG_ASP1_CONTROL2,
173+
(16 << 24) | (ASP_FMT_I2S << 8));
174+
175+
// Rx: Channel 1 on slot 0, channel 2 on slot 1
176+
cs47l63_write_spi_reg(spi, CS47L63_REG_ASP1_FRAME_CONTROL5, (0x01 << 8));
177+
178+
// 16 valid data bits per Rx slot
179+
cs47l63_write_spi_reg(spi, CS47L63_REG_ASP1_DATA_CONTROL5, 16);
180+
181+
// Enable Rx chan 1&2 from ASP1
182+
cs47l63_write_spi_reg(spi, CS47L63_REG_ASP1_ENABLES1, BIT(16) | BIT(17));
183+
184+
// Route to output
185+
cs47l63_write_spi_reg(spi, CS47L63_REG_OUT1L_INPUT1, MIXER_SRC_ASP1_RX1);
186+
cs47l63_write_spi_reg(spi, CS47L63_REG_OUT1L_INPUT2, MIXER_SRC_ASP1_RX2);
187+
188+
// Enable output
189+
cs47l63_write_spi_reg(spi, CS47L63_REG_OUTPUT_ENABLE_1, BIT(1));
190+
191+
// Unmute, set to 0dB
192+
cs47l63_write_spi_reg(spi, CS47L63_REG_OUT1L_VOLUME_1, 0x280);
193+
194+
// Set to 16kHz sample rate
195+
cs47l63_write_spi_reg(spi, CS47L63_REG_SAMPLE_RATE1, SAMPLE_RATE_16_kHz);
196+
197+
// System clock enabled & derived from ASP1_BCLK
198+
uint32_t current_system_clock;
199+
cs47l63_read_spi_regs(spi, CS47L63_REG_SYSTEM_CLOCK1, &current_system_clock, 1);
200+
cs47l63_write_spi_reg(spi, CS47L63_REG_SYSTEM_CLOCK1, BIT(6) | current_system_clock);
201+
202+
LOG_INF("Titou's config applied !");
203+
204+
return 0;
205+
}
206+
207+
static void print_all_regs(const struct spi_dt_spec *spi)
208+
{
209+
LOG_INF("--------------------------------------");
210+
LOG_REG(spi, CS47L63_REG_DEVID);
211+
LOG_REG(spi, CS47L63_REG_REVID);
212+
LOG_REG(spi, CS47L63_REG_GPIO_STATUS1);
213+
LOG_REG(spi, CS47L63_REG_GPIO1_CTRL1);
214+
LOG_REG(spi, CS47L63_REG_GPIO2_CTRL1);
215+
LOG_REG(spi, CS47L63_REG_GPIO3_CTRL1);
216+
LOG_REG(spi, CS47L63_REG_GPIO4_CTRL1);
217+
LOG_REG(spi, CS47L63_REG_GPIO5_CTRL1);
218+
LOG_REG(spi, CS47L63_REG_GPIO6_CTRL1);
219+
LOG_REG(spi, CS47L63_REG_GPIO7_CTRL1);
220+
LOG_REG(spi, CS47L63_REG_GPIO8_CTRL1);
221+
LOG_REG(spi, CS47L63_REG_GPIO9_CTRL1);
222+
LOG_REG(spi, CS47L63_REG_GPIO10_CTRL1);
223+
LOG_REG(spi, CS47L63_REG_GPIO11_CTRL1);
224+
LOG_REG(spi, CS47L63_REG_GPIO12_CTRL1);
225+
LOG_REG(spi, CS47L63_REG_CLKGEN_PAD_CTRL);
226+
LOG_REG(spi, CS47L63_REG_CLOCK32K);
227+
LOG_REG(spi, CS47L63_REG_SYSTEM_CLOCK1);
228+
LOG_REG(spi, CS47L63_REG_SYSTEM_CLOCK2);
229+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE1);
230+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE2);
231+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE3);
232+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE4);
233+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE_STATUS1);
234+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE_STATUS2);
235+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE_STATUS3);
236+
LOG_REG(spi, CS47L63_REG_SAMPLE_RATE_STATUS4);
237+
LOG_REG(spi, CS47L63_REG_OUTPUT_ENABLE_1);
238+
LOG_REG(spi, CS47L63_REG_OUTPUT_STATUS_1);
239+
LOG_REG(spi, CS47L63_REG_OUTPUT_CONTROL_1);
240+
LOG_REG(spi, CS47L63_REG_OUT1L_VOLUME_1);
241+
LOG_REG(spi, CS47L63_REG_OUT1L_CONTROL_1);
242+
LOG_REG(spi, CS47L63_REG_ASP1_ENABLES1);
243+
LOG_REG(spi, CS47L63_REG_ASP1_CONTROL1);
244+
LOG_REG(spi, CS47L63_REG_ASP1_CONTROL2);
245+
LOG_REG(spi, CS47L63_REG_ASP1_CONTROL3);
246+
LOG_REG(spi, CS47L63_REG_ASP1_FRAME_CONTROL1);
247+
LOG_REG(spi, CS47L63_REG_ASP1_FRAME_CONTROL2);
248+
LOG_REG(spi, CS47L63_REG_ASP1_FRAME_CONTROL5);
249+
LOG_REG(spi, CS47L63_REG_ASP1_FRAME_CONTROL6);
250+
LOG_REG(spi, CS47L63_REG_ASP1_DATA_CONTROL1);
251+
LOG_REG(spi, CS47L63_REG_ASP1_DATA_CONTROL5);
252+
LOG_REG(spi, CS47L63_REG_OUT1L_INPUT1);
253+
LOG_REG(spi, CS47L63_REG_OUT1L_INPUT2);
254+
LOG_REG(spi, CS47L63_REG_OUT1L_INPUT3);
255+
LOG_REG(spi, CS47L63_REG_OUT1L_INPUT4);
256+
LOG_INF("======================================");
257+
}
258+
259+
static int cs47l63_init(const struct device *dev)
260+
{
261+
const struct cs47l63_config *cfg = dev->config;
262+
uint32_t devid[2];
263+
int ret;
264+
265+
if (!spi_is_ready_dt(&cfg->spi)) {
266+
LOG_ERR("SPI bus is not ready");
267+
return -ENODEV;
268+
}
269+
270+
ret = gpio_pin_configure_dt(&cfg->irq_gpio, GPIO_INPUT);
271+
if (ret != 0) {
272+
LOG_ERR("Unable to setup IRQ pin");
273+
return -EIO;
274+
}
275+
276+
/* Put device in hardware RESET */
277+
ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE);
278+
if (ret != 0) {
279+
LOG_ERR("Unable to setup nreset GPIO");
280+
return -EIO;
281+
}
282+
283+
/* Wait for device to be in reset state */
284+
while (gpio_pin_get_dt(&cfg->irq_gpio));
285+
286+
/* Release RESET state */
287+
ret = gpio_pin_set_dt(&cfg->reset_gpio, 0);
288+
if (ret != 0) {
289+
LOG_ERR("Unable to complete hw reset");
290+
return -EIO;
291+
}
292+
293+
/* Wait for device to be ready */
294+
while (!gpio_pin_get_dt(&cfg->irq_gpio));
295+
296+
ret = cs47l63_read_spi_regs(&cfg->spi, CS47L63_REG_DEVID, devid, ARRAY_SIZE(devid));
297+
if (ret != 0) {
298+
LOG_ERR("Error reading DEVID: %d", ret);
299+
return -EIO;
300+
}
301+
302+
if (devid[0] != 0x47A63) {
303+
LOG_WRN("Invalid device ID");
304+
return -ENODEV;
305+
}
306+
307+
LOG_DBG("Found CS47L63 rev %d.%d", (devid[1] >> 4) & 0xF, devid[1] & 0xF);
308+
309+
/* ================================ */
310+
print_all_regs(&cfg->spi);
311+
ret = apply_titou_config(&cfg->spi);
312+
if (ret != 0) {
313+
return -EIO;
314+
}
315+
print_all_regs(&cfg->spi);
316+
/* ================================ */
317+
318+
return 0;
319+
}
320+
321+
#define CS47L63_SPI_OPMODE (SPI_OP_MODE_MASTER | SPI_WORD_SET(8))
322+
323+
#define CS47L63_INIT(inst) \
324+
static const struct cs47l63_config cs47l63_config_##inst = { \
325+
.spi = SPI_DT_SPEC_INST_GET(inst, CS47L63_SPI_OPMODE, 0), \
326+
.reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \
327+
.irq_gpio = GPIO_DT_SPEC_INST_GET(inst, irq_gpios), \
328+
}; \
329+
DEVICE_DT_INST_DEFINE(inst, cs47l63_init, NULL, NULL, \
330+
&cs47l63_config_##inst, POST_KERNEL, \
331+
CONFIG_AUDIO_CODEC_INIT_PRIORITY, \
332+
&cs47l63_driver_api);
333+
334+
DT_INST_FOREACH_STATUS_OKAY(CS47L63_INIT)

0 commit comments

Comments
 (0)