drivers: audio: wm8962: Add wm8962 codec support
- create driver for codec wm8962 - add Kconfig, Cmakelist references - create dts binding Signed-off-by: Tomas Barak <tomas.barak@nxp.com>
This commit is contained in:
committed by
Benjamin Cabé
parent
4512cbf509
commit
9dd494f966
@@ -11,4 +11,5 @@ zephyr_library_sources_ifdef(CONFIG_AUDIO_TAS6422DAC tas6422dac.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_SHELL codec_shell.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUDIO_DMIC_MCUX dmic_mcux.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_WM8904 wm8904.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_WM8962 wm8962.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_AUDIO_CODEC_CS43L22 cs43l22.c)
|
||||
|
||||
@@ -40,6 +40,7 @@ source "drivers/audio/Kconfig.tas6422dac"
|
||||
source "drivers/audio/Kconfig.tlv320aic3110"
|
||||
source "drivers/audio/Kconfig.tlv320dac"
|
||||
source "drivers/audio/Kconfig.wm8904"
|
||||
source "drivers/audio/Kconfig.wm8962"
|
||||
|
||||
endif # AUDIO_CODEC
|
||||
|
||||
|
||||
10
drivers/audio/Kconfig.wm8962
Normal file
10
drivers/audio/Kconfig.wm8962
Normal file
@@ -0,0 +1,10 @@
|
||||
# Copyright 2025 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config AUDIO_CODEC_WM8962
|
||||
bool "Wolfson WM8962 codec support"
|
||||
default y
|
||||
select I2C
|
||||
depends on DT_HAS_WOLFSON_WM8962_ENABLED
|
||||
help
|
||||
Enable support for the Wolfson WM8962 codec
|
||||
728
drivers/audio/wm8962.c
Normal file
728
drivers/audio/wm8962.c
Normal file
@@ -0,0 +1,728 @@
|
||||
/*
|
||||
* Copyright 2025 NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <errno.h>
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/i2c.h>
|
||||
#include <zephyr/drivers/clock_control.h>
|
||||
#include <zephyr/audio/codec.h>
|
||||
#include <zephyr/devicetree/clocks.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_REGISTER(wolfson_wm8962, CONFIG_AUDIO_CODEC_LOG_LEVEL);
|
||||
|
||||
#include "wm8962.h"
|
||||
|
||||
#define DT_DRV_COMPAT wolfson_wm8962
|
||||
|
||||
struct wm8962_driver_config {
|
||||
struct i2c_dt_spec i2c;
|
||||
int clock_source;
|
||||
const struct device *mclk_dev;
|
||||
clock_control_subsys_t mclk_name;
|
||||
};
|
||||
|
||||
#define DEV_CFG(dev) ((const struct wm8962_driver_config *const)dev->config)
|
||||
|
||||
static void wm8962_write_reg(const struct device *dev, uint16_t reg, uint16_t val);
|
||||
static void wm8962_read_reg(const struct device *dev, uint16_t reg, uint16_t *val);
|
||||
static void wm8962_update_reg(const struct device *dev, uint16_t reg, uint16_t mask, uint16_t val);
|
||||
static void wm8962_soft_reset(const struct device *dev);
|
||||
#if DEBUG_WM8962_REGISTER
|
||||
static void WM8962_read_all_reg(const struct device *dev, uint16_t endAddress);
|
||||
#endif
|
||||
|
||||
static void wm8962_configure_output(const struct device *dev);
|
||||
|
||||
static void wm8962_configure_input(const struct device *dev);
|
||||
|
||||
static int wm8962_apply_properties(const struct device *dev);
|
||||
|
||||
static int wm8962_start_sequence(const struct device *dev, wm8962_sequence_id_t id)
|
||||
{
|
||||
uint32_t delayUs = 93000U;
|
||||
uint16_t sequenceStat = 0U;
|
||||
|
||||
switch (id) {
|
||||
case kWM8962_SequenceDACToHeadphonePowerUp:
|
||||
delayUs = 93000U;
|
||||
break;
|
||||
case kWM8962_SequenceAnalogueInputPowerUp:
|
||||
delayUs = 75000U;
|
||||
break;
|
||||
case kWM8962_SequenceChipPowerDown:
|
||||
delayUs = 32000U;
|
||||
break;
|
||||
case kWM8962_SequenceSpeakerSleep:
|
||||
delayUs = 2000U;
|
||||
break;
|
||||
case kWM8962_SequenceSpeakerWake:
|
||||
delayUs = 2000U;
|
||||
break;
|
||||
default:
|
||||
delayUs = 93000U;
|
||||
break;
|
||||
}
|
||||
|
||||
wm8962_write_reg(dev, WM8962_REG_WRITE_SEQ_CTRL_1, WM8962_WSEQ_ENA);
|
||||
wm8962_write_reg(dev, WM8962_REG_WRITE_SEQ_CTRL_2, (uint16_t)id);
|
||||
while (delayUs != 0U) {
|
||||
wm8962_read_reg(dev, WM8962_REG_WRITE_SEQ_CTRL_3, &sequenceStat);
|
||||
if ((sequenceStat & 1U) == 0U) {
|
||||
break;
|
||||
}
|
||||
k_msleep(1U);
|
||||
delayUs -= 1000U;
|
||||
}
|
||||
|
||||
return (sequenceStat & 1U) == 0U ? 0 : -EBUSY;
|
||||
}
|
||||
|
||||
static int wm8962_get_clock_divider(uint32_t inputClock, uint32_t maxClock, uint16_t *divider)
|
||||
{
|
||||
if ((inputClock >> 2U) > maxClock) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* fll reference clock divider */
|
||||
if (inputClock > maxClock) {
|
||||
if ((inputClock >> 1U) > maxClock) {
|
||||
*divider = 2U;
|
||||
} else {
|
||||
*divider = 1U;
|
||||
}
|
||||
} else {
|
||||
*divider = 0U;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8962_protocol_config(const struct device *dev, audio_dai_type_t dai_type)
|
||||
{
|
||||
wm8962_protocol_t proto;
|
||||
|
||||
switch (dai_type) {
|
||||
case AUDIO_DAI_TYPE_I2S:
|
||||
proto = kWM8962_BusI2S;
|
||||
break;
|
||||
case AUDIO_DAI_TYPE_LEFT_JUSTIFIED:
|
||||
proto = kWM8962_BusLeftJustified;
|
||||
break;
|
||||
case AUDIO_DAI_TYPE_RIGHT_JUSTIFIED:
|
||||
proto = kWM8962_BusRightJustified;
|
||||
break;
|
||||
case AUDIO_DAI_TYPE_PCMA:
|
||||
proto = kWM8962_BusPCMA - 1;
|
||||
break;
|
||||
case AUDIO_DAI_TYPE_PCMB:
|
||||
proto = kWM8962_BusPCMB | 0x10U;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8962_update_reg(dev, WM8962_REG_IFACE0, WM8962_IFACE0_FORMAT_MASK, (uint16_t)proto);
|
||||
|
||||
LOG_DBG("Codec protocol: %#x", proto);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8962_audio_fmt_config(const struct device *dev, audio_dai_cfg_t *cfg, uint32_t mclk)
|
||||
{
|
||||
uint32_t val;
|
||||
uint16_t word_size = cfg->i2s.word_size;
|
||||
uint32_t ratio = mclk / cfg->i2s.frame_clk_freq;
|
||||
|
||||
switch (word_size) {
|
||||
case 16:
|
||||
val = WM8962_IFACE0_WL_16BITS;
|
||||
break;
|
||||
case 20:
|
||||
val = WM8962_IFACE0_WL_20BITS;
|
||||
break;
|
||||
case 24:
|
||||
val = WM8962_IFACE0_WL_24BITS;
|
||||
break;
|
||||
case 32:
|
||||
val = WM8962_IFACE0_WL_32BITS;
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("Invalid codec bit width: %d", cfg->i2s.word_size);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8962_update_reg(dev, WM8962_REG_IFACE0, WM8962_IFACE0_WL_MASK, WM8962_IFACE0_WL(val));
|
||||
|
||||
switch (cfg->i2s.frame_clk_freq) {
|
||||
case kWM8962_AudioSampleRate8kHz:
|
||||
val = 0x15U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate11025Hz:
|
||||
val = 0x04U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate12kHz:
|
||||
val = 0x14U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate16kHz:
|
||||
val = 0x13U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate22050Hz:
|
||||
val = 0x02U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate24kHz:
|
||||
val = 0x12U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate32kHz:
|
||||
val = 0x11U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate44100Hz:
|
||||
val = 0x00U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate48kHz:
|
||||
val = 0x10U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate88200Hz:
|
||||
val = 0x06U;
|
||||
break;
|
||||
case kWM8962_AudioSampleRate96kHz:
|
||||
val = 0x16U;
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("Invalid codec sample rate: %d", cfg->i2s.frame_clk_freq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8962_write_reg(dev, WM8962_REG_ADDCTL3, val);
|
||||
|
||||
switch (ratio) {
|
||||
case 64:
|
||||
val = 0x00U;
|
||||
break;
|
||||
case 128:
|
||||
val = 0x02U;
|
||||
break;
|
||||
case 192:
|
||||
val = 0x04U;
|
||||
break;
|
||||
case 256:
|
||||
val = 0x06U;
|
||||
break;
|
||||
case 384:
|
||||
val = 0x08U;
|
||||
break;
|
||||
case 512:
|
||||
val = 0x0AU;
|
||||
break;
|
||||
case 768:
|
||||
val = 0x0CU;
|
||||
break;
|
||||
case 1024:
|
||||
val = 0x0EU;
|
||||
break;
|
||||
case 1536:
|
||||
val = 0x12U;
|
||||
break;
|
||||
case 3072:
|
||||
val = 0x14U;
|
||||
break;
|
||||
case 6144:
|
||||
val = 0x16U;
|
||||
break;
|
||||
default:
|
||||
LOG_WRN("Invalid codec ratio: %d", ratio);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8962_write_reg(dev, WM8962_REG_CLK4, val);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8962_out_update(const struct device *dev, audio_channel_t channel, uint16_t val,
|
||||
uint16_t mask)
|
||||
{
|
||||
switch (channel) {
|
||||
case AUDIO_CHANNEL_FRONT_LEFT:
|
||||
wm8962_update_reg(dev, WM8962_REG_LOUT2, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_FRONT_RIGHT:
|
||||
wm8962_update_reg(dev, WM8962_REG_ROUT2, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_HEADPHONE_LEFT:
|
||||
wm8962_update_reg(dev, WM8962_REG_LOUT1, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_HEADPHONE_RIGHT:
|
||||
wm8962_update_reg(dev, WM8962_REG_ROUT1, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_ALL:
|
||||
wm8962_update_reg(dev, WM8962_REG_LOUT1, mask, val);
|
||||
wm8962_update_reg(dev, WM8962_REG_ROUT1, mask, val);
|
||||
wm8962_update_reg(dev, WM8962_REG_LOUT2, mask, val);
|
||||
wm8962_update_reg(dev, WM8962_REG_ROUT2, mask, val);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8962_out_volume_config(const struct device *dev, audio_channel_t channel, int volume)
|
||||
{
|
||||
/* Set volume values with VU = 0 */
|
||||
const uint16_t val = WM8962_REGVAL_OUT_VOL(1, 0, volume);
|
||||
const uint16_t mask =
|
||||
WM8962_REGMASK_OUT_VU | WM8962_REGMASK_OUT_ZC | WM8962_REGMASK_OUT_VOL;
|
||||
|
||||
return wm8962_out_update(dev, channel, val, mask);
|
||||
}
|
||||
|
||||
static int wm8962_out_mute_config(const struct device *dev, audio_channel_t channel, bool mute)
|
||||
{
|
||||
uint8_t val = 0U;
|
||||
|
||||
switch (channel) {
|
||||
case AUDIO_CHANNEL_FRONT_LEFT:
|
||||
val = mute ? 2U : 0U;
|
||||
wm8962_update_reg(dev, WM8962_REG_CLASSD1, WM8962_L_CH_MUTE_MASK, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_FRONT_RIGHT:
|
||||
val = mute ? 1U : 0U;
|
||||
wm8962_update_reg(dev, WM8962_REG_CLASSD1, WM8962_R_CH_MUTE_MASK, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_HEADPHONE_LEFT:
|
||||
val = mute ? 2U : 0U;
|
||||
wm8962_update_reg(dev, WM8962_REG_POWER2, WM8962_L_CH_MUTE_MASK, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_HEADPHONE_RIGHT:
|
||||
val = mute ? 1U : 0U;
|
||||
wm8962_update_reg(dev, WM8962_REG_POWER2, WM8962_R_CH_MUTE_MASK, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_ALL:
|
||||
val = mute ? 3U : 0U;
|
||||
wm8962_update_reg(dev, WM8962_REG_CLASSD1,
|
||||
(WM8962_L_CH_MUTE_MASK | WM8962_R_CH_MUTE_MASK), val);
|
||||
wm8962_update_reg(dev, WM8962_REG_POWER2,
|
||||
(WM8962_L_CH_MUTE_MASK | WM8962_R_CH_MUTE_MASK), val);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8962_in_update(const struct device *dev, audio_channel_t channel, uint16_t mask,
|
||||
uint16_t val)
|
||||
{
|
||||
switch (channel) {
|
||||
case AUDIO_CHANNEL_FRONT_LEFT:
|
||||
wm8962_update_reg(dev, WM8962_REG_LINVOL, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_FRONT_RIGHT:
|
||||
wm8962_update_reg(dev, WM8962_REG_RINVOL, mask, val);
|
||||
return 0;
|
||||
|
||||
case AUDIO_CHANNEL_ALL:
|
||||
wm8962_update_reg(dev, WM8962_REG_LINVOL, mask, val);
|
||||
wm8962_update_reg(dev, WM8962_REG_RINVOL, mask, val);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8962_in_volume_config(const struct device *dev, audio_channel_t channel, int volume)
|
||||
{
|
||||
const uint16_t val = WM8962_REGVAL_IN_VOL(1, 0, 0, volume);
|
||||
const uint16_t mask = WM8962_REGMASK_IN_MUTE;
|
||||
|
||||
return wm8962_in_update(dev, channel, mask, val);
|
||||
}
|
||||
|
||||
static int wm8962_in_mute_config(const struct device *dev, audio_channel_t channel, bool mute)
|
||||
{
|
||||
const uint16_t val = WM8962_REGVAL_IN_VOL(1, mute, 0, 0);
|
||||
const uint16_t mask = WM8962_REGMASK_IN_MUTE;
|
||||
|
||||
return wm8962_in_update(dev, channel, mask, val);
|
||||
}
|
||||
|
||||
static int wm8962_route_input(const struct device *dev, audio_channel_t channel, uint32_t input)
|
||||
{
|
||||
uint8_t reg;
|
||||
|
||||
switch (channel) {
|
||||
case AUDIO_CHANNEL_FRONT_LEFT:
|
||||
reg = WM8962_REG_LEFT_INPUT_PGA;
|
||||
break;
|
||||
|
||||
case AUDIO_CHANNEL_FRONT_RIGHT:
|
||||
reg = WM8962_REG_RIGHT_INPUT_PGA;
|
||||
break;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Input PGA source */
|
||||
wm8962_write_reg(dev, reg, input);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8962_route_output(const struct device *dev, audio_channel_t channel, uint32_t output)
|
||||
{
|
||||
/* Output MIXER */
|
||||
switch (channel) {
|
||||
case AUDIO_CHANNEL_HEADPHONE_LEFT:
|
||||
wm8962_write_reg(dev, WM8962_REG_LEFT_HEADPHONE_MIXER, output);
|
||||
break;
|
||||
case AUDIO_CHANNEL_HEADPHONE_RIGHT:
|
||||
wm8962_write_reg(dev, WM8962_REG_RIGHT_HEADPHONE_MIXER, output);
|
||||
break;
|
||||
case AUDIO_CHANNEL_FRONT_LEFT:
|
||||
case AUDIO_CHANNEL_REAR_LEFT:
|
||||
case AUDIO_CHANNEL_SIDE_LEFT:
|
||||
wm8962_write_reg(dev, WM8962_REG_LEFT_SPEAKER_MIXER, output);
|
||||
break;
|
||||
case AUDIO_CHANNEL_FRONT_RIGHT:
|
||||
case AUDIO_CHANNEL_REAR_RIGHT:
|
||||
case AUDIO_CHANNEL_SIDE_RIGHT:
|
||||
wm8962_write_reg(dev, WM8962_REG_RIGHT_SPEAKER_MIXER, output);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8962_set_master_clock(const struct device *dev, audio_dai_cfg_t *cfg, uint32_t sysclk)
|
||||
{
|
||||
uint32_t sampleRate = cfg->i2s.frame_clk_freq;
|
||||
uint32_t bitWidth = cfg->i2s.word_size;
|
||||
uint32_t bclkDiv = 0U;
|
||||
uint16_t regClkDiv = 0U, sysClkDiv = 0U;
|
||||
int ret = 0;
|
||||
|
||||
wm8962_get_clock_divider(sysclk, WM8962_MAX_DSP_CLOCK, &sysClkDiv);
|
||||
sysclk /= 1 << sysClkDiv;
|
||||
|
||||
bclkDiv = sysclk / (sampleRate * bitWidth * 2U);
|
||||
|
||||
switch (bclkDiv) {
|
||||
case 1:
|
||||
regClkDiv = 0U;
|
||||
break;
|
||||
case 2:
|
||||
regClkDiv = 2U;
|
||||
break;
|
||||
case 3:
|
||||
regClkDiv = 3U;
|
||||
break;
|
||||
case 4:
|
||||
regClkDiv = 4U;
|
||||
break;
|
||||
case 6:
|
||||
regClkDiv = 6U;
|
||||
break;
|
||||
case 8:
|
||||
regClkDiv = 7U;
|
||||
break;
|
||||
case 12:
|
||||
regClkDiv = 9U;
|
||||
break;
|
||||
case 16:
|
||||
regClkDiv = 10U;
|
||||
break;
|
||||
case 24:
|
||||
regClkDiv = 11U;
|
||||
break;
|
||||
case 32:
|
||||
regClkDiv = 13U;
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
if (ret == 0) {
|
||||
wm8962_update_reg(dev, WM8962_REG_CLOCK2, WM8962_CLOCK2_BCLK_DIV_MASK,
|
||||
(uint16_t)regClkDiv);
|
||||
wm8962_write_reg(dev, WM8962_REG_IFACE2, (uint16_t)(bitWidth * 2U));
|
||||
} else {
|
||||
LOG_ERR("Unsupported divider.");
|
||||
}
|
||||
}
|
||||
|
||||
static int wm8962_configure(const struct device *dev, struct audio_codec_cfg *cfg)
|
||||
{
|
||||
uint32_t sysClk = 0;
|
||||
uint16_t clockDiv = 0U;
|
||||
|
||||
const struct wm8962_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||
|
||||
if (cfg->dai_type >= AUDIO_DAI_TYPE_INVALID) {
|
||||
LOG_ERR("dai_type not supported");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (dev_cfg->clock_source == 0) {
|
||||
int err = clock_control_on(dev_cfg->mclk_dev, dev_cfg->mclk_name);
|
||||
|
||||
if (err < 0) {
|
||||
LOG_ERR("MCLK clock source enable fail: %d", err);
|
||||
}
|
||||
|
||||
err = clock_control_get_rate(dev_cfg->mclk_dev, dev_cfg->mclk_name,
|
||||
&cfg->mclk_freq);
|
||||
if (err < 0) {
|
||||
LOG_ERR("MCLK clock source freq acquire fail: %d", err);
|
||||
}
|
||||
}
|
||||
|
||||
wm8962_soft_reset(dev);
|
||||
if (cfg->dai_route == AUDIO_ROUTE_BYPASS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* disable internal osc/FLL2/FLL3/FLL*/
|
||||
wm8962_write_reg(dev, WM8962_REG_PLL2, 0);
|
||||
wm8962_update_reg(dev, WM8962_REG_FLL_CTRL_1, 1U, 0U);
|
||||
wm8962_write_reg(dev, WM8962_REG_CLOCK2, 0x9E4);
|
||||
wm8962_write_reg(dev, WM8962_REG_POWER1, 0x1FE);
|
||||
wm8962_write_reg(dev, WM8962_REG_POWER2, 0x1E0);
|
||||
|
||||
if ((cfg->dai_cfg.i2s.options & I2S_OPT_FRAME_CLK_SLAVE) == I2S_OPT_FRAME_CLK_SLAVE) {
|
||||
wm8962_set_master_clock(dev, &cfg->dai_cfg, cfg->mclk_freq);
|
||||
wm8962_update_reg(dev, WM8962_REG_IFACE0, 1U << 6U, 1U << 6U);
|
||||
}
|
||||
|
||||
wm8962_start_sequence(dev, kWM8962_SequenceDACToHeadphonePowerUp);
|
||||
wm8962_start_sequence(dev, kWM8962_SequenceAnalogueInputPowerUp);
|
||||
wm8962_start_sequence(dev, kWM8962_SequenceSpeakerWake);
|
||||
|
||||
/* enable system clock */
|
||||
wm8962_update_reg(dev, WM8962_REG_CLOCK2, 0x20U, 0x20U);
|
||||
|
||||
/* sysclk clock divider, maximum 12.288MHZ */
|
||||
wm8962_read_reg(dev, WM8962_REG_CLOCK1, &clockDiv);
|
||||
sysClk = cfg->mclk_freq / (1UL << (clockDiv & 3U));
|
||||
|
||||
/* set data protocol */
|
||||
wm8962_protocol_config(dev, cfg->dai_type);
|
||||
/*
|
||||
* ADC volume, 0dB
|
||||
*/
|
||||
wm8962_write_reg(dev, WM8962_REG_LADC, WM8962_ADC_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_write_reg(dev, WM8962_REG_RADC, WM8962_ADC_DEFAULT_VOLUME_VALUE);
|
||||
/*
|
||||
* Digital DAC volume, -15.5dB
|
||||
*/
|
||||
wm8962_write_reg(dev, WM8962_REG_LDAC, WM8962_DAC_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_write_reg(dev, WM8962_REG_RDAC, WM8962_DAC_DEFAULT_VOLUME_VALUE);
|
||||
/* speaker volume 6dB */
|
||||
wm8962_write_reg(dev, WM8962_REG_LOUT2, WM8962_SPEAKER_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_write_reg(dev, WM8962_REG_ROUT2, WM8962_SPEAKER_DEFAULT_VOLUME_VALUE);
|
||||
/* input PGA volume */
|
||||
wm8962_write_reg(dev, WM8962_REG_LINVOL, WM8962_LINEIN_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_write_reg(dev, WM8962_REG_RINVOL, WM8962_LINEIN_DEFAULT_VOLUME_VALUE);
|
||||
/* Headphone volume */
|
||||
wm8962_write_reg(dev, WM8962_REG_LOUT1, WM8962_HEADPHONE_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_write_reg(dev, WM8962_REG_ROUT1, WM8962_HEADPHONE_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_audio_fmt_config(dev, &cfg->dai_cfg, sysClk);
|
||||
|
||||
switch (cfg->dai_route) {
|
||||
case AUDIO_ROUTE_BYPASS:
|
||||
|
||||
break;
|
||||
case AUDIO_ROUTE_PLAYBACK:
|
||||
wm8962_configure_output(dev);
|
||||
break;
|
||||
|
||||
case AUDIO_ROUTE_CAPTURE:
|
||||
wm8962_configure_input(dev);
|
||||
break;
|
||||
|
||||
case AUDIO_ROUTE_PLAYBACK_CAPTURE:
|
||||
wm8962_configure_output(dev);
|
||||
wm8962_configure_input(dev);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8962_start_output(const struct device *dev)
|
||||
{
|
||||
/* Not supported */
|
||||
}
|
||||
|
||||
static void wm8962_stop_output(const struct device *dev)
|
||||
{
|
||||
/* Not supported */
|
||||
}
|
||||
|
||||
static int wm8962_set_property(const struct device *dev, audio_property_t property,
|
||||
audio_channel_t channel, audio_property_value_t val)
|
||||
{
|
||||
switch (property) {
|
||||
case AUDIO_PROPERTY_OUTPUT_VOLUME:
|
||||
return wm8962_out_volume_config(dev, channel, val.vol);
|
||||
|
||||
case AUDIO_PROPERTY_OUTPUT_MUTE:
|
||||
return wm8962_out_mute_config(dev, channel, val.mute);
|
||||
|
||||
case AUDIO_PROPERTY_INPUT_VOLUME:
|
||||
return wm8962_in_volume_config(dev, channel, val.vol);
|
||||
|
||||
case AUDIO_PROPERTY_INPUT_MUTE:
|
||||
return wm8962_in_mute_config(dev, channel, val.mute);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int wm8962_apply_properties(const struct device *dev)
|
||||
{
|
||||
/**
|
||||
* Set VU = 1 for all input and output channels, VU takes effect for the whole
|
||||
* channel pair.
|
||||
*/
|
||||
wm8962_update_reg(dev, WM8962_REG_LOUT1, WM8962_REGVAL_OUT_VOL(1, 0, 0),
|
||||
WM8962_REGMASK_OUT_VU);
|
||||
wm8962_update_reg(dev, WM8962_REG_LINVOL, WM8962_REGVAL_IN_VOL(1, 0, 0, 0),
|
||||
WM8962_REGMASK_IN_VU);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void wm8962_write_reg(const struct device *dev, uint16_t reg, uint16_t val)
|
||||
{
|
||||
const struct wm8962_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||
uint8_t data[4];
|
||||
int ret;
|
||||
|
||||
/* data is reversed */
|
||||
data[0] = (reg >> 8) & 0xff;
|
||||
data[1] = reg & 0xff;
|
||||
data[2] = (val >> 8) & 0xff;
|
||||
data[3] = val & 0xff;
|
||||
|
||||
ret = i2c_write(dev_cfg->i2c.bus, data, 4, dev_cfg->i2c.addr);
|
||||
|
||||
if (ret != 0) {
|
||||
LOG_ERR("i2c write to codec error %d", ret);
|
||||
}
|
||||
|
||||
LOG_DBG("REG:%#02x VAL:%#02x", reg, val);
|
||||
}
|
||||
|
||||
static void wm8962_read_reg(const struct device *dev, uint16_t reg, uint16_t *val)
|
||||
{
|
||||
const struct wm8962_driver_config *const dev_cfg = DEV_CFG(dev);
|
||||
uint16_t value;
|
||||
int ret;
|
||||
|
||||
reg = WM8962_SWAP_UINT16_BYTE_SEQUENCE(reg);
|
||||
|
||||
ret = i2c_write_read(dev_cfg->i2c.bus, dev_cfg->i2c.addr, ®, sizeof(reg), &value,
|
||||
sizeof(value));
|
||||
if (ret == 0) {
|
||||
*val = (value >> 8) & 0xff;
|
||||
*val += ((value & 0xff) << 8);
|
||||
/* update cache*/
|
||||
LOG_DBG("REG:%#02x VAL:%#02x", WM8962_SWAP_UINT16_BYTE_SEQUENCE(reg), *val);
|
||||
}
|
||||
}
|
||||
|
||||
static void wm8962_update_reg(const struct device *dev, uint16_t reg, uint16_t mask, uint16_t val)
|
||||
{
|
||||
uint16_t reg_val = 0;
|
||||
uint16_t new_value = 0;
|
||||
|
||||
wm8962_read_reg(dev, reg, ®_val);
|
||||
LOG_DBG("read %#x = %x", reg, reg_val);
|
||||
new_value = (reg_val & ~mask) | (val & mask);
|
||||
LOG_DBG("write %#x = %x", reg, new_value);
|
||||
wm8962_write_reg(dev, reg, new_value);
|
||||
}
|
||||
|
||||
static void wm8962_soft_reset(const struct device *dev)
|
||||
{
|
||||
wm8962_write_reg(dev, WM8962_REG_RESET, 0x6243U);
|
||||
}
|
||||
|
||||
static void wm8962_configure_output(const struct device *dev)
|
||||
{
|
||||
wm8962_out_volume_config(dev, AUDIO_CHANNEL_ALL, WM8962_HEADPHONE_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_out_mute_config(dev, AUDIO_CHANNEL_ALL, false);
|
||||
|
||||
wm8962_apply_properties(dev);
|
||||
}
|
||||
|
||||
static void wm8962_configure_input(const struct device *dev)
|
||||
{
|
||||
wm8962_route_input(dev, AUDIO_CHANNEL_FRONT_LEFT, kWM8962_InputPGASourceInput1);
|
||||
wm8962_route_input(dev, AUDIO_CHANNEL_FRONT_RIGHT, kWM8962_InputPGASourceInput3);
|
||||
|
||||
/* Input MIXER source */
|
||||
wm8962_write_reg(dev, WM8962_REG_INPUTMIX,
|
||||
(((kWM8962_InputMixerSourceInputPGA & 7U) << 3U) |
|
||||
(kWM8962_InputMixerSourceInputPGA & 7U)));
|
||||
/* Input MIXER enable */
|
||||
wm8962_write_reg(dev, WM8962_REG_INPUT_MIXER_1, 3U);
|
||||
|
||||
wm8962_in_volume_config(dev, AUDIO_CHANNEL_ALL, WM8962_LINEIN_DEFAULT_VOLUME_VALUE);
|
||||
wm8962_in_mute_config(dev, AUDIO_CHANNEL_ALL, false);
|
||||
}
|
||||
|
||||
#if DEBUG_WM8962_REGISTER
|
||||
static void WM8962_read_all_reg(const struct device *dev, uint16_t endAddress)
|
||||
{
|
||||
uint16_t readValue = 0U, i = 0U;
|
||||
|
||||
for (i = 0U; i < endAddress; i++) {
|
||||
wm8962_read_reg(dev, i, &readValue);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct audio_codec_api wm8962_driver_api = {.configure = wm8962_configure,
|
||||
.start_output = wm8962_start_output,
|
||||
.stop_output = wm8962_stop_output,
|
||||
.set_property = wm8962_set_property,
|
||||
.apply_properties =
|
||||
wm8962_apply_properties,
|
||||
.route_input = wm8962_route_input,
|
||||
.route_output = wm8962_route_output};
|
||||
|
||||
#define wm8962_INIT(n) \
|
||||
static const struct wm8962_driver_config wm8962_device_config_##n = { \
|
||||
.i2c = I2C_DT_SPEC_INST_GET(n), \
|
||||
.clock_source = DT_INST_PROP_OR(n, clk_source, 0), \
|
||||
.mclk_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR_BY_NAME(n, mclk)), \
|
||||
.mclk_name = (clock_control_subsys_t)DT_INST_CLOCKS_CELL_BY_NAME(n, mclk, name)}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(n, NULL, NULL, NULL, &wm8962_device_config_##n, POST_KERNEL, \
|
||||
CONFIG_AUDIO_CODEC_INIT_PRIORITY, &wm8962_driver_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(wm8962_INIT)
|
||||
391
drivers/audio/wm8962.h
Normal file
391
drivers/audio/wm8962.h
Normal file
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2025 NXP
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#ifndef ZEPHYR_DRIVERS_AUDIO_WM8962_H_
|
||||
#define ZEPHYR_DRIVERS_AUDIO_WM8962_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define WM8962_SWAP_UINT16_BYTE_SEQUENCE(x) ((((x) & 0x00ffU) << 8U) | (((x) & 0xff00U) >> 8U))
|
||||
/*! @brief WM8962 max clock */
|
||||
#define WM8962_MAX_DSP_CLOCK (24576000U)
|
||||
#define WM8962_MAX_SYS_CLOCK (12288000U)
|
||||
/*! @brief WM8962 f2 better performance range */
|
||||
#define WM8962_FLL_VCO_MIN_FREQ 90000000U
|
||||
#define WM8962_FLL_VCO_MAX_FREQ 100000000U
|
||||
#define WM8962_FLL_LOCK_TIMEOUT 10000000U
|
||||
/*! @brief WM8962 FLLN range */
|
||||
#define WM8962_FLL_N_MIN_VALUE 6U
|
||||
#define WM8962_FLL_N_MAX_VALUE 12U
|
||||
#define WM8962_FLL_MAX_REFERENCE_CLOCK (13500000U)
|
||||
|
||||
/*! @brief Define the register address of WM8962. */
|
||||
#define WM8962_REG_LINVOL 0x0U
|
||||
#define WM8962_REG_RINVOL 0x1U
|
||||
#define WM8962_REG_LOUT1 0x2U
|
||||
#define WM8962_REG_ROUT1 0x3U
|
||||
#define WM8962_REG_CLOCK1 0x4U
|
||||
#define WM8962_REG_DACCTL1 0x5U
|
||||
#define WM8962_REG_DACCTL2 0x6U
|
||||
#define WM8962_REG_IFACE0 0x7U
|
||||
#define WM8962_REG_IFACE1 0x9U
|
||||
#define WM8962_REG_CLOCK2 0x8U
|
||||
#define WM8962_REG_IFACE2 0xEU
|
||||
#define WM8962_REG_LDAC 0xaU
|
||||
#define WM8962_REG_RDAC 0xbU
|
||||
|
||||
#define WM8962_REG_RESET 0xfU
|
||||
#define WM8962_REG_3D 0x10U
|
||||
#define WM8962_REG_ALC1 0x11U
|
||||
#define WM8962_REG_ALC2 0x12U
|
||||
#define WM8962_REG_ALC3 0x13U
|
||||
#define WM8962_REG_NOISEG 0x14U
|
||||
#define WM8962_REG_LADC 0x15U
|
||||
#define WM8962_REG_RADC 0x16U
|
||||
#define WM8962_REG_ADDCTL1 0x17U
|
||||
#define WM8962_REG_ADDCTL2 0x18U
|
||||
#define WM8962_REG_POWER1 0x19U
|
||||
#define WM8962_REG_POWER2 0x1aU
|
||||
#define WM8962_REG_ADDCTL3 0x1bU
|
||||
#define WM8962_REG_APOP1 0x1cU
|
||||
#define WM8962_REG_APOP2 0x1dU
|
||||
#define WM8962_REG_INPUT_MIXER_1 0x1FU
|
||||
|
||||
#define WM8962_REG_LINPATH 0x20U
|
||||
#define WM8962_REG_RINPATH 0x21U
|
||||
#define WM8962_REG_INPUTMIX 0x22U
|
||||
|
||||
#define WM8962_REG_LEFT_INPUT_PGA 0x25U
|
||||
#define WM8962_REG_RIGHT_INPUT_PGA 0x26U
|
||||
#define WM8962_REG_MONOMIX2 0x27U
|
||||
#define WM8962_REG_LOUT2 0x28U
|
||||
#define WM8962_REG_ROUT2 0x29U
|
||||
#define WM8962_REG_TEMP 0x2FU
|
||||
#define WM8962_REG_ADDCTL4 0x30U
|
||||
#define WM8962_REG_CLASSD1 0x31U
|
||||
|
||||
#define WM8962_REG_CLASSD3 0x33U
|
||||
#define WM8962_REG_CLK4 0x38U
|
||||
#define WM8962_REG_DC_SERVO_0 0x3CU
|
||||
#define WM8962_REG_DC_SERVO_1 0x3DU
|
||||
#define WM8962_REG_ANALOG_HP_0 0x45U
|
||||
#define WM8962_REG_CHARGE_PUMP_1 0x48U
|
||||
|
||||
#define WM8962_REG_WRITE_SEQ_CTRL_1 0x57U
|
||||
#define WM8962_REG_WRITE_SEQ_CTRL_2 0x5AU
|
||||
#define WM8962_REG_WRITE_SEQ_CTRL_3 0x5DU
|
||||
|
||||
#define WM8962_WSEQ_ENA 0x20U
|
||||
|
||||
#define WM8962_REG_LEFT_HEADPHONE_MIXER 0x64U
|
||||
#define WM8962_REG_RIGHT_HEADPHONE_MIXER 0x65U
|
||||
#define WM8962_REG_LEFT_HEADPHONE_MIXER_VOLUME 0x66U
|
||||
#define WM8962_REG_RIGHT_HEADPHONE_MIXER_VOLUME 0x67U
|
||||
|
||||
#define WM8962_REG_LEFT_SPEAKER_MIXER 0x69U
|
||||
#define WM8962_REG_RIGHT_SPEAKER_MIXER 0x6AU
|
||||
#define WM8962_REG_LEFT_SPEAKER_MIXER_VOLUME 0x6BU
|
||||
#define WM8962_REG_RIGHT_SPEAKER_MIXER_VOLUME 0x6CU
|
||||
|
||||
#define WM8962_REG_PLL2 0x81U
|
||||
|
||||
#define WM8962_REG_FLL_CTRL_1 0x9BU
|
||||
#define WM8962_REG_FLL_CTRL_2 0x9CU
|
||||
#define WM8962_REG_FLL_CTRL_3 0x9DU
|
||||
#define WM8962_REG_FLL_CTRL_6 0xA0U
|
||||
#define WM8962_REG_FLL_CTRL_7 0xA1U
|
||||
#define WM8962_REG_FLL_CTRL_8 0xA2U
|
||||
#define WM8962_REG_INT_STATUS_2 0x231U
|
||||
|
||||
#define WSEQ_DONE_EINT_MASK 0x80U
|
||||
|
||||
#define WM8962_L_CH_MUTE_MASK 2U
|
||||
#define WM8962_R_CH_MUTE_MASK 1U
|
||||
|
||||
/*! @brief WM8962 CLOCK2 bits */
|
||||
#define WM8962_CLOCK2_BCLK_DIV_MASK 0xFU
|
||||
|
||||
/*! @brief WM8962_IFACE0 FORMAT bits */
|
||||
#define WM8962_IFACE0_FORMAT_MASK 0x13U
|
||||
#define WM8962_IFACE0_FORMAT_SHIFT 0x00U
|
||||
#define WM8962_IFACE0_FORMAT_RJ 0x00U
|
||||
#define WM8962_IFACE0_FORMAT_LJ 0x01U
|
||||
#define WM8962_IFACE0_FORMAT_I2S 0x02U
|
||||
#define WM8962_IFACE0_FORMAT_DSP 0x03U
|
||||
#define WM8962_IFACE0_FORMAT(x) (((x) << WM8962_IFACE1_FORMAT_SHIFT) & WM8962_IFACE1_FORMAT_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE0 WL bits */
|
||||
#define WM8962_IFACE0_WL_MASK 0x0CU
|
||||
#define WM8962_IFACE0_WL_SHIFT 0x02U
|
||||
#define WM8962_IFACE0_WL_16BITS 0x00U
|
||||
#define WM8962_IFACE0_WL_20BITS 0x01U
|
||||
#define WM8962_IFACE0_WL_24BITS 0x02U
|
||||
#define WM8962_IFACE0_WL_32BITS 0x03U
|
||||
#define WM8962_IFACE0_WL(x) (((x) << WM8962_IFACE0_WL_SHIFT) & WM8962_IFACE0_WL_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE1 LRP bit */
|
||||
#define WM8962_IFACE1_LRP_MASK 0x10U
|
||||
#define WM8962_IFACE1_LRP_SHIFT 0x04U
|
||||
#define WM8962_IFACE1_LRCLK_NORMAL_POL 0x00U
|
||||
#define WM8962_IFACE1_LRCLK_INVERT_POL 0x01U
|
||||
#define WM8962_IFACE1_DSP_MODEA 0x00U
|
||||
#define WM8962_IFACE1_DSP_MODEB 0x01U
|
||||
#define WM8962_IFACE1_LRP(x) (((x) << WM8962_IFACE1_LRP_SHIFT) & WM8962_IFACE1_LRP_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE1 DLRSWAP bit */
|
||||
#define WM8962_IFACE1_DLRSWAP_MASK 0x20U
|
||||
#define WM8962_IFACE1_DLRSWAP_SHIFT 0x05U
|
||||
#define WM8962_IFACE1_DACCH_NORMAL 0x00U
|
||||
#define WM8962_IFACE1_DACCH_SWAP 0x01U
|
||||
|
||||
#define WM8962_IFACE1_DLRSWAP(x) (((x) << WM8962_IFACE1_DLRSWAP_SHIFT) & WM8962_IFACE1_DLRSWAP_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE1 MS bit */
|
||||
#define WM8962_IFACE1_MS_MASK 0x40U
|
||||
#define WM8962_IFACE1_MS_SHIFT 0x06U
|
||||
#define WM8962_IFACE1_SLAVE 0x00U
|
||||
#define WM8962_IFACE1_MASTER 0x01U
|
||||
#define WM8962_IFACE1_MS(x) (((x) << WM8962_IFACE1_MS_SHIFT) & WM8962_IFACE1_MS_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE1 BCLKINV bit */
|
||||
#define WM8962_IFACE1_BCLKINV_MASK 0x80U
|
||||
#define WM8962_IFACE1_BCLKINV_SHIFT 0x07U
|
||||
#define WM8962_IFACE1_BCLK_NONINVERT 0x00U
|
||||
#define WM8962_IFACE1_BCLK_INVERT 0x01U
|
||||
|
||||
#define WM8962_IFACE1_BCLKINV(x) (((x) << WM8962_IFACE1_BCLKINV_SHIFT) & WM8962_IFACE1_BCLKINV_MASK)
|
||||
|
||||
/*! @brief WM8962_IFACE1 ALRSWAP bit */
|
||||
#define WM8962_IFACE1_ALRSWAP_MASK 0x100U
|
||||
#define WM8962_IFACE1_ALRSWAP_SHIFT 0x08U
|
||||
#define WM8962_IFACE1_ADCCH_NORMAL 0x00U
|
||||
#define WM8962_IFACE1_ADCCH_SWAP 0x01U
|
||||
|
||||
#define WM8962_IFACE1_ALRSWAP(x) (((x) << WM8962_IFACE1_ALRSWAP_SHIFT) & WM8962_IFACE1_ALRSWAP_MASK)
|
||||
|
||||
/*! @brief WM8962_POWER1 */
|
||||
#define WM8962_POWER1_VREF_MASK 0x40U
|
||||
#define WM8962_POWER1_VREF_SHIFT 0x06U
|
||||
|
||||
#define WM8962_POWER1_AINL_MASK 0x20U
|
||||
#define WM8962_POWER1_AINL_SHIFT 0x05U
|
||||
|
||||
#define WM8962_POWER1_AINR_MASK 0x10U
|
||||
#define WM8962_POWER1_AINR_SHIFT 0x04U
|
||||
|
||||
#define WM8962_POWER1_ADCL_MASK 0x08U
|
||||
#define WM8962_POWER1_ADCL_SHIFT 0x03U
|
||||
|
||||
#define WM8962_POWER1_ADCR_MASK 0x4U
|
||||
#define WM8962_POWER1_ADCR_SHIFT 0x02U
|
||||
|
||||
#define WM8962_POWER1_MICB_MASK 0x02U
|
||||
#define WM8962_POWER1_MICB_SHIFT 0x01U
|
||||
|
||||
#define WM8962_POWER1_DIGENB_MASK 0x01U
|
||||
#define WM8962_POWER1_DIGENB_SHIFT 0x00U
|
||||
|
||||
/*! @brief WM8962_POWER2 */
|
||||
#define WM8962_POWER2_DACL_MASK 0x100U
|
||||
#define WM8962_POWER2_DACL_SHIFT 0x08U
|
||||
|
||||
#define WM8962_POWER2_DACR_MASK 0x80U
|
||||
#define WM8962_POWER2_DACR_SHIFT 0x07U
|
||||
|
||||
#define WM8962_POWER2_LOUT1_MASK 0x40U
|
||||
#define WM8962_POWER2_LOUT1_SHIFT 0x06U
|
||||
|
||||
#define WM8962_POWER2_ROUT1_MASK 0x20U
|
||||
#define WM8962_POWER2_ROUT1_SHIFT 0x05U
|
||||
|
||||
#define WM8962_POWER2_SPKL_MASK 0x10U
|
||||
#define WM8962_POWER2_SPKL_SHIFT 0x04U
|
||||
|
||||
#define WM8962_POWER2_SPKR_MASK 0x08U
|
||||
#define WM8962_POWER2_SPKR_SHIFT 0x03U
|
||||
|
||||
#define WM8962_POWER3_LMIC_MASK 0x20U
|
||||
#define WM8962_POWER3_LMIC_SHIFT 0x05U
|
||||
#define WM8962_POWER3_RMIC_MASK 0x10U
|
||||
#define WM8962_POWER3_RMIC_SHIFT 0x04U
|
||||
#define WM8962_POWER3_LOMIX_MASK 0x08U
|
||||
#define WM8962_POWER3_LOMIX_SHIFT 0x03U
|
||||
#define WM8962_POWER3_ROMIX_MASK 0x04U
|
||||
#define WM8962_POWER3_ROMIX_SHIFT 0x02U
|
||||
/*! @brief WM8962 I2C address. */
|
||||
#define WM8962_I2C_ADDR (0x34 >> 1U)
|
||||
/*! @brief WM8962 I2C baudrate */
|
||||
#define WM8962_I2C_BAUDRATE (100000U)
|
||||
/*! @brief WM8962 maximum volume value */
|
||||
#define WM8962_ADC_MAX_VOLUME_VALUE 0xFFU
|
||||
#define WM8962_DAC_MAX_VOLUME_VALUE 0xFFU
|
||||
#define WM8962_HEADPHONE_MAX_VOLUME_VALUE 0x7FU
|
||||
#define WM8962_HEADPHONE_MIN_VOLUME_VALUE 0x2FU
|
||||
#define WM8962_LINEIN_MAX_VOLUME_VALUE 0x3FU
|
||||
#define WM8962_SPEAKER_MAX_VOLUME_VALUE 0x7FU
|
||||
#define WM8962_SPEAKER_MIN_VOLUME_VALUE 0x2FU
|
||||
|
||||
#define WM8962_ADC_DEFAULT_VOLUME_VALUE 0x1C0U
|
||||
#define WM8962_DAC_DEFAULT_VOLUME_VALUE 0x1C0U
|
||||
#define WM8962_HEADPHONE_DEFAULT_VOLUME_VALUE 0x179U
|
||||
#define WM8962_LINEIN_DEFAULT_VOLUME_VALUE 0x12DU
|
||||
#define WM8962_SPEAKER_DEFAULT_VOLUME_VALUE 0x179U
|
||||
|
||||
/**
|
||||
* WM8962_REG_LOUT1, WM8962_REG_ROUT1 (headphone outs),
|
||||
* WM8962_REG_LOUT2, WM8962_REG_ROUT2 (line outs):
|
||||
* [8] - VU: Volume update, works for entire channel pair
|
||||
* [7] - ZC: Zero-crossing enable
|
||||
* [6:0] - VOL: 7-bit volume value
|
||||
*/
|
||||
#define WM8962_REGVAL_OUT_VOL(vu, zc, vol) \
|
||||
(((vu & 0b1) << 8) | (zc & 0b1) << 7 | (vol & 0b001111111))
|
||||
#define WM8962_REGMASK_OUT_VU 0b100000000
|
||||
#define WM8962_REGMASK_OUT_ZC 0b010000000
|
||||
#define WM8962_REGMASK_OUT_VOL 0b001111111
|
||||
|
||||
#define WM8962_OUT_MUTE(x) ((x << 8U) & (1U << 8U))
|
||||
|
||||
/**
|
||||
* WM8962_REG_LINVOL, WM8962_REG_RINVOL:
|
||||
* [8] - VU: Volume update, works for entire channel pair
|
||||
* [7] - MUTE: Input mute
|
||||
* [6] - ZC: Zero-crossing enable
|
||||
* [5:0] - VOL: 6-bit volume value
|
||||
*/
|
||||
#define WM8962_REGVAL_IN_VOL(vu, mute, zc, vol) \
|
||||
((vu & 0b1) << 8 | (mute & 0b1) << 7 | (zc & 0b1) << 6 | (vol & 0b000111111))
|
||||
#define WM8962_REGMASK_IN_VU 0b100000000
|
||||
#define WM8962_REGMASK_IN_MUTE 0b010000000
|
||||
#define WM8962_REGMASK_IN_ZC 0b001000000
|
||||
#define WM8962_REGMASK_IN_VOLUME 0b000111111
|
||||
|
||||
/*! @brief wm8962 input mixer source.
|
||||
* @anchor wm8962_input_mixer_source_t
|
||||
*/
|
||||
typedef enum _wm8962_input_mixer_source {
|
||||
kWM8962_InputMixerSourceInput2 = 4U, /*!< input mixer source input 2 */
|
||||
kWM8962_InputMixerSourceInput3 = 2U, /*!< input mixer source input 3 */
|
||||
kWM8962_InputMixerSourceInputPGA = 1U, /*!< input mixer source input PGA */
|
||||
} wm8962_input_mixer_source_t;
|
||||
|
||||
/*! @brief wm8962 output mixer source.
|
||||
* @anchor wm8962_output_mixer_source_t
|
||||
*/
|
||||
typedef enum _wm8962_output_mixer_source {
|
||||
kWM8962_OutputMixerDisabled = 0U, /*!< output mixer disabled */
|
||||
kWM8962_OutputMixerSourceInput4Right = 1U, /*!< output mixer source input 4 left */
|
||||
kWM8962_OutputMixerSourceInput4Left = 2U, /*!< output mixer source input 4 right */
|
||||
kWM8962_OutputMixerSourceRightInputMixer = 4U, /*!< output mixer source left input mixer */
|
||||
kWM8962_OutputMixerSourceLeftInputMixer = 8U, /*!< output mixer source right input mixer*/
|
||||
kWM8962_OutputMixerSourceRightDAC = 0x10U, /*!< output mixer source left DAC */
|
||||
kWM8962_OutputMixerSourceLeftDAC = 0x20U, /*!< output mixer source Right DAC */
|
||||
} wm8962_output_mixer_source_t;
|
||||
|
||||
/*! @brief Modules in WM8962 board. */
|
||||
typedef enum _wm8962_module {
|
||||
kWM8962_ModuleADC = 0, /*!< ADC module in WM8962 */
|
||||
kWM8962_ModuleDAC = 1, /*!< DAC module in WM8962 */
|
||||
kWM8962_ModuleMICB = 4, /*!< Mic bias */
|
||||
kWM8962_ModuleMIC = 5, /*!< Input Mic */
|
||||
kWM8962_ModuleLineIn = 6, /*!< Analog in PGA */
|
||||
kWM8962_ModuleHeadphone = 7, /*!< Line out module */
|
||||
kWM8962_ModuleSpeaker = 8, /*!< Speaker module */
|
||||
kWM8962_ModuleHeaphoneMixer = 9, /*!< Output mixer */
|
||||
kWM8962_ModuleSpeakerMixer = 10, /*!< Output mixer */
|
||||
} wm8962_module_t;
|
||||
|
||||
/*! @brief wm8962 play channel
|
||||
* @anchor _wm8962_play_channel
|
||||
*/
|
||||
typedef enum _wm8962_play_channel {
|
||||
kWM8962_HeadphoneLeft = 1, /*!< wm8962 headphone left channel */
|
||||
kWM8962_HeadphoneRight = 2, /*!< wm8962 headphone right channel */
|
||||
kWM8962_SpeakerLeft = 4, /*!< wm8962 speaker left channel */
|
||||
kWM8962_SpeakerRight = 8, /*!< wm8962 speaker right channel */
|
||||
} wm8962_play_channel_t;
|
||||
|
||||
/*!
|
||||
* @brief The audio data transfer protocol choice.
|
||||
* WM8962 only supports I2S format and PCM format.
|
||||
*/
|
||||
typedef enum _wm8962_protocol {
|
||||
kWM8962_BusPCMA = 4, /*!< PCMA mode */
|
||||
kWM8962_BusPCMB = 3, /*!< PCMB mode */
|
||||
kWM8962_BusI2S = 2, /*!< I2S type */
|
||||
kWM8962_BusLeftJustified = 1, /*!< Left justified mode */
|
||||
kWM8962_BusRightJustified = 0, /*!< Right justified mode */
|
||||
} wm8962_protocol_t;
|
||||
|
||||
/*! @brief wm8962 input source */
|
||||
typedef enum _wm8962_input_pga_source {
|
||||
kWM8962_InputPGASourceInput1 = 8, /*!< Input PGA source input1 */
|
||||
kWM8962_InputPGASourceInput2 = 4, /*!< Input PGA source input2 */
|
||||
kWM8962_InputPGASourceInput3 = 2, /*!< Input PGA source input3 */
|
||||
kWM8962_InputPGASourceInput4 = 1, /*!< Input PGA source input4 */
|
||||
} wm8962_input_pga_source_t;
|
||||
|
||||
/*! @brief wm8962 input source */
|
||||
typedef enum _wm8962_output_pga_source {
|
||||
kWM8962_OutputPGASourceMixer = 0, /*!< Output PGA source mixer */
|
||||
kWM8962_OutputPGASourceDAC = 1, /*!< Output PGA source DAC */
|
||||
} wm8962_output_pga_source_t;
|
||||
|
||||
/*! @brief audio sample rate definition
|
||||
* @anchor _wm8962_sample_rate
|
||||
*/
|
||||
typedef enum _wm8962_sample_rate {
|
||||
kWM8962_AudioSampleRate8kHz = 8000U, /*!< Sample rate 8000 Hz */
|
||||
kWM8962_AudioSampleRate11025Hz = 11025U, /*!< Sample rate 11025 Hz */
|
||||
kWM8962_AudioSampleRate12kHz = 12000U, /*!< Sample rate 12000 Hz */
|
||||
kWM8962_AudioSampleRate16kHz = 16000U, /*!< Sample rate 16000 Hz */
|
||||
kWM8962_AudioSampleRate22050Hz = 22050U, /*!< Sample rate 22050 Hz */
|
||||
kWM8962_AudioSampleRate24kHz = 24000U, /*!< Sample rate 24000 Hz */
|
||||
kWM8962_AudioSampleRate32kHz = 32000U, /*!< Sample rate 32000 Hz */
|
||||
kWM8962_AudioSampleRate44100Hz = 44100U, /*!< Sample rate 44100 Hz */
|
||||
kWM8962_AudioSampleRate48kHz = 48000U, /*!< Sample rate 48000 Hz */
|
||||
kWM8962_AudioSampleRate88200Hz = 88200U, /*!< Sample rate 88200 Hz */
|
||||
kWM8962_AudioSampleRate96kHz = 96000U, /*!< Sample rate 96000 Hz */
|
||||
} wm8962_sample_rate_t;
|
||||
|
||||
/*! @brief audio bit width
|
||||
* @anchor _wm8962_audio_bit_width
|
||||
*/
|
||||
typedef enum _wm8962_audio_bit_width {
|
||||
kWM8962_AudioBitWidth16bit = 16U, /*!< audio bit width 16 */
|
||||
kWM8962_AudioBitWidth20bit = 20U, /*!< audio bit width 20 */
|
||||
kWM8962_AudioBitWidth24bit = 24U, /*!< audio bit width 24 */
|
||||
kWM8962_AudioBitWidth32bit = 32U, /*!< audio bit width 32 */
|
||||
} wm8962_audio_bit_width_t;
|
||||
|
||||
/*! @brief wm8962 fll clock source */
|
||||
typedef enum _wm8962_fllclk_source {
|
||||
kWM8962_FLLClkSourceMCLK = 0U, /*!< FLL clock source from MCLK */
|
||||
kWM8962_FLLClkSourceBCLK = 1U, /*!< FLL clock source from BCLK */
|
||||
} wm8962_fllclk_source_t;
|
||||
|
||||
/*! @brief wm8962 sysclk source */
|
||||
typedef enum _wm8962_sysclk_source {
|
||||
kWM8962_SysClkSourceMclk = 0U, /*!< sysclk source from external MCLK */
|
||||
kWM8962_SysClkSourceFLL = 1U, /*!< sysclk source from internal FLL */
|
||||
} wm8962_sysclk_source_t;
|
||||
|
||||
/*! @brief WM8962 default sequence */
|
||||
typedef enum _wm8962_sequence_id {
|
||||
kWM8962_SequenceDACToHeadphonePowerUp = 0x80U, /*!< dac to headphone power up sequence */
|
||||
kWM8962_SequenceAnalogueInputPowerUp = 0x92U, /*!< Analogue input power up sequence */
|
||||
kWM8962_SequenceChipPowerDown = 0x9BU, /*!< Chip power down sequence */
|
||||
kWM8962_SequenceSpeakerSleep = 0xE4U, /*!< Speaker sleep sequence */
|
||||
kWM8962_SequenceSpeakerWake = 0xE8U, /*!< speaker wake sequence */
|
||||
} wm8962_sequence_id_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* ZEPHYR_DRIVERS_AUDIO_WM8962_H_ */
|
||||
25
dts/bindings/audio/wolfson,wm8962.yaml
Normal file
25
dts/bindings/audio/wolfson,wm8962.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright 2025 NXP
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
description: WM8962 audio codec
|
||||
|
||||
include: [i2c-device.yaml]
|
||||
|
||||
compatible: "wolfson,wm8962"
|
||||
|
||||
properties:
|
||||
reg:
|
||||
required: true
|
||||
clock-source:
|
||||
type: string
|
||||
default: "MCLK"
|
||||
description: |
|
||||
Codec's internal clock signal (SYSCLK) source selection. These options
|
||||
are available:
|
||||
- "MCLK": wm8962's MCLK pin (supplied by the host)
|
||||
- "FLL": wm8962's FLL facility, can be free-running
|
||||
The "MCLK" option is default, as this clock signal is usually supplied
|
||||
by the host.
|
||||
enum:
|
||||
- "MCLK"
|
||||
- "FLL"
|
||||
Reference in New Issue
Block a user