LoginSignup
1
0

More than 1 year has passed since last update.

CM6206の設定プログラム

Last updated at Posted at 2022-02-12

C-MediaのCM6206はネットでデーターシートが手に入るので、ながめていたらUSBのHIDで設定できるようです。

以前BTOさんのUSB赤外線リモコンのHIDな転送プログラムを作ったので、それを下敷きにいろいろ試してみました。

% sudo usbconfig -d ugen1.3 dump_all_desc
ugen1.3: <vendor 0x0d8c USB Sound Device> at usbus1, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (500mA)

  bLength = 0x0012 
  bDescriptorType = 0x0001 
  bcdUSB = 0x0110 
  bDeviceClass = 0x0000  <Probed by interface class>
  bDeviceSubClass = 0x0000 
  bDeviceProtocol = 0x0000 
  bMaxPacketSize0 = 0x0008 
  idVendor = 0x0d8c 
  idProduct = 0x0102 
  bcdDevice = 0x0010 
  iManufacturer = 0x0000  <no string>
  iProduct = 0x0002  <USB Sound Device        >
  iSerialNumber = 0x0000  <no string>
  bNumConfigurations = 0x0001 

省略

    Interface 3
      bLength = 0x0009 
      bDescriptorType = 0x0004 
      bInterfaceNumber = 0x0003 
      bAlternateSetting = 0x0000 
      bNumEndpoints = 0x0001 
      bInterfaceClass = 0x0003  <HID device>
      bInterfaceSubClass = 0x0000 
      bInterfaceProtocol = 0x0000 
      iInterface = 0x0000  <no string>

      Additional Descriptor

      bLength = 0x09
      bDescriptorType = 0x21
      bDescriptorSubType = 0x00
       RAW dump: 
       0x00 | 0x09, 0x21, 0x00, 0x01, 0x00, 0x01, 0x22, 0x32, 
       0x08 | 0x00

     Endpoint 0
        bLength = 0x0007 
        bDescriptorType = 0x0005 
        bEndpointAddress = 0x0081  <IN>
        bmAttributes = 0x0003  <INTERRUPT>
        wMaxPacketSize = 0x0003 
        bInterval = 0x0001 
        bRefresh = 0x0000 
        bSynchAddress = 0x0000 

USB赤外線リモコンは受信送信ともインターラプト転送でおこなっていましたが、CM6206は送信はコントロール転送で、受信はインターラプト転送とデーターシートに書いてありました。

To access registers via HID interface, users should issue a “Set Output Report” HID request.

When users intend to read register / EEPROM by “Set Output Report”, the returned data will be transferred to USB host via HID input report through interrupt pipe.

コントロール転送で送るのは4バイトで、readの場合のインターラプト転送で送られてくるのは3バイトになります。

レジスタは16bitでREG0からREG5までの6つです。設定内容についてはデーターシートを確認してください。

SPDIFで送られる情報はREG0で設定されるのですが、ここの情報はC Channelと呼ばれるものと思われます。

C-Mediaのサイトにあるデーターシート(Rev. 2.3)はインターラプト転送の1バイト目のビットフィールドの説明がありません。なぜかネットで拾える古いもの(Rev. 2.1)にはあります。

image.png

一番上のインターラプトの箱からはホストへの矢印しかありません。転送はEndpoint 1のデバイスから見てOUTなので0x81で行われます。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#if defined( __FreeBSD__)
#include <libusb.h>
#else
#include <libusb-1.0/libusb.h>
#endif

#define USB_HID_SET_REPORT              0x09

#define USB_HID_REPORT_TYPE_INPUT       1
#define USB_HID_REPORT_TYPE_OUTPUT      2
#define USB_HID_REPORT_TYPE_FEATURE     3

#define USB_REQUEST_TYPE_OUT            (LIBUSB_REQUEST_TYPE_CLASS | \
        LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_OUT)

#define USB_INTERFACE                   0

#define VENDOR_ID       0x0d8c
#define PRODUCT_ID      0x0102
#define CM_EP_IN        0x81

#define MAX_SIZE 64

void close_device(libusb_context *ctx, libusb_device_handle *devh) {
  libusb_close(devh);
  libusb_exit(ctx);
}

