summaryrefslogtreecommitdiffstats
path: root/sound/usb/6fire/control.c
diff options
context:
space:
mode:
authorIngo Molnar <mingo@kernel.org>2012-04-14 13:18:27 +0200
committerIngo Molnar <mingo@kernel.org>2012-04-14 13:19:04 +0200
commit6ac1ef482d7ae0c690f1640bf6eb818ff9a2d91e (patch)
tree021cc9f6b477146fcebe6f3be4752abfa2ba18a9 /sound/usb/6fire/control.c
parent682968e0c425c60f0dde37977e5beb2b12ddc4cc (diff)
parenta385ec4f11bdcf81af094c03e2444ee9b7fad2e5 (diff)
Merge branch 'perf/core' into perf/uprobes
Merge in latest upstream (and the latest perf development tree), to prepare for tooling changes, and also to pick up v3.4 MM changes that the uprobes code needs to take care of. Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'sound/usb/6fire/control.c')
-rw-r--r--sound/usb/6fire/control.c341
1 files changed, 296 insertions, 45 deletions
diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c
index ac828eff1a6..07ed914d5e7 100644
--- a/sound/usb/6fire/control.c
+++ b/sound/usb/6fire/control.c
@@ -5,9 +5,12 @@
*
* Author: Torsten Schenk <torsten.schenk@zoho.com>
* Created: Jan 01, 2011
- * Version: 0.3.0
* Copyright: (C) Torsten Schenk
*
+ * Thanks to:
+ * - Holger Ruckdeschel: he found out how to control individual channel
+ * volumes and introduced mute switch
+ *
* 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; either version 2 of the License, or
@@ -16,6 +19,7 @@
#include <linux/interrupt.h>
#include <sound/control.h>
+#include <sound/tlv.h>
#include "control.h"
#include "comm.h"
@@ -25,26 +29,6 @@ static char *opt_coax_texts[2] = { "Optical", "Coax" };
static char *line_phono_texts[2] = { "Line", "Phono" };
/*
- * calculated with $value\[i\] = 128 \cdot sqrt[3]{\frac{i}{128}}$
- * this is done because the linear values cause rapid degredation
- * of volume in the uppermost region.
- */
-static const u8 log_volume_table[128] = {
- 0x00, 0x19, 0x20, 0x24, 0x28, 0x2b, 0x2e, 0x30, 0x32, 0x34,
- 0x36, 0x38, 0x3a, 0x3b, 0x3d, 0x3e, 0x40, 0x41, 0x42, 0x43,
- 0x44, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
- 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56,
- 0x56, 0x57, 0x58, 0x58, 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c,
- 0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62,
- 0x63, 0x63, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68,
- 0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c,
- 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71,
- 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75,
- 0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79,
- 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c,
- 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f };
-
-/*
* data that needs to be sent to device. sets up card internal stuff.
* values dumped from windows driver and filtered by trial'n'error.
*/
@@ -59,7 +43,7 @@ init_data[] = {
{ 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
{ 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
{ 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
- { 0x12, 0x0d, 0x78 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
+ { 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
{ 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
{ 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
{ 0 } /* TERMINATING ENTRY */
@@ -70,20 +54,47 @@ static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
+static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
+static DECLARE_TLV_DB_MINMAX(tlv_input, -1500, 1500);
+
enum {
DIGITAL_THRU_ONLY_SAMPLERATE = 3
};
-static void usb6fire_control_master_vol_update(struct control_runtime *rt)
+static void usb6fire_control_output_vol_update(struct control_runtime *rt)
{
struct comm_runtime *comm_rt = rt->chip->comm;
- if (comm_rt) {
- /* set volume */
- comm_rt->write8(comm_rt, 0x12, 0x0f, 0x7f -
- log_volume_table[rt->master_vol]);
- /* unmute */
- comm_rt->write8(comm_rt, 0x12, 0x0e, 0x00);
- }
+ int i;
+
+ if (comm_rt)
+ for (i = 0; i < 6; i++)
+ if (!(rt->ovol_updated & (1 << i))) {
+ comm_rt->write8(comm_rt, 0x12, 0x0f + i,
+ 180 - rt->output_vol[i]);
+ rt->ovol_updated |= 1 << i;
+ }
+}
+
+static void usb6fire_control_output_mute_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+
+ if (comm_rt)
+ comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
+}
+
+static void usb6fire_control_input_vol_update(struct control_runtime *rt)
+{
+ struct comm_runtime *comm_rt = rt->chip->comm;
+ int i;
+
+ if (comm_rt)
+ for (i = 0; i < 2; i++)
+ if (!(rt->ivol_updated & (1 << i))) {
+ comm_rt->write8(comm_rt, 0x12, 0x1c + i,
+ rt->input_vol[i] & 0x3f);
+ rt->ivol_updated |= 1 << i;
+ }
}
static void usb6fire_control_line_phono_update(struct control_runtime *rt)
@@ -165,34 +176,147 @@ static int usb6fire_control_streaming_update(struct control_runtime *rt)
return -EINVAL;
}
-static int usb6fire_control_master_vol_info(struct snd_kcontrol *kcontrol,
+static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- uinfo->count = 1;
+ uinfo->count = 2;
uinfo->value.integer.min = 0;
- uinfo->value.integer.max = 127;
+ uinfo->value.integer.max = 180;
return 0;
}
-static int usb6fire_control_master_vol_put(struct snd_kcontrol *kcontrol,
+static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
int changed = 0;
- if (rt->master_vol != ucontrol->value.integer.value[0]) {
- rt->master_vol = ucontrol->value.integer.value[0];
- usb6fire_control_master_vol_update(rt);
+
+ if (ch > 4) {
+ snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
+ rt->output_vol[ch] = ucontrol->value.integer.value[0];
+ rt->ovol_updated &= ~(1 << ch);
changed = 1;
}
+ if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
+ rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
+ rt->ovol_updated &= ~(2 << ch);
+ changed = 1;
+ }
+
+ if (changed)
+ usb6fire_control_output_vol_update(rt);
+
return changed;
}
-static int usb6fire_control_master_vol_get(struct snd_kcontrol *kcontrol,
+static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
- ucontrol->value.integer.value[0] = rt->master_vol;
+ unsigned int ch = kcontrol->private_value;
+
+ if (ch > 4) {
+ snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ ucontrol->value.integer.value[0] = rt->output_vol[ch];
+ ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
+ return 0;
+}
+
+static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+ u8 old = rt->output_mute;
+ u8 value = 0;
+
+ if (ch > 4) {
+ snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ rt->output_mute &= ~(3 << ch);
+ if (ucontrol->value.integer.value[0])
+ value |= 1;
+ if (ucontrol->value.integer.value[1])
+ value |= 2;
+ rt->output_mute |= value << ch;
+
+ if (rt->output_mute != old)
+ usb6fire_control_output_mute_update(rt);
+
+ return rt->output_mute != old;
+}
+
+static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ unsigned int ch = kcontrol->private_value;
+ u8 value = rt->output_mute >> ch;
+
+ if (ch > 4) {
+ snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
+ return -EINVAL;
+ }
+
+ ucontrol->value.integer.value[0] = 1 & value;
+ value >>= 1;
+ ucontrol->value.integer.value[1] = 1 & value;
+
+ return 0;
+}
+
+static int usb6fire_control_input_vol_info(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo)
+{
+ uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+ uinfo->count = 2;
+ uinfo->value.integer.min = 0;
+ uinfo->value.integer.max = 30;
+ return 0;
+}
+
+static int usb6fire_control_input_vol_put(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+ int changed = 0;
+
+ if (rt->input_vol[0] != ucontrol->value.integer.value[0]) {
+ rt->input_vol[0] = ucontrol->value.integer.value[0] - 15;
+ rt->ivol_updated &= ~(1 << 0);
+ changed = 1;
+ }
+ if (rt->input_vol[1] != ucontrol->value.integer.value[1]) {
+ rt->input_vol[1] = ucontrol->value.integer.value[1] - 15;
+ rt->ivol_updated &= ~(1 << 1);
+ changed = 1;
+ }
+
+ if (changed)
+ usb6fire_control_input_vol_update(rt);
+
+ return changed;
+}
+
+static int usb6fire_control_input_vol_get(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
+
+ ucontrol->value.integer.value[0] = rt->input_vol[0] + 15;
+ ucontrol->value.integer.value[1] = rt->input_vol[1] + 15;
+
return 0;
}
@@ -287,18 +411,83 @@ static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
return 0;
}
-static struct __devinitdata snd_kcontrol_new elements[] = {
+static struct __devinitdata snd_kcontrol_new vol_elements[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
- .name = "Master Playback Volume",
+ .name = "Analog Playback Volume",
.index = 0,
+ .private_value = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Volume",
+ .index = 1,
+ .private_value = 2,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Volume",
+ .index = 2,
+ .private_value = 4,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_output_vol_info,
+ .get = usb6fire_control_output_vol_get,
+ .put = usb6fire_control_output_vol_put,
+ .tlv = { .p = tlv_output }
+ },
+ {}
+};
+
+static struct __devinitdata snd_kcontrol_new mute_elements[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 0,
+ .private_value = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
+ },
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 1,
+ .private_value = 2,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
- .info = usb6fire_control_master_vol_info,
- .get = usb6fire_control_master_vol_get,
- .put = usb6fire_control_master_vol_put
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Playback Switch",
+ .index = 2,
+ .private_value = 4,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+ .info = snd_ctl_boolean_stereo_info,
+ .get = usb6fire_control_output_mute_get,
+ .put = usb6fire_control_output_mute_put,
+ },
+ {}
+};
+
+static struct __devinitdata snd_kcontrol_new elements[] = {
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line/Phono Capture Route",
.index = 0,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
@@ -324,9 +513,54 @@ static struct __devinitdata snd_kcontrol_new elements[] = {
.get = usb6fire_control_digital_thru_get,
.put = usb6fire_control_digital_thru_put
},
+ {
+ .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+ .name = "Analog Capture Volume",
+ .index = 0,
+ .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+ SNDRV_CTL_ELEM_ACCESS_TLV_READ,
+ .info = usb6fire_control_input_vol_info,
+ .get = usb6fire_control_input_vol_get,
+ .put = usb6fire_control_input_vol_put,
+ .tlv = { .p = tlv_input }
+ },
{}
};
+static int usb6fire_control_add_virtual(
+ struct control_runtime *rt,
+ struct snd_card *card,
+ char *name,
+ struct snd_kcontrol_new *elems)
+{
+ int ret;
+ int i;
+ struct snd_kcontrol *vmaster =
+ snd_ctl_make_virtual_master(name, tlv_output);
+ struct snd_kcontrol *control;
+
+ if (!vmaster)
+ return -ENOMEM;
+ ret = snd_ctl_add(card, vmaster);
+ if (ret < 0)
+ return ret;
+
+ i = 0;
+ while (elems[i].name) {
+ control = snd_ctl_new1(&elems[i], rt);
+ if (!control)
+ return -ENOMEM;
+ ret = snd_ctl_add(card, control);
+ if (ret < 0)
+ return ret;
+ ret = snd_ctl_add_slave(vmaster, control);
+ if (ret < 0)
+ return ret;
+ i++;
+ }
+ return 0;
+}
+
int __devinit usb6fire_control_init(struct sfire_chip *chip)
{
int i;
@@ -352,9 +586,26 @@ int __devinit usb6fire_control_init(struct sfire_chip *chip)
usb6fire_control_opt_coax_update(rt);
usb6fire_control_line_phono_update(rt);
- usb6fire_control_master_vol_update(rt);
+ usb6fire_control_output_vol_update(rt);
+ usb6fire_control_output_mute_update(rt);
+ usb6fire_control_input_vol_update(rt);
usb6fire_control_streaming_update(rt);
+ ret = usb6fire_control_add_virtual(rt, chip->card,
+ "Master Playback Volume", vol_elements);
+ if (ret) {
+ snd_printk(KERN_ERR PREFIX "cannot add control.\n");
+ kfree(rt);
+ return ret;
+ }
+ ret = usb6fire_control_add_virtual(rt, chip->card,
+ "Master Playback Switch", mute_elements);
+ if (ret) {
+ snd_printk(KERN_ERR PREFIX "cannot add control.\n");
+ kfree(rt);
+ return ret;
+ }
+
i = 0;
while (elements[i].name) {
ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));