ALSA: oxfw: Change the way to make PCM rules/constraints
authorTakashi Sakamoto <o-takashi@sakamocchi.jp>
Mon, 8 Dec 2014 15:10:42 +0000 (00:10 +0900)
committerTakashi Iwai <tiwai@suse.de>
Wed, 10 Dec 2014 09:47:37 +0000 (10:47 +0100)
In previous commit, this driver can get to know stream formations at
each supported sampling rates. This commit uses it to make PCM
rules/constraints and obsoletes hard-coded rules/constraints.

For this purpose, this commit adds 'struct snd_oxfw_stream_formation' and
snd_oxfw_stream_parse_format() to parse data channel formation of data
block.

According to datasheet of OXFW970/971, they support 32.0kHz to 196.0kHz.

As long as developers investigate, some devices are confirmed to have
several formats for the same sampling rate.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Acked-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/firewire/oxfw/oxfw-pcm.c
sound/firewire/oxfw/oxfw-stream.c
sound/firewire/oxfw/oxfw.c
sound/firewire/oxfw/oxfw.h

index d39f17a8f8c02bc82540813a9d45b4529131fa58..0c0be984edcee79dc9805e0f15bb1b414030019f 100644 (file)
 
 #include "oxfw.h"
 
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
-                                   struct snd_pcm_hw_rule *rule)
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+                       struct snd_pcm_hw_rule *rule)
 {
-       static unsigned int stereo_rates[] = { 48000, 96000 };
-       struct snd_interval *channels =
-                       hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-       struct snd_interval *rate =
-                       hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-
-       /* two channels work only at 48/96 kHz */
-       if (snd_interval_max(channels) < 6)
-               return snd_interval_list(rate, 2, stereo_rates, 0);
-       return 0;
+       u8 **formats = rule->private;
+       struct snd_interval *r =
+               hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+       const struct snd_interval *c =
+               hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+       struct snd_interval t = {
+               .min = UINT_MAX, .max = 0, .integer = 1
+       };
+       struct snd_oxfw_stream_formation formation;
+       unsigned int i, err;
+
+       for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+               if (formats[i] == NULL)
+                       continue;
+
+               err = snd_oxfw_stream_parse_format(formats[i], &formation);
+               if (err < 0)
+                       continue;
+               if (!snd_interval_test(c, formation.pcm))
+                       continue;
+
+               t.min = min(t.min, formation.rate);
+               t.max = max(t.max, formation.rate);
+
+       }
+       return snd_interval_refine(r, &t);
 }
 
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
-                                       struct snd_pcm_hw_rule *rule)
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+                           struct snd_pcm_hw_rule *rule)
 {
-       static const struct snd_interval all_channels = { .min = 6, .max = 6 };
-       struct snd_interval *rate =
-                       hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-       struct snd_interval *channels =
-                       hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-
-       /* 32/44.1 kHz work only with all six channels */
-       if (snd_interval_max(rate) < 48000)
-               return snd_interval_refine(channels, &all_channels);
-       return 0;
+       u8 **formats = rule->private;
+       struct snd_interval *c =
+               hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+       const struct snd_interval *r =
+               hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+       struct snd_oxfw_stream_formation formation;
+       unsigned int i, j, err;
+       unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0};
+
+       count = 0;
+       for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+               if (formats[i] == NULL)
+                       break;
+
+               err = snd_oxfw_stream_parse_format(formats[i], &formation);
+               if (err < 0)
+                       continue;
+               if (!snd_interval_test(r, formation.rate))
+                       continue;
+               if (list[count] == formation.pcm)
+                       continue;
+
+               for (j = 0; j < ARRAY_SIZE(list); j++) {
+                       if (list[j] == formation.pcm)
+                               break;
+               }
+               if (j == ARRAY_SIZE(list)) {
+                       list[count] = formation.pcm;
+                       if (++count == ARRAY_SIZE(list))
+                               break;
+               }
+       }
+
+       return snd_interval_list(c, count, list, 0);
 }
 