libusb_device_handle* open_device(libusb_context *ctx) {
  struct libusb_device_handle *devh = NULL;
  libusb_device *dev;
  libusb_device **devs;

  int r = 1;
  int i = 0;
  int cnt = 0;

//  libusb_set_debug(ctx, 3);

  if ((libusb_get_device_list(ctx, &devs)) < 0) {
//    perror("no usb device found");
//    exit(1);
    return -1;
  }

  /* check every usb devices */
  while ((dev = devs[i++]) != NULL) {
    struct libusb_device_descriptor desc;
    if (libusb_get_device_descriptor(dev, &desc) < 0) {
      perror("failed to get device descriptor\n");
    }
    /* count how many device connected */
    if (desc.idVendor == VENDOR_ID && desc.idProduct == PRODUCT_ID) {
      cnt++;
    }
  }

  /* device not found */
  if (cnt == 0) {
//    fprintf(stderr, "device not connected or lack of permissions\n");
//    exit(1);
    return -1;
  }

  if (cnt > 1) {
//    fprintf(stderr, "multi device is not implemented yet\n");
//    exit(1);
    return -1;
  }


  /* open device */
  if ((devh = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID)) < 0) {
    perror("can't find device\n");
//    close_device(ctx, devh);
//    exit(1);
    return -1;
  } 

  /* detach kernel driver if attached. */
  r = libusb_kernel_driver_active(devh, 3);
  if (r == 1) {
    /* detaching kernel driver */
    r = libusb_detach_kernel_driver(devh, 3);
    if (r != 0) {
//      perror("detaching kernel driver failed");
//      exit(1);
      return -1;
    }
  }

  r = libusb_claim_interface(devh, 0);
  if (r < 0) {
//    fprintf(stderr, "claim interface failed (%d): %s\n", r, strerror(errno));
//    exit(1);
    return -1;
  }

  return devh;
}

void write_device(struct libusb_device_handle *devh, unsigned char *cmd, int len) {
  int i, r;
  uint8_t buf[MAX_SIZE];

  int size = 0;

  memset(buf, 0xff, sizeof(buf));
  for (i = 0; i < len; i++) {
    buf[i] = cmd[i];
  }
  r = libusb_control_transfer(devh, USB_REQUEST_TYPE_OUT,
                USB_HID_SET_REPORT, USB_HID_REPORT_TYPE_FEATURE, USB_INTERFACE,
                (unsigned char *)buf, len, 1000);
  if (r < 0) {
    fprintf(stderr, "libusb_control_transfer (%d): %s\n", r, strerror(errno));
    exit(1);
  }
}

int read_device(struct libusb_device_handle *devh, unsigned char *buf, int bufsize) {
  int size = 0;
  memset(buf, 0x00, bufsize);

  int r = libusb_interrupt_transfer(devh, CM_EP_IN, buf, bufsize, &size, 100);
  if (r < 0) {
    fprintf(stderr, "libusb_interrupt_transfer (%d): %s\n", r, strerror(errno));
    exit(1);
  }

  return size;
}

int cm6206_read(struct libusb_device_handle *devh, int regnum, int *value)
{
  int res;
  unsigned char buf[4] = {
            0x30,           // 0x30 = read, 0x20 = write
            0x00,           // DATAL
            0x00,           // DATAH
            regnum          // Register address
    };

  write_device(devh, buf, 4);
  res = read_device(devh, buf, 3);

  if ((buf[0] & 0xe0) != 0x20)    // No register data in the input report
    return -3;

  *value = (((uint16_t)buf[2]) << 8) | buf[1];

  return 1;
}

int main(int argc, char *argv[]) {
  libusb_context *ctx = NULL;
  int r = 1;
  int val;

  /* libusb initialize*/
  if ((r = libusb_init(&ctx)) < 0) {
    perror("libusb_init\n");
    exit(1);
  } 

  /* open device */
  libusb_device_handle *devh = open_device(ctx);

  cm6206_read(devh, 0, &val);

  printf("REG0: %04x\n", val);

  close_device(ctx, devh);

  return 0;
}

送信はされてるようなのですが、受信ができません。誰かが拾ってしまってるようです。

sys/dev/sound/usb/uaudio.cを見てみたところ、uaudioのsysctlに以下のような設定がありました。

 % sysctl hw.usb.uaudio.handle_hid
hw.usb.uaudio.handle_hid: 1

これが1だとuaudioが拾ってプログラムで拾えません。0にしてデバイスをさしなおしたら、ちゃんとデータ拾えました。

 % sudo ./cm6206
REG0: 0020

EEPROMは付いてないのですが、なぜかデータシートのデフォルト値(2000)ではありません。最初バイトオーダーが違っているのかと思ったのですが、他のレジスタは初期値が正しく読めたりするので謎です。

スイッチがついたモジュールだとスイッチを押すと、その情報が飛んでくるので区別する必要があります。

設定だけであれば、コントロールのOUTだけで処理できるので、hw.usb.uaudio.handle_hidの変更は必要ありません。

hidapiを使ったプログラムもありました。

HIDなデバイスを制御するのは上のサンプルプログラムのようにlibusbを直接使う方法や、hidapiを使う方法などがあります。実はFreeBSDにはNetBSDからポートされたlibusbhidというライブラリもありますが、使い方が分かりません。

あ、それとFreeBSD 13で新しいHIDの仕組みが入ったみたいです。こっちはFreeBSDは12で終わりなのであまり興味ないので調べてません。

hidapiはiconvを使っていて、面倒なのでmrubyのmrbgemにしてみました。

設定をしないと、SCMSが効いてMDへの録音が"Cannot Copy"になりできません。設定すると録音できるようになるので、なんらかSPDIFの情報が変わっていると思われますが、どう変わってるのか良く分かりません。

FreeBSDではOS標準で再実装したlibusbが入っています。

いろいろ調べていたところ、この機能はすでに実装済みでした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0