You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
728 lines
18 KiB
728 lines
18 KiB
/* |
|
* 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)
|
|
|