Skip to content

Commit 5f4e14b

Browse files
committed
drivers: audio: add new driver for Cirrus CS47L63
Add a new driver for the CS47L63 audio codec and DSP. This initial implementation only supports audio output on the unique mono headphones out present on this chip. This driver currently assumes that digital audio is delivered to the CS47L63 through its 1st audio serial port (ASP1). Also, move its dts binding out of sound/ (where it is the only entry), so that it sits together with other audio codec drivers in audio/ Signed-off-by: Titouan Christophe <[email protected]>
1 parent 4fe2c5b commit 5f4e14b

File tree

5 files changed

+530
-0
lines changed

5 files changed

+530
-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: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
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(cs47l63, CONFIG_AUDIO_CODEC_LOG_LEVEL);
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+
static int cs47l63_read_spi_regs(const struct spi_dt_spec *spi,
25+
uint32_t start_reg, uint32_t *values, size_t count)
26+
{
27+
int ret;
28+
/* Register address + READ bit set */
29+
uint32_t addr = sys_cpu_to_be32(BIT(31) | start_reg);
30+
struct spi_buf tx_buf = {.buf = (void *) &addr, .len = sizeof(addr)};
31+
struct spi_buf_set tx_bufset = {.buffers = &tx_buf, .count = 1};
32+
uint32_t rx_padbuf[2];
33+
struct spi_buf rx_bufs[2] = {
34+
/* 1 empty rx word while we write out the address
35+
* + 1 padding word (see datasheet 4.13.1)
36+
*/
37+
{.buf = rx_padbuf, .len = sizeof(rx_padbuf)},
38+
/* The actual reading result */
39+
{.buf = values, .len = count * sizeof(uint32_t)},
40+
};
41+
42+
struct spi_buf_set rx_bufset = {.buffers = rx_bufs, .count = 2};
43+
44+
ret = spi_transceive_dt(spi, &tx_bufset, &rx_bufset);
45+
if (ret != 0) {
46+
LOG_ERR("Unable to read register: %d", ret);
47+
return ret;
48+
}
49+
50+
for (size_t i = 0; i < count; i++) {
51+
values[i] = sys_be32_to_cpu(values[i]);
52+
}
53+
54+
return 0;
55+
}
56+
57+
static int cs47l63_write_spi_blob(const struct spi_dt_spec *spi,
58+
uint32_t addr, const uint8_t *data, size_t len)
59+
{
60+
int ret;
61+
uint32_t setup_buf[2] = {
62+
sys_cpu_to_be32(addr & 0x7FFFFFFF),
63+
0, /* Padding (see datasheet 4.13.1) */
64+
};
65+
struct spi_buf tx_bufs[2] = {
66+
{.buf = setup_buf, .len = sizeof(setup_buf)},
67+
{.buf = (void *) data, .len = len},
68+
};
69+
struct spi_buf_set bufset = {.buffers = tx_bufs, .count = 2};
70+
71+
ret = spi_write_dt(spi, &bufset);
72+
if (ret != 0) {
73+
LOG_ERR("Unable to write blob[%d]: %d", len, ret);
74+
}
75+
76+
return ret;
77+
}
78+
79+
static inline int cs47l63_write_spi_reg(const struct spi_dt_spec *spi,
80+
uint32_t reg, uint32_t value)
81+
{
82+
uint32_t encoded_value = sys_cpu_to_be32(value);
83+
84+
return cs47l63_write_spi_blob(spi, reg, (const uint8_t *) &encoded_value, 4);
85+
}
86+
87+
static inline int cs47l63_update_spi_reg(const struct spi_dt_spec *spi,
88+
uint32_t reg, uint32_t value, uint32_t mask)
89+
{
90+
int ret;
91+
uint32_t prev_value;
92+
93+
ret = cs47l63_read_spi_regs(spi, reg, &prev_value, 1);
94+
if (ret != 0) {
95+
return ret;
96+
}
97+
98+
return cs47l63_write_spi_reg(spi, reg, (prev_value & ~mask) | (value & mask));
99+
}
100+
101+
static inline int cs47l63_sample_rate(uint32_t frame_clk_freq)
102+
{
103+
switch (frame_clk_freq) {
104+
case 8000:
105+
return SAMPLE_RATE_8_kHz;
106+
case 11025:
107+
return SAMPLE_RATE_11_025_kHz;
108+
case 12000:
109+
return SAMPLE_RATE_12_kHz;
110+
case 16000:
111+
return SAMPLE_RATE_16_kHz;
112+
case 22050:
113+
return SAMPLE_RATE_22_05_kHz;
114+
case 24000:
115+
return SAMPLE_RATE_24_kHz;
116+
case 32000:
117+
return SAMPLE_RATE_32_kHz;
118+
case 44100:
119+
return SAMPLE_RATE_44_1_kHz;
120+
case 48000:
121+
return SAMPLE_RATE_48_kHz;
122+
case 88200:
123+
return SAMPLE_RATE_88_2_kHz;
124+
case 96000:
125+
return SAMPLE_RATE_96_kHz;
126+
case 176400:
127+
return SAMPLE_RATE_176_4_kHz;
128+
case 192000:
129+
return SAMPLE_RATE_192_kHz;
130+
default:
131+
LOG_WRN("Unsupported sample rate: %dHz", frame_clk_freq);
132+
return -ENOTSUP;
133+
}
134+
}
135+
136+
static int cs47l63_configure(const struct device *dev, struct audio_codec_cfg *audiocfg)
137+
{
138+
int ret;
139+
uint8_t format, sample_rate;
140+
uint8_t wordsize = audiocfg->dai_cfg.i2s.word_size;
141+
const struct cs47l63_config *cfg = dev->config;
142+
143+
if (audiocfg->dai_route != AUDIO_ROUTE_PLAYBACK) {
144+
return -ENOTSUP;
145+
}
146+
147+
if (wordsize < 16 || wordsize > 32) {
148+
LOG_WRN("Unsupported word size");
149+
return -ENOTSUP;
150+
}
151+
152+
switch (audiocfg->dai_type) {
153+
case AUDIO_DAI_TYPE_LEFT_JUSTIFIED:
154+
format = ASP_FMT_LEFTJUST;
155+
break;
156+
case AUDIO_DAI_TYPE_I2S:
157+
format = ASP_FMT_I2S;
158+
break;
159+
default:
160+
LOG_WRN("Unsupported interface type");
161+
return -ENOTSUP;
162+
}
163+
164+
sample_rate = cs47l63_sample_rate(audiocfg->dai_cfg.i2s.frame_clk_freq);
165+
if (sample_rate < 0) {
166+
return -ENOTSUP;
167+
}
168+
169+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_ASP1_CONTROL2,
170+
(wordsize << 24) | (format << 8));
171+
if (ret != 0) {
172+
LOG_ERR("Unable to set DAI format on ASP1");
173+
return -EIO;
174+
}
175+
176+
/* Set the number of valid data bits per slot */
177+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_ASP1_DATA_CONTROL5, wordsize);
178+
if (ret != 0) {
179+
return -EIO;
180+
}
181+
182+
/* Enable reception of channel 1 on ASP1 */
183+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_ASP1_ENABLES1, BIT(16));
184+
if (ret != 0) {
185+
return -EIO;
186+
}
187+
188+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_SAMPLE_RATE1, sample_rate);
189+
if (ret != 0) {
190+
return -EIO;
191+
}
192+
193+
/* Route ASP1 Rx channel 1 to the output */
194+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_OUT1L_INPUT1, MIXER_SRC_ASP1_RX1);
195+
if (ret != 0) {
196+
return -EIO;
197+
}
198+
199+
/* Enable output data path */
200+
ret = cs47l63_write_spi_reg(&cfg->spi, CS47L63_REG_OUTPUT_ENABLE_1, BIT(1));
201+
if (ret != 0) {
202+
return -EIO;
203+
}
204+
205+
/* Unmute output */
206+
ret = cs47l63_update_spi_reg(&cfg->spi, CS47L63_REG_OUT1L_VOLUME_1,
207+
BIT(9), BIT(9)|BIT(8));
208+
if (ret != 0) {
209+
return -EIO;
210+
}
211+
212+
return 0;
213+
}
214+
215+
static void cs47l63_start_output(const struct device *dev)
216+
{
217+
}
218+
219+
static void cs47l63_stop_output(const struct device *dev)
220+
{
221+
}
222+
223+
static int cs47l63_set_property(const struct device *dev, audio_property_t property,
224+
audio_channel_t channel, audio_property_value_t val)
225+
{
226+
const struct cs47l63_config *cfg = dev->config;
227+
int ret;
228+
uint32_t value, mask;
229+
230+
if (channel != AUDIO_CHANNEL_HEADPHONE_LEFT) {
231+
return -ENOTSUP;
232+
}
233+
234+
switch (property) {
235+
case AUDIO_PROPERTY_OUTPUT_MUTE:
236+
mask = BIT(8);
237+
value = val.mute ? BIT(8) : 0;
238+
break;
239+
case AUDIO_PROPERTY_OUTPUT_VOLUME:
240+
mask = 0xFF;
241+
value = 0xBF * val.vol / 100;
242+
break;
243+
default:
244+
return -ENOTSUP;
245+
}
246+
247+
ret = cs47l63_update_spi_reg(&cfg->spi, CS47L63_REG_OUT1L_VOLUME_1,
248+
BIT(9)|value, BIT(9)|mask);
249+
return (ret == 0) ? 0 : -EIO;
250+
}
251+
252+
static int cs47l63_apply_properties(const struct device *dev)
253+
{
254+
return 0;
255+
}
256+
257+
static const struct audio_codec_api cs47l63_driver_api = {
258+
.configure = cs47l63_configure,
259+
.start_output = cs47l63_start_output,
260+
.stop_output = cs47l63_stop_output,
261+
.set_property = cs47l63_set_property,
262+
.apply_properties = cs47l63_apply_properties,
263+
};
264+
265+
static inline bool wait_gpio(const struct gpio_dt_spec *gpio, int expected_state,
266+
int timeout_ms)
267+
{
268+
while (timeout_ms-- > 0) {
269+
if (gpio_pin_get_dt(gpio) == expected_state) {
270+
return true;
271+
}
272+
273+
k_msleep(1);
274+
}
275+
276+
return false;
277+
}
278+
279+
static int cs47l63_init(const struct device *dev)
280+
{
281+
const struct cs47l63_config *cfg = dev->config;
282+
uint32_t devid[2];
283+
uint32_t zeros[4];
284+
int ret;
285+
286+
if (!spi_is_ready_dt(&cfg->spi)) {
287+
LOG_ERR("SPI bus is not ready");
288+
return -ENODEV;
289+
}
290+
291+
ret = gpio_pin_configure_dt(&cfg->irq_gpio, GPIO_INPUT);
292+
if (ret != 0) {
293+
LOG_ERR("Unable to setup IRQ pin");
294+
return -EIO;
295+
}
296+
297+
/* Put device in hardware RESET */
298+
ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE);
299+
if (ret != 0) {
300+
LOG_ERR("Unable to setup nreset GPIO");
301+
return -EIO;
302+
}
303+
304+
/* Wait for device to be in reset state */
305+
if (!wait_gpio(&cfg->irq_gpio, 0, 10)) {
306+
LOG_ERR("Timed out while waiting for device reset");
307+
return -ETIMEDOUT;
308+
}
309+
310+
/* Release RESET state */
311+
ret = gpio_pin_set_dt(&cfg->reset_gpio, 0);
312+
if (ret != 0) {
313+
LOG_ERR("Unable to complete hw reset");
314+
return -EIO;
315+
}
316+
317+
/* Wait for device to be ready */
318+
if (!wait_gpio(&cfg->irq_gpio, 1, 10)) {
319+
LOG_ERR("Timed out while waiting for device start");
320+
return -ETIMEDOUT;
321+
}
322+
323+
ret = cs47l63_read_spi_regs(&cfg->spi, CS47L63_REG_DEVID, devid, ARRAY_SIZE(devid));
324+
if (ret != 0) {
325+
LOG_ERR("Error reading DEVID: %d", ret);
326+
return -EIO;
327+
}
328+
329+
if (devid[0] != 0x47A63) {
330+
LOG_WRN("Invalid device ID");
331+
return -ENODEV;
332+
}
333+
334+
LOG_DBG("Found CS47L63 rev %d.%d", (devid[1] >> 4) & 0xF, devid[1] & 0xF);
335+
336+
/* Set GPIO1-4 to pin-specific alternate function (ASP1) */
337+
ret = cs47l63_write_spi_blob(&cfg->spi, CS47L63_REG_GPIO1_CTRL1,
338+
(const uint8_t *) zeros, 4);
339+
if (ret != 0) {
340+
LOG_ERR("Unable to setup GPIO1-4 to ASP1");
341+
return -EIO;
342+
}
343+
344+
/* Enable SYSCLK */
345+
ret = cs47l63_update_spi_reg(&cfg->spi, CS47L63_REG_SYSTEM_CLOCK1, BIT(6), BIT(6));
346+
if (ret != 0) {
347+
LOG_ERR("Unable to enable SYSCLK");
348+
return -EIO;
349+
}
350+
351+
return 0;
352+
}
353+
354+
#define CS47L63_SPI_OPMODE (SPI_OP_MODE_MASTER | SPI_WORD_SET(8))
355+
356+
#define CS47L63_INIT(inst) \
357+
static const struct cs47l63_config cs47l63_config_##inst = { \
358+
.spi = SPI_DT_SPEC_INST_GET(inst, CS47L63_SPI_OPMODE, 0), \
359+
.reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \
360+
.irq_gpio = GPIO_DT_SPEC_INST_GET(inst, irq_gpios), \
361+
}; \
362+
DEVICE_DT_INST_DEFINE(inst, cs47l63_init, NULL, NULL, \
363+
&cs47l63_config_##inst, POST_KERNEL, \
364+
CONFIG_AUDIO_CODEC_INIT_PRIORITY, \
365+
&cs47l63_driver_api);
366+
367+
DT_INST_FOREACH_STATUS_OKAY(CS47L63_INIT)

0 commit comments

Comments
 (0)