-int firewave_constraints(struct snd_pcm_runtime *runtime)
+static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats)
 {
-       static unsigned int channels_list[] = { 2, 6 };
-       static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-               .count = 2,
-               .list = channels_list,
-       };
-       int err;
+       struct snd_oxfw_stream_formation formation;
+       unsigned int i, err;
 
-       runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-                           SNDRV_PCM_RATE_44100 |
-                           SNDRV_PCM_RATE_48000 |
-                           SNDRV_PCM_RATE_96000;
-       runtime->hw.channels_max = 6;
+       hw->channels_min = UINT_MAX;
+       hw->channels_max = 0;
 
-       err = snd_pcm_hw_constraint_list(runtime, 0,
-                                        SNDRV_PCM_HW_PARAM_CHANNELS,
-                                        &channels_list_constraint);
-       if (err < 0)
-               return err;
-       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-                                 firewave_rate_constraint, NULL,
-                                 SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-       if (err < 0)
-               return err;
-       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-                                 firewave_channels_constraint, NULL,
-                                 SNDRV_PCM_HW_PARAM_RATE, -1);
-       if (err < 0)
-               return err;
+       hw->rate_min = UINT_MAX;
+       hw->rate_max = 0;
+       hw->rates = 0;
 
-       return 0;
+       for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+               if (formats[i] == NULL)
+                       break;
+
+               err = snd_oxfw_stream_parse_format(formats[i], &formation);
+               if (err < 0)
+                       continue;
+
+               hw->channels_min = min(hw->channels_min, formation.pcm);
+               hw->channels_max = max(hw->channels_max, formation.pcm);
+
+               hw->rate_min = min(hw->rate_min, formation.rate);
+               hw->rate_max = max(hw->rate_max, formation.rate);
+               hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate);
+       }
 }
 
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
 {
-       runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-                           SNDRV_PCM_RATE_44100 |
-                           SNDRV_PCM_RATE_48000 |
-                           SNDRV_PCM_RATE_88200 |
-                           SNDRV_PCM_RATE_96000;
+       hw->periods_min = 2;            /* SNDRV_PCM_INFO_BATCH */
+       hw->periods_max = UINT_MAX;
 
-       return 0;
+       hw->period_bytes_min = 4 * hw->channels_max;    /* bytes for a frame */
+
+       /* Just to prevent from allocating much pages. */
+       hw->period_bytes_max = hw->period_bytes_min * 2048;
+       hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
 }
 
 static int pcm_open(struct snd_pcm_substream *substream)
 {
-       static const struct snd_pcm_hardware hardware = {
-               .info = SNDRV_PCM_INFO_MMAP |
-                       SNDRV_PCM_INFO_MMAP_VALID |
-                       SNDRV_PCM_INFO_BATCH |
-                       SNDRV_PCM_INFO_INTERLEAVED |
-                       SNDRV_PCM_INFO_BLOCK_TRANSFER,
-               .formats = AMDTP_OUT_PCM_FORMAT_BITS,
-               .channels_min = 2,
-               .channels_max = 2,
-               .buffer_bytes_max = 4 * 1024 * 1024,
-               .period_bytes_min = 1,
-               .period_bytes_max = UINT_MAX,
-               .periods_min = 1,
-               .periods_max = UINT_MAX,
-       };
        struct snd_oxfw *oxfw = substream->private_data;
        struct snd_pcm_runtime *runtime = substream->runtime;
-       bool used;
+       u8 **formats;
        int err;
 
-       err = cmp_connection_check_used(&oxfw->in_conn, &used);
-       if ((err < 0) || used)
-               goto end;
+       formats = oxfw->rx_stream_formats;
+
+       runtime->hw.info = SNDRV_PCM_INFO_BATCH |
+                          SNDRV_PCM_INFO_BLOCK_TRANSFER |
+                          SNDRV_PCM_INFO_INTERLEAVED |
+                          SNDRV_PCM_INFO_MMAP |
+                          SNDRV_PCM_INFO_MMAP_VALID;
 
-       runtime->hw = hardware;
+       limit_channels_and_rates(&runtime->hw, formats);
+       limit_period_and_buffer(&runtime->hw);
 
-       err = oxfw->device_info->pcm_constraints(runtime);
+       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+                                 hw_rule_channels, formats,
+                                 SNDRV_PCM_HW_PARAM_RATE, -1);
        if (err < 0)
                goto end;
-       err = snd_pcm_limit_hw_rates(runtime);
+
+       err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+                                 hw_rule_rate, formats,
+                                 SNDRV_PCM_HW_PARAM_CHANNELS, -1);
        if (err < 0)
                goto end;
 
        err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+       if (err < 0)
+               goto end;
+
+       snd_pcm_set_sync(substream);
 end:
        return err;
 }
index ebd156f3e29d63f83b3bde577ab0b62d077203cd..17e3802e6ac25ab075577801bf835b57f124caea 100644 (file)
@@ -8,6 +8,35 @@
 
 #include "oxfw.h"
 
+#define AVC_GENERIC_FRAME_MAXIMUM_BYTES        512
+
+/*
+ * According to datasheet of Oxford Semiconductor:
+ *  OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O
+ *  OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O
+ */
+static const unsigned int oxfw_rate_table[] = {
+       [0] = 32000,
+       [1] = 44100,
+       [2] = 48000,
+       [3] = 88200,
+       [4] = 96000,
+       [5] = 192000,
+};
+
+/*
+ * See Table 5.7 – Sampling frequency for Multi-bit Audio
+ * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static const unsigned int avc_stream_rate_table[] = {
+       [0] = 0x02,
+       [1] = 0x03,
+       [2] = 0x04,
+       [3] = 0x0a,
+       [4] = 0x05,
+       [5] = 0x07,
+};
+
 int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw)
 {
        int err;
@@ -78,3 +107,242 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw)
        else
                amdtp_stream_update(&oxfw->rx_stream);
 }
+
+/*
+ * See Table 6.16 - AM824 Stream Format
+ *     Figure 6.19 - format_information field for AM824 Compound
+ * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
+ */
+int snd_oxfw_stream_parse_format(u8 *format,
+                                struct snd_oxfw_stream_formation *formation)
+{
+       unsigned int i, e, channels, type;
+
+       memset(formation, 0, sizeof(struct snd_oxfw_stream_formation));
+
+       /*
+        * this module can support a hierarchy combination that:
+        *  Root:       Audio and Music (0x90)
+        *  Level 1:    AM824 Compound  (0x40)
+        */
+       if ((format[0] != 0x90) || (format[1] != 0x40))
+               return -ENOSYS;
+
+       /* check the sampling rate */
+       for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) {
+               if (format[2] == avc_stream_rate_table[i])
+                       break;
+       }
+       if (i == ARRAY_SIZE(avc_stream_rate_table))
+               return -ENOSYS;
+
+       formation->rate = oxfw_rate_table[i];
+
+       for (e = 0; e < format[4]; e++) {
+               channels = format[5 + e * 2];
+               type = format[6 + e * 2];
+
+               switch (type) {
+               /* IEC 60958 Conformant, currently handled as MBLA */
+               case 0x00:
+               /* Multi Bit Linear Audio (Raw) */
+               case 0x06:
+                       formation->pcm += channels;
+                       break;
+               /* MIDI Conformant */
+               case 0x0d:
+                       formation->midi = channels;
+                       break;
+               /* IEC 61937-3 to 7 */
+               case 0x01:
+               case 0x02:
+               case 0x03:
+               case 0x04:
+               case 0x05:
+               /* Multi Bit Linear Audio */
+               case 0x07:      /* DVD-Audio */
+               case 0x0c:      /* High Precision */
+               /* One Bit Audio */
+               case 0x08:      /* (Plain) Raw */
+               case 0x09:      /* (Plain) SACD */
+               case 0x0a:      /* (Encoded) Raw */
+               case 0x0b:      /* (Encoded) SACD */
+               /* SMPTE Time-Code conformant */
+               case 0x0e:
+               /* Sample Count */
+               case 0x0f:
+               /* Anciliary Data */
+               case 0x10:
+               /* Synchronization Stream (Stereo Raw audio) */
+               case 0x40:
+               /* Don't care */
+               case 0xff:
+               default:
+                       return -ENOSYS; /* not supported */
+               }
+       }
+
+       if (formation->pcm  > AMDTP_MAX_CHANNELS_FOR_PCM ||
+           formation->midi > AMDTP_MAX_CHANNELS_FOR_MIDI)
+               return -ENOSYS;
+
+       return 0;
+}
+
+static int
+assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir,
+                     unsigned int pid, u8 *buf, unsigned int *len,
+                     u8 **formats)
+{
+       struct snd_oxfw_stream_formation formation;
+       unsigned int i, eid;
+       int err;
+
+       /* get format at current sampling rate */
+       err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
+       if (err < 0) {
+               dev_err(&oxfw->unit->device,
+               "fail to get current stream format for isoc %s plug %d:%d\n",
+                       (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
+                       pid, err);
+               goto end;
+       }
+
+       /* parse and set stream format */
+       eid = 0;
+       err = snd_oxfw_stream_parse_format(buf, &formation);
+       if (err < 0)
+               goto end;
+
+       formats[eid] = kmalloc(*len, GFP_KERNEL);
+       if (formats[eid] == NULL) {
+               err = -ENOMEM;
+               goto end;
+       }
+       memcpy(formats[eid], buf, *len);
+
+       /* apply the format for each available sampling rate */
+       for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) {
+               if (formation.rate == oxfw_rate_table[i])
+                       continue;
+
+               err = avc_general_inquiry_sig_fmt(oxfw->unit,
+                                                 oxfw_rate_table[i],
+                                                 dir, pid);
+               if (err < 0)
+                       continue;
+
+               eid++;
+               formats[eid] = kmalloc(*len, GFP_KERNEL);
+               if (formats[eid] == NULL) {
+                       err = -ENOMEM;
+                       goto end;
+               }
+               memcpy(formats[eid], buf, *len);
+               formats[eid][2] = avc_stream_rate_table[i];
+       }
+
+       err = 0;
+       oxfw->assumed = true;
+end:
+       return err;
+}
+
+static int fill_stream_formats(struct snd_oxfw *oxfw,
+                              enum avc_general_plug_dir dir,
+                              unsigned short pid)
+{
+       u8 *buf, **formats;
+       unsigned int len, eid = 0;
+       struct snd_oxfw_stream_formation dummy;
+       int err;
+
+       buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
+       if (buf == NULL)
+               return -ENOMEM;
+
+       formats = oxfw->rx_stream_formats;
+
+       /* get first entry */
+       len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+       err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0);
+       if (err == -ENOSYS) {
+               /* LIST subfunction is not implemented */
+               len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+               err = assume_stream_formats(oxfw, dir, pid, buf, &len,
+                                           formats);
+               goto end;
+       } else if (err < 0) {
+               dev_err(&oxfw->unit->device,
+                       "fail to get stream format %d for isoc %s plug %d:%d\n",
+                       eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out",
+                       pid, err);
+               goto end;
+       }
+
+       /* LIST subfunction is implemented */
+       while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) {
+               /* The format is too short. */
+               if (len < 3) {
+                       err = -EIO;
+                       break;
+               }
+
+               /* parse and set stream format */
+               err = snd_oxfw_stream_parse_format(buf, &dummy);
+               if (err < 0)
+                       break;
+
+               formats[eid] = kmalloc(len, GFP_KERNEL);
+               if (formats[eid] == NULL) {
+                       err = -ENOMEM;
+                       break;
+               }
+               memcpy(formats[eid], buf, len);
+
+               /* get next entry */
+               len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+               err = avc_stream_get_format_list(oxfw->unit, dir, 0,
+                                                buf, &len, ++eid);
+               /* No entries remained. */
+               if (err == -EINVAL) {
+                       err = 0;
+                       break;
+               } else if (err < 0) {
+                       dev_err(&oxfw->unit->device,
+                       "fail to get stream format %d for isoc %s plug %d:%d\n",
+                               eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" :
+                                                                       "out",
+                               pid, err);
+                       break;
+               }
+       }
+end:
+       kfree(buf);
+       return err;
+}
+
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
+{
+       u8 plugs[AVC_PLUG_INFO_BUF_BYTES];
+       int err;
+
+       /* the number of plugs for isoc in/out, ext in/out  */
+       err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs);
+       if (err < 0) {
+               dev_err(&oxfw->unit->device,
+               "fail to get info for isoc/external in/out plugs: %d\n",
+                       err);
+               goto end;
+       } else if (plugs[0] == 0) {
+               err = -ENOSYS;
+               goto end;
+       }
+
+       /* use iPCR[0] if exists */
+       if (plugs[0] > 0)
+               err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
+end:
+       return err;
+}
index dd576bf61c37b26a171b6bb15bf7ef3e7d09eee8..a8f9062b2884b94643560cd6c5a9596229db7ea6 100644 (file)
@@ -58,6 +58,10 @@ end:
 static void oxfw_card_free(struct snd_card *card)
 {
        struct snd_oxfw *oxfw = card->private_data;
+       unsigned int i;
+
+       for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++)
+               kfree(oxfw->rx_stream_formats[i]);
 
        mutex_destroy(&oxfw->mutex);
 }
