/* * Copyright (c) 2025 Titouan Christophe * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include 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)