Загрузка данных
#include <errno.h>
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/audio/codec.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(nxp_sgtl5000, CONFIG_AUDIO_CODEC_LOG_LEVEL);
#define DT_DRV_COMPAT nxp_sgtl5000
/* SGTL5000 Register Map (16-bit addresses) */
#define SGTL5000_CHIP_ID 0x0000
#define SGTL5000_CHIP_DIG_POWER 0x0002
#define SGTL5000_CHIP_CLK_CTRL 0x0004
#define SGTL5000_CHIP_I2S_CTRL 0x0006
#define SGTL5000_CHIP_SSS_CTRL 0x000A
#define SGTL5000_CHIP_ADCDAC_CTRL 0x000E
#define SGTL5000_CHIP_DAC_VOL 0x0010
#define SGTL5000_CHIP_PAD_STRENGTH 0x0014
#define SGTL5000_CHIP_ANA_ADC_CTRL 0x0020
#define SGTL5000_CHIP_ANA_HP_CTRL 0x0022
#define SGTL5000_CHIP_ANA_CTRL 0x0024
#define SGTL5000_CHIP_LINREG_CTRL 0x0026
#define SGTL5000_CHIP_REF_CTRL 0x0028
#define SGTL5000_CHIP_MIC_CTRL 0x002A
#define SGTL5000_CHIP_LINE_OUT_CTRL 0x002C
#define SGTL5000_CHIP_LINE_OUT_VOL 0x002E
#define SGTL5000_CHIP_ANA_POWER 0x0030
#define SGTL5000_CHIP_PLL_CTRL 0x0032
#define SGTL5000_CHIP_CLK_TOP_CTRL 0x0034
#define SGTL5000_CHIP_SHORT_CTRL 0x003C
#define SGTL5000_DAP_AUDIO_EQ 0x0116
/* Bitmask definitions from Linux driver */
#define SGTL5000_DAC_STEREO 0x0020
#define SGTL5000_ADC_STEREO 0x0004
#define SGTL5000_REFTOP_POWERUP 0x0010
#define SGTL5000_VAG_POWERUP 0x0002
#define SGTL5000_DAC_POWERUP 0x0010
#define SGTL5000_ADC_POWERUP 0x0040
#define SGTL5000_HP_POWERUP 0x0080
#define SGTL5000_I2S_IN_POWERUP 0x0001
#define SGTL5000_I2S_OUT_POWERUP 0x0002
#define SGTL5000_PLL_POWERUP 0x0400
#define SGTL5000_VCOAMP_POWERUP 0x0100
#define SGTL5000_LINE_OUT_POWERUP 0x0001
#define SGTL5000_I2S_MASTER 0x0080
#define SGTL5000_I2S_SCLK_INV 0x0040
#define SGTL5000_I2S_LRALIGN 0x0004
#define SGTL5000_I2S_LRPOL 0x0002
#define SGTL5000_HP_MUTE 0x0010
#define SGTL5000_LINE_OUT_MUTE 0x0100
#define SGTL5000_HP_SEL_MASK 0x0040
#define SGTL5000_HP_SEL_SHIFT 6
#define SGTL5000_DAC_SEL_SHIFT 2
#define SGTL5000_RATE_MODE_DIV_1 0x0
#define SGTL5000_RATE_MODE_DIV_2 0x1
#define SGTL5000_RATE_MODE_DIV_4 0x2
#define SGTL5000_RATE_MODE_SHIFT 2
#define SGTL5000_SYS_FS_32k 0x0
#define SGTL5000_SYS_FS_44_1k 0x1
#define SGTL5000_SYS_FS_48k 0x2
#define SGTL5000_SYS_FS_96k 0x3
#define SGTL5000_SYS_FS_SHIFT 0
#define SGTL5000_MCLK_FREQ_256FS 0x0
#define SGTL5000_MCLK_FREQ_384FS 0x1
#define SGTL5000_MCLK_FREQ_512FS 0x2
#define SGTL5000_MCLK_FREQ_PLL 0x3
#define SGTL5000_MCLK_FREQ_SHIFT 4
#define SGTL5000_MCLK_FREQ_MASK 0x0030
#define SGTL5000_PLL_INT_DIV_SHIFT 11
#define SGTL5000_PLL_FRAC_DIV_SHIFT 0
#define SGTL5000_INPUT_FREQ_DIV2 0x0008
struct sgtl5000_driver_config {
struct i2c_dt_spec i2c;
};
/* Low-level I2C Write: 16-bit Reg Address, 16-bit Data Value (Big Endian) */
static int sgtl5000_write_reg(const struct device *dev, uint16_t reg, uint16_t val)
{
const struct sgtl5000_driver_config *const dev_cfg = dev->config;
uint8_t buf[4] = {
(uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF),
(uint8_t)(val >> 8), (uint8_t)(val & 0xFF)
};
int ret = i2c_write_dt(&dev_cfg->i2c, buf, sizeof(buf));
if (ret != 0) {
LOG_ERR("I2C write error %d to reg 0x%04X", ret, reg);
}
return ret;
}
/* Low-level I2C Read: 16-bit Reg Address -> returns 16-bit Data Value */
static int sgtl5000_read_reg(const struct device *dev, uint16_t reg, uint16_t *val)
{
const struct sgtl5000_driver_config *const dev_cfg = dev->config;
uint8_t reg_buf[2] = { (uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF) };
uint8_t data_buf[2];
int ret = i2c_write_read_dt(&dev_cfg->i2c, reg_buf, sizeof(reg_buf), data_buf, sizeof(data_buf));
if (ret != 0) {
LOG_ERR("I2C read error %d from reg 0x%04X", ret, reg);
return ret;
}
*val = (data_buf[0] << 8) | data_buf[1];
return 0;
}
/* Read-Modify-Write Update Function */
static int sgtl5000_update_reg(const struct device *dev, uint16_t reg, uint16_t mask, uint16_t val)
{
uint16_t reg_val = 0;
int ret = sgtl5000_read_reg(dev, reg, ®_val);
if (ret != 0) return ret;
uint16_t new_value = (reg_val & ~mask) | (val & mask);
return sgtl5000_write_reg(dev, reg, new_value);
}
/* Clocks and Dividers Math Engine (Ported from Linux sgtl5000_set_clock) */
static int sgtl5000_set_clock(const struct device *dev, uint32_t mclk_freq, int frame_rate, bool is_master)
{
int clk_ctl = 0;
int sys_fs;
switch (frame_rate) {
case 8000:
case 16000:
sys_fs = 32000;
break;
case 11025:
case 22050:
sys_fs = 44100;
break;
default:
sys_fs = frame_rate;
break;
}
switch (sys_fs / frame_rate) {
case 4: clk_ctl |= SGTL5000_RATE_MODE_DIV_4 << SGTL5000_RATE_MODE_SHIFT; break;
case 2: clk_ctl |= SGTL5000_RATE_MODE_DIV_2 << SGTL5000_RATE_MODE_SHIFT; break;
case 1: clk_ctl |= SGTL5000_RATE_MODE_DIV_1 << SGTL5000_RATE_MODE_SHIFT; break;
default: return -EINVAL;
}
switch (sys_fs) {
case 32000: clk_ctl |= SGTL5000_SYS_FS_32k << SGTL5000_SYS_FS_SHIFT; break;
case 44100: clk_ctl |= SGTL5000_SYS_FS_44_1k << SGTL5000_SYS_FS_SHIFT; break;
case 48000: clk_ctl |= SGTL5000_SYS_FS_48k << SGTL5000_SYS_FS_SHIFT; break;
case 96000: clk_ctl |= SGTL5000_SYS_FS_96k << SGTL5000_SYS_FS_SHIFT; break;
default: return -EINVAL;
}
uint32_t mclk_ratio = mclk_freq / frame_rate;
switch (mclk_ratio) {
case 256: clk_ctl |= SGTL5000_MCLK_FREQ_256FS << SGTL5000_MCLK_FREQ_SHIFT; break;
case 384: clk_ctl |= SGTL5000_MCLK_FREQ_384FS << SGTL5000_MCLK_FREQ_SHIFT; break;
case 512: clk_ctl |= SGTL5000_MCLK_FREQ_512FS << SGTL5000_MCLK_FREQ_SHIFT; break;
default:
if (is_master) {
clk_ctl |= SGTL5000_MCLK_FREQ_PLL << SGTL5000_MCLK_FREQ_SHIFT;
} else {
LOG_ERR("PLL calculation not supported in slave mode. Ratio %d invalid", mclk_ratio);
return -EINVAL;
}
}
/* PLL Configuration if required */
if ((clk_ctl & SGTL5000_MCLK_FREQ_MASK) == SGTL5000_MCLK_FREQ_PLL) {
uint64_t out;
int div2 = 0;
uint32_t in = mclk_freq;
if (mclk_freq > 17000000) {
div2 = 1;
in = mclk_freq / 2;
}
out = (sys_fs == 44100) ? 180633600ULL : 196608000ULL;
uint32_t int_div = (uint32_t)(out / in);
uint32_t frac_div = (uint32_t)(((out % in) * 2048ULL) / in);
uint32_t pll_ctl = (int_div << SGTL5000_PLL_INT_DIV_SHIFT) | (frac_div << SGTL5000_PLL_FRAC_DIV_SHIFT);
sgtl5000_write_reg(dev, SGTL5000_CHIP_PLL_CTRL, pll_ctl);
sgtl5000_update_reg(dev, SGTL5000_CHIP_CLK_TOP_CTRL, SGTL5000_INPUT_FREQ_DIV2, div2 ? SGTL5000_INPUT_FREQ_DIV2 : 0);
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP);
sgtl5000_write_reg(dev, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
} else {
sgtl5000_write_reg(dev, SGTL5000_CHIP_CLK_CTRL, clk_ctl);
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_PLL_POWERUP | SGTL5000_VCOAMP_POWERUP, 0);
}
return 0;
}
static int sgtl5000_protocol_config(const struct device *dev, audio_dai_type_t dai_type, bool is_master)
{
uint16_t i2sctl = 0;
if (is_master) {
i2sctl |= SGTL5000_I2S_MASTER;
}
switch (dai_type) {
case AUDIO_DAI_TYPE_I2S:
i2sctl |= (0x00 << 2); /* I2S mode */
break;
case AUDIO_DAI_TYPE_LEFT_JUSTIFIED:
i2sctl |= (0x01 << 2); /* Left Justified */
i2sctl |= SGTL5000_I2S_LRALIGN;
break;
case AUDIO_DAI_TYPE_RIGHT_JUSTIFIED:
i2sctl |= (0x02 << 2); /* Right Justified */
i2sctl |= SGTL5000_I2S_LRPOL;
break;
default:
LOG_ERR("Unsupported DAI protocol type");
return -EINVAL;
}
return sgtl5000_update_reg(dev, SGTL5000_CHIP_I2S_CTRL, 0x00FF, i2sctl);
}
/* Audio Configuration Interface Implementation */
static int sgtl5000_configure(const struct device *dev, struct audio_codec_cfg *cfg)
{
int ret;
bool is_master = (cfg->dai_cfg.i2s.options & I2S_OPT_FRAME_CLK_MASTER);
if (cfg->dai_type >= AUDIO_DAI_TYPE_INVALID) {
return -EINVAL;
}
/* 1. Setup Data Length inside I2S Control based on word_size */
uint16_t i2s_size_ctl = 0;
switch (cfg->dai_cfg.i2s.word_size) {
case 16: i2s_size_ctl |= (0x03 << 4); break; /* 16 bits */
case 20: i2s_size_ctl |= (0x02 << 4); break; /* 20 bits */
case 24: i2s_size_ctl |= (0x01 << 4); break; /* 24 bits */
case 32: i2s_size_ctl |= (0x00 << 4); break; /* 32 bits */
default: return -EINVAL;
}
sgtl5000_update_reg(dev, SGTL5000_CHIP_I2S_CTRL, 0x0030, i2s_size_ctl);
/* 2. Configure Core Hardware Clocks & Dividers Matrix */
ret = sgtl5000_set_clock(dev, cfg->mclk_freq, cfg->dai_cfg.i2s.frame_clk_freq, is_master);
if (ret != 0) return ret;
/* 3. Program Data Bus Routing */
ret = sgtl5000_protocol_config(dev, cfg->dai_type, is_master);
if (ret != 0) return ret;
/* 4. Map Paths based on Zephyr Router configuration */
switch (cfg->dai_route) {
case AUDIO_ROUTE_PLAYBACK:
/* Source Selection System: I2S -> DAC */
sgtl5000_update_reg(dev, SGTL5000_CHIP_SSS_CTRL, 0x0030, 0x01 << 4);
/* Headphone Mux Selection: DAC to HP */
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_CTRL, SGTL5000_HP_SEL_MASK, 0);
/* Power UP Execution Block */
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_DAC_POWERUP | SGTL5000_HP_POWERUP | SGTL5000_VAG_POWERUP,
SGTL5000_DAC_POWERUP | SGTL5000_HP_POWERUP | SGTL5000_VAG_POWERUP);
break;
case AUDIO_ROUTE_CAPTURE:
/* Capture Mux Selection: Default to LINE_IN (bit 2 = 1) */
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_CTRL, 0x0004, 0x0004);
/* Power UP Capture Core Engine */
sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_ADC_POWERUP | SGTL5000_VAG_POWERUP,SGTL5000_ADC_POWERUP | SGTL5000_VAG_POWERUP);break;case AUDIO_ROUTE_PLAYBACK_CAPTURE:sgtl5000_update_reg(dev, SGTL5000_CHIP_SSS_CTRL, 0x0030, 0x01 << 4);sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_CTRL, SGTL5000_HP_SEL_MASK | 0x0004, 0x0004);sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_DAC_POWERUP | SGTL5000_HP_POWERUP | SGTL5000_ADC_POWERUP | SGTL5000_VAG_POWERUP,SGTL5000_DAC_POWERUP | SGTL5000_HP_POWERUP | SGTL5000_ADC_POWERUP | SGTL5000_VAG_POWERUP);break;default:break;}return 0;}
/* Volume Mapping and Muting Control Logic */static int sgtl5000_set_property(const struct device *dev, audio_property_t property,audio_channel_t channel, audio_property_value_t val){uint16_t reg_val;switch (property) {case AUDIO_PROPERTY_OUTPUT_VOLUME:/* Map Zephyr 0-100% Volume scale into SGTL5000 hardware units // Hardware Scale: 0x3C (0dB, MAX) down to 0xF0 (-90dB, MIN) /if (val.vol == 0) {reg_val = (0xFC << 8) | 0xFC; / Muted code */} else {uint8_t steps = (uint8_t)(0x3C + ((100 - val.vol) * (0xF0 - 0x3C)) / 100);reg_val = (steps << 8) | steps;}return sgtl5000_write_reg(dev, SGTL5000_CHIP_DAC_VOL, reg_val);case AUDIO_PROPERTY_OUTPUT_MUTE:return sgtl5000_update_reg(dev, SGTL5000_CHIP_ANA_CTRL, SGTL5000_HP_MUTE, val.mute ? SGTL5000_HP_MUTE : 0);default:break;}return -ENOTSUP;}static void sgtl5000_start_output(const struct device *dev) {}static void sgtl5000_stop_output(const struct device *dev) {}/* Core Hardware Power-Up Sequence (Ported from Linux Probe Routine) */static int sgtl5000_init(const struct device *dev){const struct sgtl5000_driver_config *const dev_cfg = dev->config;uint16_t chip_id = 0;if (!device_is_ready(dev_cfg->i2c.bus)) {LOG_ERR("I2C Control Bus not ready");return -ENODEV;}
/* Read Chip Device Signature Verification code */if (sgtl5000_read_reg(dev, SGTL5000_CHIP_ID, &chip_id) != 0) {return -EIO;}if ((chip_id >> 8) != 0xA0) {LOG_ERR("Device signature mismatch! Read ID: 0x%04X", chip_id);return -ENODEV;}/* Power configuration sequence assuming default 3.3V inputs */sgtl5000_write_reg(dev, SGTL5000_CHIP_ANA_POWER, SGTL5000_DAC_STEREO | SGTL5000_ADC_STEREO | SGTL5000_REFTOP_POWERUP);/* Linear Regulator Setup: Use internal LDO for VDDD */sgtl5000_write_reg(dev, SGTL5000_CHIP_LINREG_CTRL, 0x0008);/* VAG GND Reference voltage configuration -> VDDA/2 (~1.65V) /sgtl5000_write_reg(dev, SGTL5000_CHIP_REF_CTRL, 0x01FF);sgtl5000_write_reg(dev, SGTL5000_CHIP_SHORT_CTRL, 0x0000); / Disable short detection */sgtl5000_write_reg(dev, SGTL5000_CHIP_DIG_POWER, SGTL5000_ADC_EN | SGTL5000_DAC_EN);/* Default unmuted configurations with soft ramp profiles /sgtl5000_write_reg(dev, SGTL5000_CHIP_ADCDAC_CTRL, 0x000C);sgtl5000_write_reg(dev, SGTL5000_CHIP_PAD_STRENGTH, 0x015F);sgtl5000_write_reg(dev, SGTL5000_DAP_AUDIO_EQ, 0x0001); / Enable DAP GEQ mode */LOG_INF("NXP SGTL5000 Codec initialized successfully. ID: 0x%02X Revision: 0x%02X", (chip_id >> 8), (chip_id & 0xFF));return 0;}static const struct audio_codec_api sgtl5000_driver_api = {.configure = sgtl5000_configure,.start_output = sgtl5000_start_output,.stop_output = sgtl5000_stop_output,.set_property = sgtl5000_set_property,};#define SGTL5000_INIT(n) static const struct sgtl5000_driver_config sgtl5000_device_config_##n = { .i2c = I2C_DT_SPEC_INST_GET(n), }; DEVICE_DT_INST_DEFINE(n, sgtl5000_init, NULL, NULL, &sgtl5000_device_config_##n, POST_KERNEL, CONFIG_AUDIO_CODEC_INIT_PRIORITY, &sgtl5000_driver_api);DT_INST_FOREACH_STATUS_OKAY(SGTL5000_INIT)