From: Ferus Castor Date: Mon, 1 Jun 2026 01:58:48 +0000 (-0700) Subject: ALSA: oxygen: add HT-Omega eClaro (7284:9783) support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af31e980def6cd6554ad4d7f266ecf5c4ebce4e3;p=thirdparty%2Flinux.git ALSA: oxygen: add HT-Omega eClaro (7284:9783) support The HT-Omega eClaro is a PCI sound card built on the C-Media CMI8788 (Oxygen HD) controller, with PCI subsystem ID 7284:9783. Output hardware: - AK4396VF stereo DAC: front L/R output, connected via SPI CE0 - CS4362A 6-channel DAC: surround, center/LFE, and side outputs, connected via SPI CE1 with a 3-byte [0x30, reg, val] frame The CS4362A uses inverse attenuation encoding (0 = 0 dB, 127 = max attenuation) and a 0.5 dB/step logarithmic scale. Volume TLV is set to TLV_DB_SCALE(-6350, 50, 0) to match the hardware. The channel-to- register mapping was verified by listening test: - Pair 1 (regs 7/8): side L/R (ALSA channels 6/7) - Pair 2 (regs 10/11): center/LFE (ALSA channels 4/5) - Pair 3 (regs 13/14): rear L/R (ALSA channels 2/3) Input hardware: - CS5361 stereo ADC: Line In and Mic In capture GPIO assignments: - GPIO 0 (0x0001): CS4362A RESET# (active-low, driven high) - GPIO 2/3: CS5361 M0/M1 (sample rate mode) - GPIO 5 (0x0020): front output stage enable (driven high) - GPIO 8 (0x0100): headphone amplifier enable Signed-off-by: Ferus Castor Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/20260601015848.128566-1-feruscastor@proton.me Signed-off-by: Takashi Iwai --- diff --git a/sound/pci/oxygen/oxygen.c b/sound/pci/oxygen/oxygen.c index e6f869cf8ca26..a4212b2558eed 100644 --- a/sound/pci/oxygen/oxygen.c +++ b/sound/pci/oxygen/oxygen.c @@ -25,6 +25,11 @@ * GPIO 6 -> S/PDIF from optical (0) or coaxial (1) input * GPIO 8 -> enable headphone amplifier * + * eClaro model: + * GPIO 2 -> M0 of CS5361 + * GPIO 3 -> M1 of CS5361 + * GPIO 8 -> enable headphone amplifier + * * CM9780: * * LINE_OUT -> input of ADC @@ -51,6 +56,7 @@ #include "oxygen.h" #include "xonar_dg.h" #include "ak4396.h" +#include "cs4362a.h" #include "wm8785.h" MODULE_AUTHOR("Clemens Ladisch "); @@ -74,6 +80,7 @@ enum { MODEL_MERIDIAN_2G, MODEL_CLARO, MODEL_CLARO_HALO, + MODEL_ECLARO, MODEL_FANTASIA, MODEL_SERENADE, MODEL_2CH_OUTPUT, @@ -113,6 +120,8 @@ static const struct pci_device_id oxygen_ids[] = { { OXYGEN_PCI_SUBID(0x7284, 0x9761), .driver_data = MODEL_CLARO }, /* HT-Omega Claro halo */ { OXYGEN_PCI_SUBID(0x7284, 0x9781), .driver_data = MODEL_CLARO_HALO }, + /* HT-Omega eClaro */ + { OXYGEN_PCI_SUBID(0x7284, 0x9783), .driver_data = MODEL_ECLARO }, { } }; MODULE_DEVICE_TABLE(pci, oxygen_ids); @@ -130,27 +139,35 @@ MODULE_DEVICE_TABLE(pci, oxygen_ids); #define GPIO_CLARO_DIG_COAX 0x0040 #define GPIO_CLARO_HP 0x0100 +#define GPIO_ECLARO_CS4362A_NRESET 0x0001 /* GPIO 0: CS4362A RESET# (active-low) */ +#define GPIO_ECLARO_FRONT_ENABLE 0x0020 /* GPIO 5: front output stage enable */ + +/* CS4362A SPI: 3-byte frame [0x30, reg, value] on CE1, 1280 ns/bit clock */ +#define ECLARO_CS4362A_SPI_CONTROL \ + (OXYGEN_SPI_TRIGGER | OXYGEN_SPI_DATA_LENGTH_3 | \ + OXYGEN_SPI_CLOCK_1280 | (1 << OXYGEN_SPI_CODEC_SHIFT) | \ + OXYGEN_SPI_CEN_LATCH_CLOCK_HI) + struct generic_data { unsigned int dacs; + u8 spi_map[4]; + u16 spi_prefix[4]; u8 ak4396_regs[4][5]; + u8 cs4362a_regs[15]; u16 wm8785_regs[3]; }; static void ak4396_write(struct oxygen *chip, unsigned int codec, u8 reg, u8 value) { - /* maps ALSA channel pair number to SPI output */ - static const u8 codec_spi_map[4] = { - 0, 1, 2, 4 - }; struct generic_data *data = chip->model_data; oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | OXYGEN_SPI_DATA_LENGTH_2 | OXYGEN_SPI_CLOCK_160 | - (codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | + (data->spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) | OXYGEN_SPI_CEN_LATCH_CLOCK_HI, - AK4396_WRITE | (reg << 8) | value); + data->spi_prefix[codec] | (reg << 8) | value); data->ak4396_regs[codec][reg] = value; } @@ -163,6 +180,51 @@ static void ak4396_write_cached(struct oxygen *chip, unsigned int codec, ak4396_write(chip, codec, reg, value); } +static void eclaro_cs4362a_write(struct oxygen *chip, u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + int err; + + if (reg < ARRAY_SIZE(data->cs4362a_regs)) + data->cs4362a_regs[reg] = value; + + err = oxygen_write_spi(chip, ECLARO_CS4362A_SPI_CONTROL, + (0x30u << 16) | ((u32)reg << 8) | value); + if (err) + dev_err(chip->card->dev, + "CS4362A SPI timeout: reg=0x%02x val=0x%02x\n", + reg, value); +} + +static void eclaro_cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value) +{ + struct generic_data *data = chip->model_data; + + if (value != data->cs4362a_regs[reg]) + eclaro_cs4362a_write(chip, reg, value); +} + +static void eclaro_cs4362a_registers_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + eclaro_cs4362a_write(chip, 1, CS4362A_CPEN | CS4362A_PDN); + eclaro_cs4362a_write(chip, 2, CS4362A_DIF_LJUST); + eclaro_cs4362a_write(chip, 3, CS4362A_SOFT_RAMP | CS4362A_AMUTE); + eclaro_cs4362a_write(chip, 4, data->cs4362a_regs[4]); + eclaro_cs4362a_write(chip, 5, 0); + eclaro_cs4362a_write(chip, 6, data->cs4362a_regs[6]); + eclaro_cs4362a_write(chip, 7, data->cs4362a_regs[7]); + eclaro_cs4362a_write(chip, 8, data->cs4362a_regs[8]); + eclaro_cs4362a_write(chip, 9, data->cs4362a_regs[9]); + eclaro_cs4362a_write(chip, 10, data->cs4362a_regs[10]); + eclaro_cs4362a_write(chip, 11, data->cs4362a_regs[11]); + eclaro_cs4362a_write(chip, 12, data->cs4362a_regs[12]); + eclaro_cs4362a_write(chip, 13, data->cs4362a_regs[13]); + eclaro_cs4362a_write(chip, 14, data->cs4362a_regs[14]); + eclaro_cs4362a_write(chip, 1, CS4362A_CPEN); +} + static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value) { struct generic_data *data = chip->model_data; @@ -199,8 +261,13 @@ static void ak4396_registers_init(struct oxygen *chip) static void ak4396_init(struct oxygen *chip) { struct generic_data *data = chip->model_data; + static const u8 default_spi_map[4] = { 0, 1, 2, 4 }; + unsigned int i; data->dacs = chip->model.dac_channels_pcm / 2; + memcpy(data->spi_map, default_spi_map, sizeof(default_spi_map)); + for (i = 0; i < 4; ++i) + data->spi_prefix[i] = AK4396_WRITE; data->ak4396_regs[0][AK4396_CONTROL_2] = AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL; ak4396_registers_init(chip); @@ -322,6 +389,102 @@ static void claro_resume(struct oxygen *chip) claro_enable_hp(chip); } +#define GPIO_CS5361_M_MASK 0x000c +#define GPIO_CS5361_M_SINGLE 0x0000 +#define GPIO_CS5361_M_DOUBLE 0x0004 +#define GPIO_CS5361_M_QUAD 0x0008 + +static void cs5361_init(struct oxygen *chip) +{ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS5361_M_MASK); + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + GPIO_CS5361_M_SINGLE, GPIO_CS5361_M_MASK); +} + +static void set_cs5361_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + unsigned int value; + + if (params_rate(params) <= 54000) + value = GPIO_CS5361_M_SINGLE; + else if (params_rate(params) <= 108000) + value = GPIO_CS5361_M_DOUBLE; + else + value = GPIO_CS5361_M_QUAD; + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + value, GPIO_CS5361_M_MASK); +} + +static void eclaro_init(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX); + oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX); + + /* Single AK4396VF on SPI CE0/CA=00 handles front L/R */ + data->dacs = 1; + data->spi_map[0] = 0; + data->spi_prefix[0] = AK4396_WRITE; + data->ak4396_regs[0][AK4396_CONTROL_2] = + AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL; + + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS); + ak4396_write(chip, 0, AK4396_CONTROL_2, + data->ak4396_regs[0][AK4396_CONTROL_2]); + ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM); + ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + + /* CS4362A (SPI CE1): surround/center-LFE/side L/R. + * GPIO 0 (RESET#, active-low) and GPIO 5 (front output enable) must + * be driven high. GPIOs 1 and 7 are outputs driven high. + */ + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x00a3); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, 0x00a3); + usleep_range(1000, 2000); + + data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE; + data->cs4362a_regs[6] = CS4362A_FM_SINGLE | + CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L; + data->cs4362a_regs[7] = CS4362A_MUTE; + data->cs4362a_regs[9] = data->cs4362a_regs[6]; + data->cs4362a_regs[12] = data->cs4362a_regs[6]; + + eclaro_cs4362a_registers_init(chip); + + snd_component_add(chip->card, "AK4396"); + snd_component_add(chip->card, "CS4362A"); + cs5361_init(chip); + claro_enable_hp(chip); + snd_component_add(chip->card, "CS5361"); +} + +static void eclaro_resume(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + + oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, + GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, + GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE); + + /* AK4396 chip 0 */ + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + ak4396_write(chip, 0, AK4396_CONTROL_2, + data->ak4396_regs[0][AK4396_CONTROL_2]); + ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM); + ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + + eclaro_cs4362a_registers_init(chip); + + cs5361_init(chip); + claro_enable_hp(chip); +} + static void stereo_resume(struct oxygen *chip) { ak4396_registers_init(chip); @@ -355,6 +518,76 @@ static void set_ak4396_params(struct oxygen *chip, } } +static void eclaro_set_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + struct generic_data *data = chip->model_data; + u8 ak_value, cs_fm; + + ak_value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK; + if (params_rate(params) <= 54000) { + ak_value |= AK4396_DFS_NORMAL; + cs_fm = CS4362A_FM_SINGLE; + } else if (params_rate(params) <= 108000) { + ak_value |= AK4396_DFS_DOUBLE; + cs_fm = CS4362A_FM_DOUBLE; + } else { + ak_value |= AK4396_DFS_QUAD; + cs_fm = CS4362A_FM_QUAD; + } + + usleep_range(1000, 2000); + + if (ak_value != data->ak4396_regs[0][AK4396_CONTROL_2]) { + ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS); + ak4396_write(chip, 0, AK4396_CONTROL_2, ak_value); + ak4396_write(chip, 0, AK4396_CONTROL_1, + AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN); + data->ak4396_regs[0][AK4396_CONTROL_2] = ak_value; + } + + /* Update CS4362A FM mode for all three DAC pairs */ + cs_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK; + eclaro_cs4362a_write_cached(chip, 6, cs_fm); + eclaro_cs4362a_write_cached(chip, 12, cs_fm); + cs_fm &= CS4362A_FM_MASK; + cs_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK; + eclaro_cs4362a_write_cached(chip, 9, cs_fm); +} + +static void update_eclaro_volume(struct oxygen *chip) +{ + u8 mute = chip->dac_mute ? CS4362A_MUTE : 0; + + ak4396_write_cached(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2); + ak4396_write_cached(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2); + + /* CS4362A attenuation is inverse: 0 = 0 dB, 127 = max attenuation. + * Pair 1 (regs 7/8) is wired to the side outputs (ALSA ch 6/7); + * pair 3 (regs 13/14) is wired to the rear outputs (ALSA ch 2/3). + */ + eclaro_cs4362a_write_cached(chip, 7, mute | (127 - chip->dac_volume[6])); + eclaro_cs4362a_write_cached(chip, 8, mute | (127 - chip->dac_volume[7])); + eclaro_cs4362a_write_cached(chip, 10, mute | (127 - chip->dac_volume[4])); + eclaro_cs4362a_write_cached(chip, 11, mute | (127 - chip->dac_volume[5])); + eclaro_cs4362a_write_cached(chip, 13, mute | (127 - chip->dac_volume[2])); + eclaro_cs4362a_write_cached(chip, 14, mute | (127 - chip->dac_volume[3])); +} + +static void update_eclaro_mute(struct oxygen *chip) +{ + struct generic_data *data = chip->model_data; + u8 value; + + value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE; + if (chip->dac_mute) + value |= AK4396_SMUTE; + ak4396_write_cached(chip, 0, AK4396_CONTROL_2, value); + + /* Re-apply volume+mute to CS4362A so the mute bit is set correctly */ + update_eclaro_volume(chip); +} + static void update_ak4396_volume(struct oxygen *chip) { struct generic_data *data = chip->model_data; @@ -702,6 +935,8 @@ static void dump_oxygen_registers(struct oxygen *chip, } static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0); +/* CS4362A: 0.5 dB/step, raw=127 -> 0 dB, raw=0 -> -63.5 dB */ +static const DECLARE_TLV_DB_SCALE(eclaro_db_scale, -6350, 50, 0); static const struct oxygen_model model_generic = { .shortname = "C-Media CMI8788", @@ -745,6 +980,7 @@ static int get_oxygen_model(struct oxygen *chip, [MODEL_MERIDIAN_2G] = "AuzenTech X-Meridian 2G", [MODEL_CLARO] = "HT-Omega Claro", [MODEL_CLARO_HALO] = "HT-Omega Claro halo", + [MODEL_ECLARO] = "HT-Omega eClaro", [MODEL_FANTASIA] = "TempoTec HiFier Fantasia", [MODEL_SERENADE] = "TempoTec HiFier Serenade", [MODEL_HG2PCI] = "CMI8787-HG2PCI", @@ -788,6 +1024,28 @@ static int get_oxygen_model(struct oxygen *chip, CAPTURE_0_FROM_I2S_2 | CAPTURE_1_FROM_SPDIF; break; + case MODEL_ECLARO: + chip->model.init = eclaro_init; + chip->model.mixer_init = generic_mixer_init; + chip->model.cleanup = claro_cleanup; + chip->model.suspend = claro_suspend; + chip->model.resume = eclaro_resume; + chip->model.set_dac_params = eclaro_set_dac_params; + chip->model.set_adc_params = set_cs5361_params; + chip->model.update_dac_volume = update_eclaro_volume; + chip->model.update_dac_mute = update_eclaro_mute; + chip->model.dump_registers = dump_ak4396_registers; + chip->model.device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_2 | + CAPTURE_1_FROM_SPDIF; + chip->model.function_flags = OXYGEN_FUNCTION_SPI | + OXYGEN_FUNCTION_ENABLE_SPI_4_5; + chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128); + chip->model.dac_volume_min = 0; + chip->model.dac_volume_max = 127; + chip->model.dac_tlv = eclaro_db_scale; + break; case MODEL_FANTASIA: case MODEL_SERENADE: case MODEL_2CH_OUTPUT: