Sorry for the very cryptic subject and plase feel free to redirect me if this question does not fit in here. So here it is: I have a LogiLink USB 5.1/7.1 sound card. It is based on the CM6206 chip. It has an S/PDIF output that can be set to output the full signal or any of frontlr/center-sub/surroundlr etc. I would like it to output the surround left/right signal.
To do this you need to set some values in REG5:
Now, the Linux kernel already has some stuff for that very chipset in a file called “quirks.c” so I guess I could add whatever I want to that file and recompile the kernel, but I guess there must be an easier way? Write a small program that does the same? Ie something along the lines of:
/*
* Muck with CM6206 internal registers
* Based on Set LED - program to control a USB LED device
* from user space using libusb
* and code from: https://github.com/torvalds/linux/blob/master/sound/usb/quirks.c
* Copyright (C) 2004
* Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can
* redistribute it and/or modify it under the terms
* of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the
* License.
*
*/
// https://www.linuxjournal.com/article/7466
#include <stdio.h>
#include <string.h>
//#include "/usr/include/linux/usb/ch9.h"
#include <linux/types.h>
#include <usb.h>
#define NONE 0x00
#define BLUE 0x04
#define RED 0x02
#define GREEN 0x01
// 046d:c044
//#define LED_VENDOR_ID 0x0fc5
//#define LED_PRODUCT_ID 0x1223
//#define LED_VENDOR_ID 0x046d
//#define LED_PRODUCT_ID 0xc044
// 0d8c:0102
#define LED_VENDOR_ID 0x0d8c
#define LED_PRODUCT_ID 0x0102
#define BIT(nr) (1UL << (nr))
/*
* CM6206 registers from the CM6206 datasheet rev 2.1
*/
#define CM6206_REG0_DMA_MASTER BIT(15)
#define CM6206_REG0_SPDIFO_RATE_48K (2 << 12)
#define CM6206_REG0_SPDIFO_RATE_96K (7 << 12)
/* Bit 4 thru 11 is the S/PDIF category code */
#define CM6206_REG0_SPDIFO_CAT_CODE_GENERAL (0 << 4)
#define CM6206_REG0_SPDIFO_EMPHASIS_CD BIT(3)
#define CM6206_REG0_SPDIFO_COPYRIGHT_NA BIT(2)
#define CM6206_REG0_SPDIFO_NON_AUDIO BIT(1)
#define CM6206_REG0_SPDIFO_PRO_FORMAT BIT(0)
#define CM6206_REG1_TEST_SEL_CLK BIT(14)
#define CM6206_REG1_PLLBIN_EN BIT(13)
#define CM6206_REG1_SOFT_MUTE_EN BIT(12)
#define CM6206_REG1_GPIO4_OUT BIT(11)
#define CM6206_REG1_GPIO4_OE BIT(10)
#define CM6206_REG1_GPIO3_OUT BIT(9)
#define CM6206_REG1_GPIO3_OE BIT(8)
#define CM6206_REG1_GPIO2_OUT BIT(7)
#define CM6206_REG1_GPIO2_OE BIT(6)
#define CM6206_REG1_GPIO1_OUT BIT(5)
#define CM6206_REG1_GPIO1_OE BIT(4)
#define CM6206_REG1_SPDIFO_INVALID BIT(3)
#define CM6206_REG1_SPDIF_LOOP_EN BIT(2)
#define CM6206_REG1_SPDIFO_DIS BIT(1)
#define CM6206_REG1_SPDIFI_MIX BIT(0)
#define CM6206_REG2_DRIVER_ON BIT(15)
#define CM6206_REG2_HEADP_SEL_SIDE_CHANNELS (0 << 13)
#define CM6206_REG2_HEADP_SEL_SURROUND_CHANNELS (1 << 13)
#define CM6206_REG2_HEADP_SEL_CENTER_SUBW (2 << 13)
#define CM6206_REG2_HEADP_SEL_FRONT_CHANNELS (3 << 13)
#define CM6206_REG2_MUTE_HEADPHONE_RIGHT BIT(12)
#define CM6206_REG2_MUTE_HEADPHONE_LEFT BIT(11)
#define CM6206_REG2_MUTE_REAR_SURROUND_RIGHT BIT(10)
#define CM6206_REG2_MUTE_REAR_SURROUND_LEFT BIT(9)
#define CM6206_REG2_MUTE_SIDE_SURROUND_RIGHT BIT(8)
#define CM6206_REG2_MUTE_SIDE_SURROUND_LEFT BIT(7)
#define CM6206_REG2_MUTE_SUBWOOFER BIT(6)
#define CM6206_REG2_MUTE_CENTER BIT(5)
#define CM6206_REG2_MUTE_RIGHT_FRONT BIT(3)
#define CM6206_REG2_MUTE_LEFT_FRONT BIT(3)
#define CM6206_REG2_EN_BTL BIT(2)
#define CM6206_REG2_MCUCLKSEL_1_5_MHZ (0)
#define CM6206_REG2_MCUCLKSEL_3_MHZ (1)
#define CM6206_REG2_MCUCLKSEL_6_MHZ (2)
#define CM6206_REG2_MCUCLKSEL_12_MHZ (3)
/* Bit 11..13 sets the sensitivity to FLY tuner volume control VP/VD signal */
#define CM6206_REG3_FLYSPEED_DEFAULT (2 << 11)
#define CM6206_REG3_VRAP25EN BIT(10)
#define CM6206_REG3_MSEL1 BIT(9)
#define CM6206_REG3_SPDIFI_RATE_44_1K BIT(0 << 7)
#define CM6206_REG3_SPDIFI_RATE_48K BIT(2 << 7)
#define CM6206_REG3_SPDIFI_RATE_32K BIT(3 << 7)
#define CM6206_REG3_PINSEL BIT(6)
#define CM6206_REG3_FOE BIT(5)
#define CM6206_REG3_ROE BIT(4)
#define CM6206_REG3_CBOE BIT(3)
#define CM6206_REG3_LOSE BIT(2)
#define CM6206_REG3_HPOE BIT(1)
#define CM6206_REG3_SPDIFI_CANREC BIT(0)
#define CM6206_REG5_DA_RSTN BIT(13)
#define CM6206_REG5_AD_RSTN BIT(12)
#define CM6206_REG5_SPDIFO_AD2SPDO BIT(12)
#define CM6206_REG5_SPDIFO_SEL_FRONT (0 << 9)
#define CM6206_REG5_SPDIFO_SEL_SIDE_SUR (1 << 9)
#define CM6206_REG5_SPDIFO_SEL_CEN_LFE (2 << 9)
#define CM6206_REG5_SPDIFO_SEL_REAR_SUR (3 << 9)
#define CM6206_REG5_CODECM BIT(8)
#define CM6206_REG5_EN_HPF BIT(7)
#define CM6206_REG5_T_SEL_DSDA4 BIT(6)
#define CM6206_REG5_T_SEL_DSDA3 BIT(5)
#define CM6206_REG5_T_SEL_DSDA2 BIT(4)
#define CM6206_REG5_T_SEL_DSDA1 BIT(3)
#define CM6206_REG5_T_SEL_DSDAD_NORMAL 0
#define CM6206_REG5_T_SEL_DSDAD_FRONT 4
#define CM6206_REG5_T_SEL_DSDAD_S_SURROUND 5
#define CM6206_REG5_T_SEL_DSDAD_CEN_LFE 6
#define CM6206_REG5_T_SEL_DSDAD_R_SURROUND 7
#define USB_DIR_OUT 0 /* to device */
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_TYPE_CLASS (0x01 << 5)
#define USB_RECIP_ENDPOINT 0x02
static void change_color
(struct usb_dev_handle *handle,
unsigned char color)
{
char *dummy;
usb_control_msg(handle,
0x000000c8,
0x00000012,
(0x02 * 0x100) + 0x0a,
0xff & (~color),
dummy,
0x00000008,
5000);
}
static struct usb_device *device_init(void)
{
struct usb_bus *usb_bus;
struct usb_device *dev;
usb_init();
usb_find_busses();
usb_find_devices();
for (usb_bus = usb_busses;
usb_bus;
usb_bus = usb_bus->next) {
for (dev = usb_bus->devices;
dev;
dev = dev->next) {
if ((dev->descriptor.idVendor
== LED_VENDOR_ID) &&
(dev->descriptor.idProduct
== LED_PRODUCT_ID))
return dev;
}
}
return NULL;
}
/*
* C-Media CM106/CM106+ have four 16-bit internal registers that are nicely
* documented in the device's data sheet.
*/
static int snd_usb_cm106_write_int_reg(usb_dev_handle *handle, int reg, __u16 value)
{
char buf[4];
buf[0] = 0x20;
buf[1] = value & 0xff;
buf[2] = (value >> 8) & 0xff;
buf[3] = reg;
//https://sourceforge.net/p/libusb-win32/wiki/Documentation/
return usb_control_msg(handle, // handle
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, // Request type
USB_REQ_SET_CONFIGURATION, // Request
0, // value
0, // index
buf, // payload
4,
5000); // length
}
int main(int argc, char **argv)
{
struct usb_device *usb_dev;
struct usb_dev_handle *usb_handle;
int retval = 1;
int i, rc;
unsigned char color = NONE;
int val;
usb_set_debug(4);
usb_dev = device_init();
if (usb_dev == NULL) {
fprintf(stderr, "Device not found\n");
goto exit;
}
usb_handle = usb_open(usb_dev);
if (usb_handle == NULL) {
fprintf(stderr, "Cannot open\n");
goto exit;
}
usb_handle = usb_open(usb_dev);
if (usb_handle == NULL) {
fprintf(stderr,
"Not able to claim the USB device\n");
goto exit;
}
/* REG5: de-assert AD/DA reset signals */
val = CM6206_REG5_DA_RSTN |
CM6206_REG5_AD_RSTN | CM6206_REG5_SPDIFO_SEL_CEN_LFE;
rc = snd_usb_cm106_write_int_reg(usb_handle, 5, val);
fprintf(stderr,
"Wrote %X to register 5, rc = %d\n", val, rc);
retval = 0;
exit:
usb_close(usb_handle);
return retval;
}
This may be controllable via alsamixer. Did you try this first?
You mean here? I just see the various volumes, not a selection for S/PDIF out?
lqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq AlsaMixer v1.1.3 qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqk
x Card: USB Sound Device F1: Help x
x Chip: USB Mixer F2: System information x
x View: F3:[Playback] F4: Capture F5: All F6: Select sound card x
x Item: Speaker [dB gain: -20.06, -20.06] Esc: Exit x
x x
x lqqk lqqk lqqk lqqk lqqk lqqk lqqk x
x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x x x x x x x x x
x x x x x x x x x x x xaax xaax x
x x x x x x x x x x x xaax xaax x
x x x x x x x x x x x xaax xaax x
x xaax xaax xaax xaax xaax xaax xaax x
x xaax xaax xaax xaax xaax xaax xaax x
x xaax xaax xaax xaax xaax xaax xaax x
x tqqu tqqu tqqu tqqu tqqu Mic tqqu tqqu x
x xOOx xOOx xOOx xOOx xOOx xMMx xMMx x
x mqqj mqqj mqqj mqqj mqqj mqqj mqqj x
x 29<>29 29<>29 29 29 29<>29 64<>64 64<>64 x
x <Speaker >Speaker Speaker Speaker Speaker PCM Capt Line Mic x
x Front Rear Center Woofer Side x
mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj
No. You have the right idea in your OP - you need to get under the skin of the soundcard driver.
OK. The snippet of code above seems to work, but has an unfortunate side-effect: The normal left/right sound goes away unless SPDIFOUT is 0. If I play a DTS test stream all works normally, even though I have not been able to verify that S/PDIF now holds the surround channels. Will do so as soon as I get my hands on a TOSLINK receiver.
Cleaned up code:
/ *
* Set SPDIFOUT - program to control the SPDIF-output on a CM6206 chip
*
* Based on: Set LED - program to control a USB LED device
* from user space using libusb (Writing a Real Driver—In User Space | Linux Journal)
*
* Copyright (C) 2004
* Greg Kroah-Hartman (greg@kroah.com)
*
* This program is free software; you can
* redistribute it and/or modify it under the terms
* of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the
* License.
*
*/
// https://www.linuxjournal.com/article/7466
#include <stdio.h>
#include <string.h>
#include <linux/types.h>
#include <usb.h>
//Id's for the C-Media Electronics, Inc. CM106 Like Sound Device
// 0d8c:0102
#define LED_VENDOR_ID 0x0d8c
#define LED_PRODUCT_ID 0x0102
#define BIT(nr) (1UL << (nr))
/*
* CM6206 registers from the CM6206 datasheet rev 2.1
*/
#define CM6206_REG0_DMA_MASTER BIT(15)
#define CM6206_REG0_SPDIFO_RATE_48K (2 << 12)
#define CM6206_REG0_SPDIFO_RATE_96K (7 << 12)
/* Bit 4 thru 11 is the S/PDIF category code */
#define CM6206_REG0_SPDIFO_CAT_CODE_GENERAL (0 << 4)
#define CM6206_REG0_SPDIFO_EMPHASIS_CD BIT(3)
#define CM6206_REG0_SPDIFO_COPYRIGHT_NA BIT(2)
#define CM6206_REG0_SPDIFO_NON_AUDIO BIT(1)
#define CM6206_REG0_SPDIFO_PRO_FORMAT BIT(0)
#define CM6206_REG1_TEST_SEL_CLK BIT(14)
#define CM6206_REG1_PLLBIN_EN BIT(13)
#define CM6206_REG1_SOFT_MUTE_EN BIT(12)
#define CM6206_REG1_GPIO4_OUT BIT(11)
#define CM6206_REG1_GPIO4_OE BIT(10)
#define CM6206_REG1_GPIO3_OUT BIT(9)
#define CM6206_REG1_GPIO3_OE BIT(8)
#define CM6206_REG1_GPIO2_OUT BIT(7)
#define CM6206_REG1_GPIO2_OE BIT(6)
#define CM6206_REG1_GPIO1_OUT BIT(5)
#define CM6206_REG1_GPIO1_OE BIT(4)
#define CM6206_REG1_SPDIFO_INVALID BIT(3)
#define CM6206_REG1_SPDIF_LOOP_EN BIT(2)
#define CM6206_REG1_SPDIFO_DIS BIT(1)
#define CM6206_REG1_SPDIFI_MIX BIT(0)
#define CM6206_REG2_DRIVER_ON BIT(15)
#define CM6206_REG2_HEADP_SEL_SIDE_CHANNELS (0 << 13)
#define CM6206_REG2_HEADP_SEL_SURROUND_CHANNELS (1 << 13)
#define CM6206_REG2_HEADP_SEL_CENTER_SUBW (2 << 13)
#define CM6206_REG2_HEADP_SEL_FRONT_CHANNELS (3 << 13)
#define CM6206_REG2_MUTE_HEADPHONE_RIGHT BIT(12)
#define CM6206_REG2_MUTE_HEADPHONE_LEFT BIT(11)
#define CM6206_REG2_MUTE_REAR_SURROUND_RIGHT BIT(10)
#define CM6206_REG2_MUTE_REAR_SURROUND_LEFT BIT(9)
#define CM6206_REG2_MUTE_SIDE_SURROUND_RIGHT BIT(8)
#define CM6206_REG2_MUTE_SIDE_SURROUND_LEFT BIT(7)
#define CM6206_REG2_MUTE_SUBWOOFER BIT(6)
#define CM6206_REG2_MUTE_CENTER BIT(5)
#define CM6206_REG2_MUTE_RIGHT_FRONT BIT(4)
#define CM6206_REG2_MUTE_LEFT_FRONT BIT(3)
#define CM6206_REG2_EN_BTL BIT(2)
#define CM6206_REG2_MCUCLKSEL_1_5_MHZ (0)
#define CM6206_REG2_MCUCLKSEL_3_MHZ (1)
#define CM6206_REG2_MCUCLKSEL_6_MHZ (2)
#define CM6206_REG2_MCUCLKSEL_12_MHZ (3)
/* Bit 11..13 sets the sensitivity to FLY tuner volume control VP/VD signal */
#define CM6206_REG3_FLYSPEED_DEFAULT (2 << 11)
#define CM6206_REG3_VRAP25EN BIT(10)
#define CM6206_REG3_MSEL1 BIT(9)
#define CM6206_REG3_SPDIFI_RATE_44_1K BIT(0 << 7)
#define CM6206_REG3_SPDIFI_RATE_48K BIT(2 << 7)
#define CM6206_REG3_SPDIFI_RATE_32K BIT(3 << 7)
#define CM6206_REG3_PINSEL BIT(6)
#define CM6206_REG3_FOE BIT(5)
#define CM6206_REG3_ROE BIT(4)
#define CM6206_REG3_CBOE BIT(3)
#define CM6206_REG3_LOSE BIT(2)
#define CM6206_REG3_HPOE BIT(1)
#define CM6206_REG3_SPDIFI_CANREC BIT(0)
#define CM6206_REG5_DA_RSTN BIT(13)
#define CM6206_REG5_AD_RSTN BIT(12)
#define CM6206_REG5_SPDIFO_AD2SPDO BIT(12)
#define CM6206_REG5_SPDIFO_SEL_FRONT (0 << 9)
#define CM6206_REG5_SPDIFO_SEL_SIDE_SUR (1 << 9)
#define CM6206_REG5_SPDIFO_SEL_CEN_LFE (2 << 9)
#define CM6206_REG5_SPDIFO_SEL_REAR_SUR (3 << 9)
#define CM6206_REG5_CODECM BIT(8)
#define CM6206_REG5_EN_HPF BIT(7)
#define CM6206_REG5_T_SEL_DSDA4 BIT(6)
#define CM6206_REG5_T_SEL_DSDA3 BIT(5)
#define CM6206_REG5_T_SEL_DSDA2 BIT(4)
#define CM6206_REG5_T_SEL_DSDA1 BIT(3)
#define CM6206_REG5_T_SEL_DSDAD_NORMAL 0
#define CM6206_REG5_T_SEL_DSDAD_FRONT 4
#define CM6206_REG5_T_SEL_DSDAD_S_SURROUND 5
#define CM6206_REG5_T_SEL_DSDAD_CEN_LFE 6
#define CM6206_REG5_T_SEL_DSDAD_R_SURROUND 7
// Sundry USB defines
#define USB_DIR_OUT 0 /* to device */
#define USB_REQ_SET_CONFIGURATION 0x09
#define USB_TYPE_CLASS (0x01 << 5)
#define USB_RECIP_ENDPOINT 0x02
static struct usb_device *device_init(void)
{
struct usb_bus *usb_bus;
struct usb_device *dev;
usb_init();
usb_find_busses();
usb_find_devices();
for (usb_bus = usb_busses;
usb_bus;
usb_bus = usb_bus->next) {
for (dev = usb_bus->devices;
dev;
dev = dev->next) {
if ((dev->descriptor.idVendor
== LED_VENDOR_ID) &&
(dev->descriptor.idProduct
== LED_PRODUCT_ID))
return dev;
}
}
return NULL;
}
/*
* C-Media CM106/CM106+ have four 16-bit internal registers that are nicely
* documented in the device's data sheet.
*/
static int snd_usb_cm106_write_int_reg(usb_dev_handle *handle, int reg, __u16 value)
{
char buf[4];
buf[0] = 0x20;
buf[1] = value & 0xff;
buf[2] = (value >> 8) & 0xff;
buf[3] = reg;
//https://sourceforge.net/p/libusb-win32/wiki/Documentation/
return usb_control_msg(handle, // handle
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, // Request type
USB_REQ_SET_CONFIGURATION, // Request
0, // value
0, // index
buf, // payload
4,
5000); // length
}
static int snd_usb_cm106_read_int_reg(usb_dev_handle *handle, int reg, __u16 value)
{
char buf[4];
buf[0] = 0x30;
buf[1] = value & 0xff;
buf[2] = (value >> 8) & 0xff;
buf[3] = reg;
//https://sourceforge.net/p/libusb-win32/wiki/Documentation/
return usb_control_msg(handle, // handle
USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT, // Request type
USB_REQ_SET_CONFIGURATION, // Request
0, // value
0, // index
buf, // payload
4,
5000); // length
}
int main(int argc, char **argv)
{
struct usb_device *usb_dev;
struct usb_dev_handle *usb_handle;
int retval = 1;
int i, rc, sel = 0;
int val;
char b[4];
usb_set_debug(4);
if(argc < 2)
{
fprintf(stderr, "Set CM6206 SPDIFOUT. Usage: setspdif n, where n is one of 0 - front, 1 - side surround, 2 - center, 3 - rear surround\n");
return 0;
}
usb_dev = device_init();
if (usb_dev == NULL) {
fprintf(stderr, "CM6206 Device not found\n");
goto exit;
}
usb_handle = usb_open(usb_dev);
if (usb_handle == NULL) {
fprintf(stderr, "Cannot open CM6206\n");
goto exit;
}
usb_handle = usb_open(usb_dev);
if (usb_handle == NULL) {
fprintf(stderr,
"Not able to claim the CM6206 USB device\n");
goto exit;
}
if( argc == 2) sel = atoi(argv[1]);
sel = sel << 9;
fprintf(stderr, "Selector is %X\n", sel);
/* REG5: de-assert AD/DA reset signals */
val = CM6206_REG5_DA_RSTN | CM6206_REG5_AD_RSTN | sel;// | CM6206_REG5_SPDIFO_SEL_SIDE_SUR; // | CM6206_REG5_SPDIFO_SEL_CEN_LFE;
rc = snd_usb_cm106_write_int_reg(usb_handle, 5, val);
fprintf(stderr,
"Wrote %X to register 5, rc = %d\n", val, rc);
retval = 0;
exit:
usb_close(usb_handle);
return retval;
}
1 Like
OK. Tested an aplay.conf like this:
defaults.pcm.card 1
defaults.pcm.device 0
pcm.ch40dup {
type route
slave.pcm surround40
slave.channels 4
ttable.0.0 1
ttable.1.1 1
ttable.0.2 1
ttable.1.3 1
}
pcm.ch51dup {
type route
slave.pcm surround51
slave.channels 6
ttable.0.0 1
ttable.1.1 1
ttable.0.2 1
ttable.1.3 1
ttable.0.4 0.5
ttable.1.4 0.5
}
This gives me L/R on stereo material, but I have to change output device which is a drag.
This seems to fix it: I now have signal on both the Vero4 PCM out and on the USB card:
pcm.both {
type route;
slave.pcm {
type multi;
slaves.a.pcm "plughw:0,0"
slaves.b.pcm "plughw:1,0"
slaves.a.channels 2;
slaves.b.channels 6;
bindings.0.slave a;
bindings.0.channel 0;
bindings.1.slave a;
bindings.1.channel 1;
bindings.2.slave b;
bindings.2.channel 0;
bindings.3.slave b;
bindings.3.channel 1;
bindings.4.slave b;
bindings.4.channel 4;
bindings.5.slave b;
bindings.5.channel 5;
bindings.6.slave b;
bindings.6.channel 2;
bindings.7.slave b;
bindings.7.channel 3;
}
ttable.0.0 1;
ttable.1.1 1;
ttable.0.2 1;
ttable.1.3 1;
ttable.2.4 1;
ttable.3.5 1;
ttable.4.6 1;
ttable.5.7 1;
}
pcm.test {
type plug
slave.pcm "both"
}
ctl.test {
type hw
card SB
}
1 Like