Загрузка данных


#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, &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)