Skip to content

Commit af31e98

Browse files
Ferus Castortiwai
authored andcommitted
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 <feruscastor@proton.me> Assisted-by: Claude:claude-sonnet-4-6 Link: https://patch.msgid.link/20260601015848.128566-1-feruscastor@proton.me Signed-off-by: Takashi Iwai <tiwai@suse.de>
1 parent e9c82f7 commit af31e98

1 file changed

Lines changed: 264 additions & 6 deletions

File tree

sound/pci/oxygen/oxygen.c

Lines changed: 264 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
* GPIO 6 -> S/PDIF from optical (0) or coaxial (1) input
2626
* GPIO 8 -> enable headphone amplifier
2727
*
28+
* eClaro model:
29+
* GPIO 2 -> M0 of CS5361
30+
* GPIO 3 -> M1 of CS5361
31+
* GPIO 8 -> enable headphone amplifier
32+
*
2833
* CM9780:
2934
*
3035
* LINE_OUT -> input of ADC
@@ -51,6 +56,7 @@
5156
#include "oxygen.h"
5257
#include "xonar_dg.h"
5358
#include "ak4396.h"
59+
#include "cs4362a.h"
5460
#include "wm8785.h"
5561

5662
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
@@ -74,6 +80,7 @@ enum {
7480
MODEL_MERIDIAN_2G,
7581
MODEL_CLARO,
7682
MODEL_CLARO_HALO,
83+
MODEL_ECLARO,
7784
MODEL_FANTASIA,
7885
MODEL_SERENADE,
7986
MODEL_2CH_OUTPUT,
@@ -113,6 +120,8 @@ static const struct pci_device_id oxygen_ids[] = {
113120
{ OXYGEN_PCI_SUBID(0x7284, 0x9761), .driver_data = MODEL_CLARO },
114121
/* HT-Omega Claro halo */
115122
{ OXYGEN_PCI_SUBID(0x7284, 0x9781), .driver_data = MODEL_CLARO_HALO },
123+
/* HT-Omega eClaro */
124+
{ OXYGEN_PCI_SUBID(0x7284, 0x9783), .driver_data = MODEL_ECLARO },
116125
{ }
117126
};
118127
MODULE_DEVICE_TABLE(pci, oxygen_ids);
@@ -130,27 +139,35 @@ MODULE_DEVICE_TABLE(pci, oxygen_ids);
130139
#define GPIO_CLARO_DIG_COAX 0x0040
131140
#define GPIO_CLARO_HP 0x0100
132141

142+
#define GPIO_ECLARO_CS4362A_NRESET 0x0001 /* GPIO 0: CS4362A RESET# (active-low) */
143+
#define GPIO_ECLARO_FRONT_ENABLE 0x0020 /* GPIO 5: front output stage enable */
144+
145+
/* CS4362A SPI: 3-byte frame [0x30, reg, value] on CE1, 1280 ns/bit clock */
146+
#define ECLARO_CS4362A_SPI_CONTROL \
147+
(OXYGEN_SPI_TRIGGER | OXYGEN_SPI_DATA_LENGTH_3 | \
148+
OXYGEN_SPI_CLOCK_1280 | (1 << OXYGEN_SPI_CODEC_SHIFT) | \
149+
OXYGEN_SPI_CEN_LATCH_CLOCK_HI)
150+
133151
struct generic_data {
134152
unsigned int dacs;
153+
u8 spi_map[4];
154+
u16 spi_prefix[4];
135155
u8 ak4396_regs[4][5];
156+
u8 cs4362a_regs[15];
136157
u16 wm8785_regs[3];
137158
};
138159

139160
static void ak4396_write(struct oxygen *chip, unsigned int codec,
140161
u8 reg, u8 value)
141162
{
142-
/* maps ALSA channel pair number to SPI output */
143-
static const u8 codec_spi_map[4] = {
144-
0, 1, 2, 4
145-
};
146163
struct generic_data *data = chip->model_data;
147164

148165
oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
149166
OXYGEN_SPI_DATA_LENGTH_2 |
150167
OXYGEN_SPI_CLOCK_160 |
151-
(codec_spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
168+
(data->spi_map[codec] << OXYGEN_SPI_CODEC_SHIFT) |
152169
OXYGEN_SPI_CEN_LATCH_CLOCK_HI,
153-
AK4396_WRITE | (reg << 8) | value);
170+
data->spi_prefix[codec] | (reg << 8) | value);
154171
data->ak4396_regs[codec][reg] = value;
155172
}
156173

@@ -163,6 +180,51 @@ static void ak4396_write_cached(struct oxygen *chip, unsigned int codec,
163180
ak4396_write(chip, codec, reg, value);
164181
}
165182

183+
static void eclaro_cs4362a_write(struct oxygen *chip, u8 reg, u8 value)
184+
{
185+
struct generic_data *data = chip->model_data;
186+
int err;
187+
188+
if (reg < ARRAY_SIZE(data->cs4362a_regs))
189+
data->cs4362a_regs[reg] = value;
190+
191+
err = oxygen_write_spi(chip, ECLARO_CS4362A_SPI_CONTROL,
192+
(0x30u << 16) | ((u32)reg << 8) | value);
193+
if (err)
194+
dev_err(chip->card->dev,
195+
"CS4362A SPI timeout: reg=0x%02x val=0x%02x\n",
196+
reg, value);
197+
}
198+
199+
static void eclaro_cs4362a_write_cached(struct oxygen *chip, u8 reg, u8 value)
200+
{
201+
struct generic_data *data = chip->model_data;
202+
203+
if (value != data->cs4362a_regs[reg])
204+
eclaro_cs4362a_write(chip, reg, value);
205+
}
206+
207+
static void eclaro_cs4362a_registers_init(struct oxygen *chip)
208+
{
209+
struct generic_data *data = chip->model_data;
210+
211+
eclaro_cs4362a_write(chip, 1, CS4362A_CPEN | CS4362A_PDN);
212+
eclaro_cs4362a_write(chip, 2, CS4362A_DIF_LJUST);
213+
eclaro_cs4362a_write(chip, 3, CS4362A_SOFT_RAMP | CS4362A_AMUTE);
214+
eclaro_cs4362a_write(chip, 4, data->cs4362a_regs[4]);
215+
eclaro_cs4362a_write(chip, 5, 0);
216+
eclaro_cs4362a_write(chip, 6, data->cs4362a_regs[6]);
217+
eclaro_cs4362a_write(chip, 7, data->cs4362a_regs[7]);
218+
eclaro_cs4362a_write(chip, 8, data->cs4362a_regs[8]);
219+
eclaro_cs4362a_write(chip, 9, data->cs4362a_regs[9]);
220+
eclaro_cs4362a_write(chip, 10, data->cs4362a_regs[10]);
221+
eclaro_cs4362a_write(chip, 11, data->cs4362a_regs[11]);
222+
eclaro_cs4362a_write(chip, 12, data->cs4362a_regs[12]);
223+
eclaro_cs4362a_write(chip, 13, data->cs4362a_regs[13]);
224+
eclaro_cs4362a_write(chip, 14, data->cs4362a_regs[14]);
225+
eclaro_cs4362a_write(chip, 1, CS4362A_CPEN);
226+
}
227+
166228
static void wm8785_write(struct oxygen *chip, u8 reg, unsigned int value)
167229
{
168230
struct generic_data *data = chip->model_data;
@@ -199,8 +261,13 @@ static void ak4396_registers_init(struct oxygen *chip)
199261
static void ak4396_init(struct oxygen *chip)
200262
{
201263
struct generic_data *data = chip->model_data;
264+
static const u8 default_spi_map[4] = { 0, 1, 2, 4 };
265+
unsigned int i;
202266

203267
data->dacs = chip->model.dac_channels_pcm / 2;
268+
memcpy(data->spi_map, default_spi_map, sizeof(default_spi_map));
269+
for (i = 0; i < 4; ++i)
270+
data->spi_prefix[i] = AK4396_WRITE;
204271
data->ak4396_regs[0][AK4396_CONTROL_2] =
205272
AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL;
206273
ak4396_registers_init(chip);
@@ -322,6 +389,102 @@ static void claro_resume(struct oxygen *chip)
322389
claro_enable_hp(chip);
323390
}
324391

392+
#define GPIO_CS5361_M_MASK 0x000c
393+
#define GPIO_CS5361_M_SINGLE 0x0000
394+
#define GPIO_CS5361_M_DOUBLE 0x0004
395+
#define GPIO_CS5361_M_QUAD 0x0008
396+
397+
static void cs5361_init(struct oxygen *chip)
398+
{
399+
oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CS5361_M_MASK);
400+
oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
401+
GPIO_CS5361_M_SINGLE, GPIO_CS5361_M_MASK);
402+
}
403+
404+
static void set_cs5361_params(struct oxygen *chip,
405+
struct snd_pcm_hw_params *params)
406+
{
407+
unsigned int value;
408+
409+
if (params_rate(params) <= 54000)
410+
value = GPIO_CS5361_M_SINGLE;
411+
else if (params_rate(params) <= 108000)
412+
value = GPIO_CS5361_M_DOUBLE;
413+
else
414+
value = GPIO_CS5361_M_QUAD;
415+
oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
416+
value, GPIO_CS5361_M_MASK);
417+
}
418+
419+
static void eclaro_init(struct oxygen *chip)
420+
{
421+
struct generic_data *data = chip->model_data;
422+
423+
oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, GPIO_CLARO_DIG_COAX);
424+
oxygen_clear_bits16(chip, OXYGEN_GPIO_DATA, GPIO_CLARO_DIG_COAX);
425+
426+
/* Single AK4396VF on SPI CE0/CA=00 handles front L/R */
427+
data->dacs = 1;
428+
data->spi_map[0] = 0;
429+
data->spi_prefix[0] = AK4396_WRITE;
430+
data->ak4396_regs[0][AK4396_CONTROL_2] =
431+
AK4396_SMUTE | AK4396_DEM_OFF | AK4396_DFS_NORMAL;
432+
433+
ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS);
434+
ak4396_write(chip, 0, AK4396_CONTROL_2,
435+
data->ak4396_regs[0][AK4396_CONTROL_2]);
436+
ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM);
437+
ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2);
438+
ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2);
439+
ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN);
440+
441+
/* CS4362A (SPI CE1): surround/center-LFE/side L/R.
442+
* GPIO 0 (RESET#, active-low) and GPIO 5 (front output enable) must
443+
* be driven high. GPIOs 1 and 7 are outputs driven high.
444+
*/
445+
oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL, 0x00a3);
446+
oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, 0x00a3);
447+
usleep_range(1000, 2000);
448+
449+
data->cs4362a_regs[4] = CS4362A_RMP_DN | CS4362A_DEM_NONE;
450+
data->cs4362a_regs[6] = CS4362A_FM_SINGLE |
451+
CS4362A_ATAPI_B_R | CS4362A_ATAPI_A_L;
452+
data->cs4362a_regs[7] = CS4362A_MUTE;
453+
data->cs4362a_regs[9] = data->cs4362a_regs[6];
454+
data->cs4362a_regs[12] = data->cs4362a_regs[6];
455+
456+
eclaro_cs4362a_registers_init(chip);
457+
458+
snd_component_add(chip->card, "AK4396");
459+
snd_component_add(chip->card, "CS4362A");
460+
cs5361_init(chip);
461+
claro_enable_hp(chip);
462+
snd_component_add(chip->card, "CS5361");
463+
}
464+
465+
static void eclaro_resume(struct oxygen *chip)
466+
{
467+
struct generic_data *data = chip->model_data;
468+
469+
oxygen_set_bits16(chip, OXYGEN_GPIO_CONTROL,
470+
GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE);
471+
oxygen_set_bits16(chip, OXYGEN_GPIO_DATA,
472+
GPIO_ECLARO_CS4362A_NRESET | GPIO_ECLARO_FRONT_ENABLE);
473+
474+
/* AK4396 chip 0 */
475+
ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN);
476+
ak4396_write(chip, 0, AK4396_CONTROL_2,
477+
data->ak4396_regs[0][AK4396_CONTROL_2]);
478+
ak4396_write(chip, 0, AK4396_CONTROL_3, AK4396_PCM);
479+
ak4396_write(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2);
480+
ak4396_write(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2);
481+
482+
eclaro_cs4362a_registers_init(chip);
483+
484+
cs5361_init(chip);
485+
claro_enable_hp(chip);
486+
}
487+
325488
static void stereo_resume(struct oxygen *chip)
326489
{
327490
ak4396_registers_init(chip);
@@ -355,6 +518,76 @@ static void set_ak4396_params(struct oxygen *chip,
355518
}
356519
}
357520

521+
static void eclaro_set_dac_params(struct oxygen *chip,
522+
struct snd_pcm_hw_params *params)
523+
{
524+
struct generic_data *data = chip->model_data;
525+
u8 ak_value, cs_fm;
526+
527+
ak_value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_DFS_MASK;
528+
if (params_rate(params) <= 54000) {
529+
ak_value |= AK4396_DFS_NORMAL;
530+
cs_fm = CS4362A_FM_SINGLE;
531+
} else if (params_rate(params) <= 108000) {
532+
ak_value |= AK4396_DFS_DOUBLE;
533+
cs_fm = CS4362A_FM_DOUBLE;
534+
} else {
535+
ak_value |= AK4396_DFS_QUAD;
536+
cs_fm = CS4362A_FM_QUAD;
537+
}
538+
539+
usleep_range(1000, 2000);
540+
541+
if (ak_value != data->ak4396_regs[0][AK4396_CONTROL_2]) {
542+
ak4396_write(chip, 0, AK4396_CONTROL_1, AK4396_DIF_24_MSB | AK4396_ACKS);
543+
ak4396_write(chip, 0, AK4396_CONTROL_2, ak_value);
544+
ak4396_write(chip, 0, AK4396_CONTROL_1,
545+
AK4396_DIF_24_MSB | AK4396_ACKS | AK4396_RSTN);
546+
data->ak4396_regs[0][AK4396_CONTROL_2] = ak_value;
547+
}
548+
549+
/* Update CS4362A FM mode for all three DAC pairs */
550+
cs_fm |= data->cs4362a_regs[6] & ~CS4362A_FM_MASK;
551+
eclaro_cs4362a_write_cached(chip, 6, cs_fm);
552+
eclaro_cs4362a_write_cached(chip, 12, cs_fm);
553+
cs_fm &= CS4362A_FM_MASK;
554+
cs_fm |= data->cs4362a_regs[9] & ~CS4362A_FM_MASK;
555+
eclaro_cs4362a_write_cached(chip, 9, cs_fm);
556+
}
557+
558+
static void update_eclaro_volume(struct oxygen *chip)
559+
{
560+
u8 mute = chip->dac_mute ? CS4362A_MUTE : 0;
561+
562+
ak4396_write_cached(chip, 0, AK4396_LCH_ATT, chip->dac_volume[0] * 2);
563+
ak4396_write_cached(chip, 0, AK4396_RCH_ATT, chip->dac_volume[1] * 2);
564+
565+
/* CS4362A attenuation is inverse: 0 = 0 dB, 127 = max attenuation.
566+
* Pair 1 (regs 7/8) is wired to the side outputs (ALSA ch 6/7);
567+
* pair 3 (regs 13/14) is wired to the rear outputs (ALSA ch 2/3).
568+
*/
569+
eclaro_cs4362a_write_cached(chip, 7, mute | (127 - chip->dac_volume[6]));
570+
eclaro_cs4362a_write_cached(chip, 8, mute | (127 - chip->dac_volume[7]));
571+
eclaro_cs4362a_write_cached(chip, 10, mute | (127 - chip->dac_volume[4]));
572+
eclaro_cs4362a_write_cached(chip, 11, mute | (127 - chip->dac_volume[5]));
573+
eclaro_cs4362a_write_cached(chip, 13, mute | (127 - chip->dac_volume[2]));
574+
eclaro_cs4362a_write_cached(chip, 14, mute | (127 - chip->dac_volume[3]));
575+
}
576+
577+
static void update_eclaro_mute(struct oxygen *chip)
578+
{
579+
struct generic_data *data = chip->model_data;
580+
u8 value;
581+
582+
value = data->ak4396_regs[0][AK4396_CONTROL_2] & ~AK4396_SMUTE;
583+
if (chip->dac_mute)
584+
value |= AK4396_SMUTE;
585+
ak4396_write_cached(chip, 0, AK4396_CONTROL_2, value);
586+
587+
/* Re-apply volume+mute to CS4362A so the mute bit is set correctly */
588+
update_eclaro_volume(chip);
589+
}
590+
358591
static void update_ak4396_volume(struct oxygen *chip)
359592
{
360593
struct generic_data *data = chip->model_data;
@@ -702,6 +935,8 @@ static void dump_oxygen_registers(struct oxygen *chip,
702935
}
703936

704937
static const DECLARE_TLV_DB_LINEAR(ak4396_db_scale, TLV_DB_GAIN_MUTE, 0);
938+
/* CS4362A: 0.5 dB/step, raw=127 -> 0 dB, raw=0 -> -63.5 dB */
939+
static const DECLARE_TLV_DB_SCALE(eclaro_db_scale, -6350, 50, 0);
705940

706941
static const struct oxygen_model model_generic = {
707942
.shortname = "C-Media CMI8788",
@@ -745,6 +980,7 @@ static int get_oxygen_model(struct oxygen *chip,
745980
[MODEL_MERIDIAN_2G] = "AuzenTech X-Meridian 2G",
746981
[MODEL_CLARO] = "HT-Omega Claro",
747982
[MODEL_CLARO_HALO] = "HT-Omega Claro halo",
983+
[MODEL_ECLARO] = "HT-Omega eClaro",
748984
[MODEL_FANTASIA] = "TempoTec HiFier Fantasia",
749985
[MODEL_SERENADE] = "TempoTec HiFier Serenade",
750986
[MODEL_HG2PCI] = "CMI8787-HG2PCI",
@@ -788,6 +1024,28 @@ static int get_oxygen_model(struct oxygen *chip,
7881024
CAPTURE_0_FROM_I2S_2 |
7891025
CAPTURE_1_FROM_SPDIF;
7901026
break;
1027+
case MODEL_ECLARO:
1028+
chip->model.init = eclaro_init;
1029+
chip->model.mixer_init = generic_mixer_init;
1030+
chip->model.cleanup = claro_cleanup;
1031+
chip->model.suspend = claro_suspend;
1032+
chip->model.resume = eclaro_resume;
1033+
chip->model.set_dac_params = eclaro_set_dac_params;
1034+
chip->model.set_adc_params = set_cs5361_params;
1035+
chip->model.update_dac_volume = update_eclaro_volume;
1036+
chip->model.update_dac_mute = update_eclaro_mute;
1037+
chip->model.dump_registers = dump_ak4396_registers;
1038+
chip->model.device_config = PLAYBACK_0_TO_I2S |
1039+
PLAYBACK_1_TO_SPDIF |
1040+
CAPTURE_0_FROM_I2S_2 |
1041+
CAPTURE_1_FROM_SPDIF;
1042+
chip->model.function_flags = OXYGEN_FUNCTION_SPI |
1043+
OXYGEN_FUNCTION_ENABLE_SPI_4_5;
1044+
chip->model.dac_mclks = OXYGEN_MCLKS(256, 128, 128);
1045+
chip->model.dac_volume_min = 0;
1046+
chip->model.dac_volume_max = 127;
1047+
chip->model.dac_tlv = eclaro_db_scale;
1048+
break;
7911049
case MODEL_FANTASIA:
7921050
case MODEL_SERENADE:
7931051
case MODEL_2CH_OUTPUT:

0 commit comments

Comments
 (0)