mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-15 00:25:22 +00:00
Add new driver for the Cirrus CS43L22 audio DAC. Signed-off-by: Titouan Christophe <moiandme@gmail.com>
312 lines
7.9 KiB
C
312 lines
7.9 KiB
C
/*
|
|
* Copyright (c) 2025 Titouan Christophe
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/drivers/gpio.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/audio/codec.h>
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(cirrus_cs43l22);
|
|
|
|
#define DT_DRV_COMPAT cirrus_cs43l22
|
|
|
|
/*
|
|
* See datasheet: https://statics.cirrus.com/pubs/proDatasheet/CS43L22_F2.pdf
|
|
*/
|
|
|
|
/* (datasheet) 6. REGISTER QUICK REFERENCE */
|
|
#define REG_ID 0x01
|
|
#define REG_POWER_CTL_1 0x02
|
|
#define REG_POWER_CTL_2 0x04
|
|
#define REG_CLOCKING_CTL 0x05
|
|
#define REG_INTERFACE_CTL_1 0x06
|
|
#define REG_INTERFACE_CTL_2 0x07
|
|
#define REG_PASSTHROUGH_A 0x08
|
|
#define REG_PASSTHROUGH_B 0x09
|
|
#define REG_ANALOG_ZC_AND_SR 0x0a
|
|
#define REG_PASSTHROUGH_GANG_CONTROL 0x0c
|
|
#define REG_PLAYBACK_CTL_1 0x0d
|
|
#define REG_MISC_CTL 0x0e
|
|
#define REG_PLAYBACK_CTL_2 0x0f
|
|
#define REG_PASSTHROUGH_A_VOL 0x14
|
|
#define REG_PASSTHROUGH_B_VOL 0x15
|
|
#define REG_PCMA_VOL 0x1a
|
|
#define REG_PCMB_VOL 0x1b
|
|
#define REG_BEEP_FREQ 0x1c
|
|
#define REG_BEEP_VOL 0x1d
|
|
#define REG_BEEP_TONE 0x1e
|
|
#define REG_TONE_CTL 0x1f
|
|
#define REG_MASTER_A_VOL 0x20
|
|
#define REG_MASTER_B_VOL 0x21
|
|
#define REG_HEADPHONES_A_VOL 0x22
|
|
#define REG_HEADPHONES_B_VOL 0x23
|
|
#define REG_SPEAKER_A_VOL 0x22
|
|
#define REG_SPEAKER_B_VOL 0x23
|
|
#define REG_LIMITER_CTL_1 0x27
|
|
#define REG_LIMITER_CTL_2 0x28
|
|
#define REG_STATUS 0x2e
|
|
#define REG_SPEAKER_STATUS 0x31
|
|
|
|
/* (datasheet) 7.5.4 DAC Interface Format */
|
|
#define DAC_IF_FORMAT_LEFT_JUSTIFIED 0
|
|
#define DAC_IF_FORMAT_I2S 1
|
|
#define DAC_IF_FORMAT_RIGHT_JUSTIFIED 2
|
|
|
|
/* (datasheet) 7.5.5 Audio Word Length */
|
|
#define WORDLEN_32 0
|
|
#define WORDLEN_24 1
|
|
#define WORDLEN_20 2
|
|
#define WORDLEN_16 3
|
|
#define WORDLEN_RIGHT_24 0
|
|
#define WORDLEN_RIGHT_20 1
|
|
#define WORDLEN_RIGHT_18 2
|
|
#define WORDLEN_RIGHT_16 3
|
|
|
|
/* (datasheet) 7.12 Playback Control 2 */
|
|
#define HEADPHONES_B_MUTE (1 << 7)
|
|
#define HEADPHONES_A_MUTE (1 << 6)
|
|
#define SPEAKER_B_MUTE (1 << 5)
|
|
#define SPEAKER_A_MUTE (1 << 4)
|
|
|
|
#define cs43l22_write(_i2c, _reg, _value) \
|
|
cs43l22_write_masked(_i2c, _reg, _value, 0xff)
|
|
|
|
static inline int cs43l22_write_masked(const struct i2c_dt_spec *i2c, uint8_t reg,
|
|
uint8_t value, uint8_t mask)
|
|
{
|
|
int ret;
|
|
uint8_t actual_value = 0;
|
|
|
|
if (mask != 0xff) {
|
|
ret = i2c_burst_read_dt(i2c, reg, &actual_value, 1);
|
|
if (ret) {
|
|
LOG_ERR("Unable to get actual register value [%02X]", reg);
|
|
return ret;
|
|
}
|
|
}
|
|
actual_value = (actual_value & ~mask) | (value & mask);
|
|
|
|
return i2c_burst_write_dt(i2c, reg, &actual_value, 1);
|
|
}
|
|
|
|
#define cs43l22_soft_power_down(_i2c) cs43l22_write(_i2c, REG_POWER_CTL_1, 0x01)
|
|
#define cs43l22_soft_power_up(_i2c) cs43l22_write(_i2c, REG_POWER_CTL_1, 0x9e)
|
|
|
|
struct cs43l22_config {
|
|
struct i2c_dt_spec i2c;
|
|
struct gpio_dt_spec reset_gpio;
|
|
};
|
|
|
|
static int cs43l22_configure(const struct device *dev, struct audio_codec_cfg *audiocfg)
|
|
{
|
|
uint8_t format, wordlen;
|
|
const struct cs43l22_config *cfg = dev->config;
|
|
|
|
if (audiocfg->dai_route != AUDIO_ROUTE_PLAYBACK) {
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
switch (audiocfg->dai_type) {
|
|
case AUDIO_DAI_TYPE_LEFT_JUSTIFIED:
|
|
format = DAC_IF_FORMAT_LEFT_JUSTIFIED;
|
|
break;
|
|
case AUDIO_DAI_TYPE_I2S:
|
|
format = DAC_IF_FORMAT_I2S;
|
|
break;
|
|
case AUDIO_DAI_TYPE_RIGHT_JUSTIFIED:
|
|
format = DAC_IF_FORMAT_RIGHT_JUSTIFIED;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
if (format == DAC_IF_FORMAT_RIGHT_JUSTIFIED) {
|
|
switch (audiocfg->dai_cfg.i2s.word_size) {
|
|
case 16:
|
|
wordlen = WORDLEN_RIGHT_16;
|
|
break;
|
|
case 18:
|
|
wordlen = WORDLEN_RIGHT_18;
|
|
break;
|
|
case 20:
|
|
wordlen = WORDLEN_RIGHT_20;
|
|
break;
|
|
case 24:
|
|
wordlen = WORDLEN_RIGHT_24;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
} else {
|
|
switch (audiocfg->dai_cfg.i2s.word_size) {
|
|
case 16:
|
|
wordlen = WORDLEN_16;
|
|
break;
|
|
case 20:
|
|
wordlen = WORDLEN_20;
|
|
break;
|
|
case 24:
|
|
wordlen = WORDLEN_24;
|
|
break;
|
|
case 32:
|
|
wordlen = WORDLEN_32;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
}
|
|
|
|
if (cs43l22_soft_power_down(&cfg->i2c)) {
|
|
return -EIO;
|
|
}
|
|
/* Set automatic clock detection */
|
|
if (cs43l22_write(&cfg->i2c, REG_CLOCKING_CTL, (1 << 7))) {
|
|
return -EIO;
|
|
}
|
|
/* Set input audio format */
|
|
if (cs43l22_write_masked(&cfg->i2c, REG_INTERFACE_CTL_1, (format << 2) | wordlen, 0xdf)) {
|
|
return -EIO;
|
|
}
|
|
if (cs43l22_soft_power_up(&cfg->i2c)) {
|
|
return -EIO;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void cs43l22_start_output(const struct device *dev)
|
|
{
|
|
}
|
|
|
|
static void cs43l22_stop_output(const struct device *dev)
|
|
{
|
|
}
|
|
|
|
static int cs43l22_apply_properties(const struct device *dev)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline int cs43l22_set_mute(const struct i2c_dt_spec *i2c,
|
|
audio_channel_t channel, bool mute)
|
|
{
|
|
uint8_t chan_mute_mask = 0;
|
|
|
|
switch (channel) {
|
|
case AUDIO_CHANNEL_ALL:
|
|
chan_mute_mask = HEADPHONES_A_MUTE
|
|
| HEADPHONES_B_MUTE
|
|
| SPEAKER_A_MUTE
|
|
| SPEAKER_B_MUTE;
|
|
break;
|
|
case AUDIO_CHANNEL_HEADPHONE_LEFT:
|
|
chan_mute_mask = HEADPHONES_A_MUTE;
|
|
break;
|
|
case AUDIO_CHANNEL_HEADPHONE_RIGHT:
|
|
chan_mute_mask = HEADPHONES_B_MUTE;
|
|
break;
|
|
case AUDIO_CHANNEL_FRONT_LEFT:
|
|
chan_mute_mask = SPEAKER_A_MUTE;
|
|
break;
|
|
case AUDIO_CHANNEL_FRONT_RIGHT:
|
|
chan_mute_mask = SPEAKER_B_MUTE;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return cs43l22_write_masked(i2c, REG_PLAYBACK_CTL_2,
|
|
mute * chan_mute_mask, chan_mute_mask);
|
|
}
|
|
|
|
static inline int cs43l22_set_volume(const struct i2c_dt_spec *i2c,
|
|
audio_channel_t channel, int vol)
|
|
{
|
|
uint8_t reg;
|
|
uint8_t volume_scaled = 65 + (191 * vol / 100);
|
|
|
|
switch (channel) {
|
|
case AUDIO_CHANNEL_HEADPHONE_LEFT:
|
|
reg = REG_HEADPHONES_A_VOL;
|
|
break;
|
|
case AUDIO_CHANNEL_HEADPHONE_RIGHT:
|
|
reg = REG_HEADPHONES_B_VOL;
|
|
break;
|
|
case AUDIO_CHANNEL_FRONT_LEFT:
|
|
reg = REG_SPEAKER_A_VOL;
|
|
break;
|
|
case AUDIO_CHANNEL_FRONT_RIGHT:
|
|
reg = REG_SPEAKER_B_VOL;
|
|
break;
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
return cs43l22_write(i2c, reg, volume_scaled);
|
|
}
|
|
|
|
static int cs43l22_set_property(const struct device *dev, audio_property_t property,
|
|
audio_channel_t channel, audio_property_value_t val)
|
|
{
|
|
const struct cs43l22_config *cfg = dev->config;
|
|
|
|
switch (property) {
|
|
case AUDIO_PROPERTY_OUTPUT_MUTE:
|
|
return cs43l22_set_mute(&cfg->i2c, channel, val.mute);
|
|
case AUDIO_PROPERTY_OUTPUT_VOLUME:
|
|
return cs43l22_set_volume(&cfg->i2c, channel, val.vol);
|
|
default:
|
|
return -ENOTSUP;
|
|
}
|
|
|
|
}
|
|
|
|
static const struct audio_codec_api cs43l22_api = {
|
|
.configure = cs43l22_configure,
|
|
.start_output = cs43l22_start_output,
|
|
.stop_output = cs43l22_stop_output,
|
|
.set_property = cs43l22_set_property,
|
|
.apply_properties = cs43l22_apply_properties,
|
|
};
|
|
|
|
static int cs43l22_init(const struct device *dev)
|
|
{
|
|
uint8_t regval;
|
|
const struct cs43l22_config *cfg = dev->config;
|
|
int ret;
|
|
|
|
ret = gpio_pin_configure_dt(&cfg->reset_gpio, GPIO_OUTPUT_ACTIVE);
|
|
if (ret) {
|
|
return -EIO;
|
|
}
|
|
|
|
ret = i2c_burst_read_dt(&cfg->i2c, REG_ID, ®val, 1);
|
|
if (ret) {
|
|
LOG_ERR("Unable to read device ID");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if ((regval >> 3) != 0x1C) {
|
|
LOG_ERR("Wrong Chip ID");
|
|
return -ENODEV;
|
|
}
|
|
|
|
LOG_DBG("Found CS43L22 (chip=%02X, rev=%c%d)",
|
|
regval >> 3, 'A' + ((regval >> 1) & 3), regval & 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define CS43L22_INIT(inst) \
|
|
static const struct cs43l22_config cs43l22_config_##inst = { \
|
|
.i2c = I2C_DT_SPEC_INST_GET(inst), \
|
|
.reset_gpio = GPIO_DT_SPEC_INST_GET(inst, reset_gpios), \
|
|
}; \
|
|
DEVICE_DT_INST_DEFINE(inst, cs43l22_init, NULL, NULL, \
|
|
&cs43l22_config_##inst, POST_KERNEL, \
|
|
CONFIG_AUDIO_CODEC_INIT_PRIORITY, \
|
|
&cs43l22_api);
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(CS43L22_INIT)
|