@@ -81,6 +85,10 @@ static int oxfw_probe(struct fw_unit *unit,
        oxfw->unit = unit;
        oxfw->device_info = (const struct device_info *)id->driver_data;
 
+       err = snd_oxfw_stream_discover(oxfw);
+       if (err < 0)
+               goto error;
+
        err = name_card(oxfw);
        if (err < 0)
                goto error;
@@ -136,7 +144,6 @@ static const struct device_info griffin_firewave = {
        .driver_name = "FireWave",
        .vendor_name = "Griffin",
        .model_name = "FireWave",
-       .pcm_constraints = firewave_constraints,
        .mixer_channels = 6,
        .mute_fb_id   = 0x01,
        .volume_fb_id = 0x02,
@@ -146,7 +153,6 @@ static const struct device_info lacie_speakers = {
        .driver_name = "FWSpeakers",
        .vendor_name = "LaCie",
        .model_name = "FireWire Speakers",
-       .pcm_constraints = lacie_speakers_constraints,
        .mixer_channels = 1,
        .mute_fb_id   = 0x01,
        .volume_fb_id = 0x01,
index a7031d4144413ad31ad2a777773966a3a2551af4..9c3d3e352665730cc13dd9c24aedb0e41ddea064 100644 (file)
@@ -30,19 +30,24 @@ struct device_info {
        const char *driver_name;
        const char *vendor_name;
        const char *model_name;
-       int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
        unsigned int mixer_channels;
        u8 mute_fb_id;
        u8 volume_fb_id;
 };
 
+/* This is an arbitrary number for convinience. */
+#define        SND_OXFW_STREAM_FORMAT_ENTRIES  10
 struct snd_oxfw {
        struct snd_card *card;
        struct fw_unit *unit;
        const struct device_info *device_info;
        struct mutex mutex;
+
+       u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES];
+       bool assumed;
        struct cmp_connection in_conn;
        struct amdtp_stream rx_stream;
+
        bool mute;
        s16 volume[6];
        s16 volume_min;
@@ -88,8 +93,18 @@ void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw);
 void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw);
 void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw);
 
-int firewave_constraints(struct snd_pcm_runtime *runtime);
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
+struct snd_oxfw_stream_formation {
+       unsigned int rate;
+       unsigned int pcm;
+       unsigned int midi;
+};
+int snd_oxfw_stream_parse_format(u8 *format,
+                                struct snd_oxfw_stream_formation *formation);
+int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw,
+                               enum avc_general_plug_dir dir,
+                               struct snd_oxfw_stream_formation *formation);
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+
 int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);