aboutsummaryrefslogtreecommitdiffstats
path: root/puppet/services/keystone.yaml
blob: 988c80ce31f612b7a7d25e05b8a4990ecd2daaeb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
heat_template_version: 2016-04-08

description: >
  OpenStack Keystone service configured with Puppet

parameters:
  KeystoneEnableDBPurge:
    default: true
    description: |
        Whether to create cron job for purging soft deleted rows in Keystone database.
    type: boolean
  KeystoneSSLCertificate:
    default: ''
    description: Keystone certificate for verifying token validity.
    type: string
  KeystoneSSLCertificateKey:
    default: ''
    description: Keystone key for signing tokens.
    type: string
    hidden: true
  KeystoneNotificationDriver:
    description: Comma-separated list of Oslo notification drivers used by Keystone
    default: ['messaging']
    type: comma_delimited_list
  KeystoneNotificationFormat:
    description: The Keystone notification format
    default: 'basic'
    type: string
    constraints:
      - allowed_values: [ 'basic', 'cadf' ]
  KeystoneRegion:
    type: string
    default: 'regionOne'
    description: Keystone region for endpoint
  EndpointMap:
    default: {}
    description: Mapping of service endpoint -> protocol. Typically set
                 via parameter_defaults in the resource registry.
    type: json
  Debug:
    type: string
    default: ''
  AdminEmail:
    default: 'admin@example.com'
    description: The email for the keystone admin account.
    type: string
    hidden: true
  AdminPassword:
    description: The password for the keystone admin account, used for monitoring, querying neutron etc.
    type: string
    hidden: true
  AdminToken:
    description: The keystone auth secret and db password.
    type: string
    hidden: true
  RabbitPassword:
    description: The password for RabbitMQ
    type: string
    hidden: true
  RabbitUserName:
    default: guest
    description: The username for RabbitMQ
    type: string
  RabbitClientUseSSL:
    default: false
    description: >
        Rabbit client subscriber parameter to specify
        an SSL connection to the RabbitMQ host.
    type: string
  RabbitClientPort:
    default: 5672
    description: Set rabbit subscriber port, change this if using SSL
    type: number
  KeystoneWorkers:
    type: string
    description: Set the number of workers for keystone::wsgi::apache
    default: '"%{::processorcount}"'
outputs:
  role_data:
    description: Role data for the Keystone role.
    value:
      service_name: keystone
      config_settings:
        keystone::database_connection:
          list_join:
            - ''
            - - {get_param: [EndpointMap, MysqlInternal, protocol]}
              - '://keystone:'
              - {get_param: AdminToken}
              - '@'
              - {get_param: [EndpointMap, MysqlInternal, host]}
              - '/keystone'
        keystone::admin_token: {get_param: AdminToken}
        keystone::roles::admin::password: {get_param: AdminPassword}
        keystone_ssl_certificate: {get_param: KeystoneSSLCertificate}
        keystone_ssl_certificate_key: {get_param: KeystoneSSLCertificateKey}
        keystone::enable_proxy_headers_parsing: true
        keystone::debug: {get_param: Debug}
        keystone::db::mysql::password: {get_param: AdminToken}
        keystone::rabbit_userid: {get_param: RabbitUserName}
        keystone::rabbit_password: {get_param: RabbitPassword}
        keystone::rabbit_use_ssl: {get_param: RabbitClientUseSSL}
        keystone::rabbit_port: {get_param: RabbitClientPort}
        keystone::notification_driver: {get_param: KeystoneNotificationDriver}
        keystone::notification_format: {get_param: KeystoneNotificationFormat}
        keystone::roles::admin::email: {get_param: AdminEmail}
        keystone::roles::admin::password: {get_param: AdminPassword}
        keystone::endpoint::public_url: {get_param: [EndpointMap, KeystonePublic, uri_no_suffix]}
        keystone::endpoint::internal_url: {get_param: [EndpointMap, KeystoneInternal, uri_no_suffix]}
        keystone::endpoint::admin_url: {get_param: [EndpointMap, KeystoneAdmin, uri_no_suffix]}
        keystone::endpoint::region: {get_param: KeystoneRegion}
        keystone_enable_db_purge: {get_param: KeystoneEnableDBPurge}
        keystone::public_endpoint: {get_param: [EndpointMap, KeystonePublic, uri_no_suffix]}
        keystone::db::mysql::user: keystone
        keystone::db::mysql::host: {get_param: [EndpointMap, MysqlInternal, host_nobrackets]}
        keystone::db::mysql::dbname: keystone
        keystone::db::mysql::allowed_hosts:
          - '%'
          - "%{hiera('mysql_bind_host')}"
        keystone::rabbit_heartbeat_timeout_threshold: 60
        keystone::cron::token_flush::maxdelay: 3600
        keystone::roles::admin::service_tenant: 'service'
        keystone::roles::admin::admin_tenant: 'admin'
        keystone::cron::token_flush::destination: '/dev/null'
        keystone::config::keystone_config:
          ec2/driver:
            value: 'keystone.contrib.ec2.backends.sql.Ec2'
        keystone::service_name: 'httpd'
        keystone::wsgi::apache::ssl: false

        keystone::wsgi::apache::workers: {get_param: KeystoneWorkers}
        # override via extraconfig:
        keystone::wsgi::apache::threads: 1
        keystone::db::database_db_max_retries: -1
        keystone::db::database_max_retries: -1
        tripleo.keystone.firewall_rules:
          '111 keystone':
            dport:
              - 5000
              - 13000
              - 35357
              - 13357
      step_config: |
        include ::tripleo::profile::base::keystone
1067' href='#n1067'>1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139
/*
 * C-Media CMI8788 driver - mixer code
 *
 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
 *
 *
 *  This driver is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License, version 2.
 *
 *  This driver is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this driver; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
 */

#include <linux/mutex.h>
#include <sound/ac97_codec.h>
#include <sound/asoundef.h>
#include <sound/control.h>
#include <sound/tlv.h>
#include "oxygen.h"
#include "cm9780.h"

static int dac_volume_info(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_info *info)
{
	struct oxygen *chip = ctl->private_data;

	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	info->count = chip->model.dac_channels_mixer;
	info->value.integer.min = chip->model.dac_volume_min;
	info->value.integer.max = chip->model.dac_volume_max;
	return 0;
}

static int dac_volume_get(struct snd_kcontrol *ctl,
			  struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int i;

	mutex_lock(&chip->mutex);
	for (i = 0; i < chip->model.dac_channels_mixer; ++i)
		value->value.integer.value[i] = chip->dac_volume[i];
	mutex_unlock(&chip->mutex);
	return 0;
}

static int dac_volume_put(struct snd_kcontrol *ctl,
			  struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int i;
	int changed;

	changed = 0;
	mutex_lock(&chip->mutex);
	for (i = 0; i < chip->model.dac_channels_mixer; ++i)
		if (value->value.integer.value[i] != chip->dac_volume[i]) {
			chip->dac_volume[i] = value->value.integer.value[i];
			changed = 1;
		}
	if (changed)
		chip->model.update_dac_volume(chip);
	mutex_unlock(&chip->mutex);
	return changed;
}

static int dac_mute_get(struct snd_kcontrol *ctl,
			struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	value->value.integer.value[0] = !chip->dac_mute;
	mutex_unlock(&chip->mutex);
	return 0;
}

static int dac_mute_put(struct snd_kcontrol *ctl,
			  struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	int changed;

	mutex_lock(&chip->mutex);
	changed = (!value->value.integer.value[0]) != chip->dac_mute;
	if (changed) {
		chip->dac_mute = !value->value.integer.value[0];
		chip->model.update_dac_mute(chip);
	}
	mutex_unlock(&chip->mutex);
	return changed;
}

static unsigned int upmix_item_count(struct oxygen *chip)
{
	if (chip->model.dac_channels_pcm < 8)
		return 2;
	else if (chip->model.update_center_lfe_mix)
		return 5;
	else
		return 3;
}

static int upmix_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
{
	static const char *const names[5] = {
		"Front",
		"Front+Surround",
		"Front+Surround+Back",
		"Front+Surround+Center/LFE",
		"Front+Surround+Center/LFE+Back",
	};
	struct oxygen *chip = ctl->private_data;
	unsigned int count = upmix_item_count(chip);

	return snd_ctl_enum_info(info, 1, count, names);
}

static int upmix_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	value->value.enumerated.item[0] = chip->dac_routing;
	mutex_unlock(&chip->mutex);
	return 0;
}

void oxygen_update_dac_routing(struct oxygen *chip)
{
	/* DAC 0: front, DAC 1: surround, DAC 2: center/LFE, DAC 3: back */
	static const unsigned int reg_values[5] = {
		/* stereo -> front */
		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
		(1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
		/* stereo -> front+surround */
		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
		/* stereo -> front+surround+back */
		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
		(2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
		/* stereo -> front+surround+center/LFE */
		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
		(3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
		/* stereo -> front+surround+center/LFE+back */
		(0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
		(0 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT),
	};
	u8 channels;
	unsigned int reg_value;

	channels = oxygen_read8(chip, OXYGEN_PLAY_CHANNELS) &
		OXYGEN_PLAY_CHANNELS_MASK;
	if (channels == OXYGEN_PLAY_CHANNELS_2)
		reg_value = reg_values[chip->dac_routing];
	else if (channels == OXYGEN_PLAY_CHANNELS_8)
		/* in 7.1 mode, "rear" channels go to the "back" jack */
		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
			    (3 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
			    (1 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
	else
		reg_value = (0 << OXYGEN_PLAY_DAC0_SOURCE_SHIFT) |
			    (1 << OXYGEN_PLAY_DAC1_SOURCE_SHIFT) |
			    (2 << OXYGEN_PLAY_DAC2_SOURCE_SHIFT) |
			    (3 << OXYGEN_PLAY_DAC3_SOURCE_SHIFT);
	if (chip->model.adjust_dac_routing)
		reg_value = chip->model.adjust_dac_routing(chip, reg_value);
	oxygen_write16_masked(chip, OXYGEN_PLAY_ROUTING, reg_value,
			      OXYGEN_PLAY_DAC0_SOURCE_MASK |
			      OXYGEN_PLAY_DAC1_SOURCE_MASK |
			      OXYGEN_PLAY_DAC2_SOURCE_MASK |
			      OXYGEN_PLAY_DAC3_SOURCE_MASK);
	if (chip->model.update_center_lfe_mix)
		chip->model.update_center_lfe_mix(chip, chip->dac_routing > 2);
}
EXPORT_SYMBOL(oxygen_update_dac_routing);

static int upmix_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int count = upmix_item_count(chip);
	int changed;

	if (value->value.enumerated.item[0] >= count)
		return -EINVAL;
	mutex_lock(&chip->mutex);
	changed = value->value.enumerated.item[0] != chip->dac_routing;
	if (changed) {
		chip->dac_routing = value->value.enumerated.item[0];
		oxygen_update_dac_routing(chip);
	}
	mutex_unlock(&chip->mutex);
	return changed;
}

static int spdif_switch_get(struct snd_kcontrol *ctl,
			    struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	value->value.integer.value[0] = chip->spdif_playback_enable;
	mutex_unlock(&chip->mutex);
	return 0;
}

static unsigned int oxygen_spdif_rate(unsigned int oxygen_rate)
{
	switch (oxygen_rate) {
	case OXYGEN_RATE_32000:
		return IEC958_AES3_CON_FS_32000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_44100:
		return IEC958_AES3_CON_FS_44100 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	default: /* OXYGEN_RATE_48000 */
		return IEC958_AES3_CON_FS_48000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_64000:
		return 0xb << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_88200:
		return IEC958_AES3_CON_FS_88200 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_96000:
		return IEC958_AES3_CON_FS_96000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_176400:
		return IEC958_AES3_CON_FS_176400 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	case OXYGEN_RATE_192000:
		return IEC958_AES3_CON_FS_192000 << OXYGEN_SPDIF_CS_RATE_SHIFT;
	}
}

void oxygen_update_spdif_source(struct oxygen *chip)
{
	u32 old_control, new_control;
	u16 old_routing, new_routing;
	unsigned int oxygen_rate;

	old_control = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
	old_routing = oxygen_read16(chip, OXYGEN_PLAY_ROUTING);
	if (chip->pcm_active & (1 << PCM_SPDIF)) {
		new_control = old_control | OXYGEN_SPDIF_OUT_ENABLE;
		new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK)
			| OXYGEN_PLAY_SPDIF_SPDIF;
		oxygen_rate = (old_control >> OXYGEN_SPDIF_OUT_RATE_SHIFT)
			& OXYGEN_I2S_RATE_MASK;
		/* S/PDIF rate was already set by the caller */
	} else if ((chip->pcm_active & (1 << PCM_MULTICH)) &&
		   chip->spdif_playback_enable) {
		new_routing = (old_routing & ~OXYGEN_PLAY_SPDIF_MASK)
			| OXYGEN_PLAY_SPDIF_MULTICH_01;
		oxygen_rate = oxygen_read16(chip, OXYGEN_I2S_MULTICH_FORMAT)
			& OXYGEN_I2S_RATE_MASK;
		new_control = (old_control & ~OXYGEN_SPDIF_OUT_RATE_MASK) |
			(oxygen_rate << OXYGEN_SPDIF_OUT_RATE_SHIFT) |
			OXYGEN_SPDIF_OUT_ENABLE;
	} else {
		new_control = old_control & ~OXYGEN_SPDIF_OUT_ENABLE;
		new_routing = old_routing;
		oxygen_rate = OXYGEN_RATE_44100;
	}
	if (old_routing != new_routing) {
		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL,
			       new_control & ~OXYGEN_SPDIF_OUT_ENABLE);
		oxygen_write16(chip, OXYGEN_PLAY_ROUTING, new_routing);
	}
	if (new_control & OXYGEN_SPDIF_OUT_ENABLE)
		oxygen_write32(chip, OXYGEN_SPDIF_OUTPUT_BITS,
			       oxygen_spdif_rate(oxygen_rate) |
			       ((chip->pcm_active & (1 << PCM_SPDIF)) ?
				chip->spdif_pcm_bits : chip->spdif_bits));
	oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, new_control);
}

static int spdif_switch_put(struct snd_kcontrol *ctl,
			    struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	int changed;

	mutex_lock(&chip->mutex);
	changed = value->value.integer.value[0] != chip->spdif_playback_enable;
	if (changed) {
		chip->spdif_playback_enable = !!value->value.integer.value[0];
		spin_lock_irq(&chip->reg_lock);
		oxygen_update_spdif_source(chip);
		spin_unlock_irq(&chip->reg_lock);
	}
	mutex_unlock(&chip->mutex);
	return changed;
}

static int spdif_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
{
	info->type = SNDRV_CTL_ELEM_TYPE_IEC958;
	info->count = 1;
	return 0;
}

static void oxygen_to_iec958(u32 bits, struct snd_ctl_elem_value *value)
{
	value->value.iec958.status[0] =
		bits & (OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
			OXYGEN_SPDIF_PREEMPHASIS);
	value->value.iec958.status[1] = /* category and original */
		bits >> OXYGEN_SPDIF_CATEGORY_SHIFT;
}

static u32 iec958_to_oxygen(struct snd_ctl_elem_value *value)
{
	u32 bits;

	bits = value->value.iec958.status[0] &
		(OXYGEN_SPDIF_NONAUDIO | OXYGEN_SPDIF_C |
		 OXYGEN_SPDIF_PREEMPHASIS);
	bits |= value->value.iec958.status[1] << OXYGEN_SPDIF_CATEGORY_SHIFT;
	if (bits & OXYGEN_SPDIF_NONAUDIO)
		bits |= OXYGEN_SPDIF_V;
	return bits;
}

static inline void write_spdif_bits(struct oxygen *chip, u32 bits)
{
	oxygen_write32_masked(chip, OXYGEN_SPDIF_OUTPUT_BITS, bits,
			      OXYGEN_SPDIF_NONAUDIO |
			      OXYGEN_SPDIF_C |
			      OXYGEN_SPDIF_PREEMPHASIS |
			      OXYGEN_SPDIF_CATEGORY_MASK |
			      OXYGEN_SPDIF_ORIGINAL |
			      OXYGEN_SPDIF_V);
}

static int spdif_default_get(struct snd_kcontrol *ctl,
			     struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	oxygen_to_iec958(chip->spdif_bits, value);
	mutex_unlock(&chip->mutex);
	return 0;
}

static int spdif_default_put(struct snd_kcontrol *ctl,
			     struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u32 new_bits;
	int changed;

	new_bits = iec958_to_oxygen(value);
	mutex_lock(&chip->mutex);
	changed = new_bits != chip->spdif_bits;
	if (changed) {
		chip->spdif_bits = new_bits;
		if (!(chip->pcm_active & (1 << PCM_SPDIF)))
			write_spdif_bits(chip, new_bits);
	}
	mutex_unlock(&chip->mutex);
	return changed;
}

static int spdif_mask_get(struct snd_kcontrol *ctl,
			  struct snd_ctl_elem_value *value)
{
	value->value.iec958.status[0] = IEC958_AES0_NONAUDIO |
		IEC958_AES0_CON_NOT_COPYRIGHT | IEC958_AES0_CON_EMPHASIS;
	value->value.iec958.status[1] =
		IEC958_AES1_CON_CATEGORY | IEC958_AES1_CON_ORIGINAL;
	return 0;
}

static int spdif_pcm_get(struct snd_kcontrol *ctl,
			 struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	oxygen_to_iec958(chip->spdif_pcm_bits, value);
	mutex_unlock(&chip->mutex);
	return 0;
}

static int spdif_pcm_put(struct snd_kcontrol *ctl,
			 struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u32 new_bits;
	int changed;

	new_bits = iec958_to_oxygen(value);
	mutex_lock(&chip->mutex);
	changed = new_bits != chip->spdif_pcm_bits;
	if (changed) {
		chip->spdif_pcm_bits = new_bits;
		if (chip->pcm_active & (1 << PCM_SPDIF))
			write_spdif_bits(chip, new_bits);
	}
	mutex_unlock(&chip->mutex);
	return changed;
}

static int spdif_input_mask_get(struct snd_kcontrol *ctl,
				struct snd_ctl_elem_value *value)
{
	value->value.iec958.status[0] = 0xff;
	value->value.iec958.status[1] = 0xff;
	value->value.iec958.status[2] = 0xff;
	value->value.iec958.status[3] = 0xff;
	return 0;
}

static int spdif_input_default_get(struct snd_kcontrol *ctl,
				   struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u32 bits;

	bits = oxygen_read32(chip, OXYGEN_SPDIF_INPUT_BITS);
	value->value.iec958.status[0] = bits;
	value->value.iec958.status[1] = bits >> 8;
	value->value.iec958.status[2] = bits >> 16;
	value->value.iec958.status[3] = bits >> 24;
	return 0;
}

static int spdif_bit_switch_get(struct snd_kcontrol *ctl,
				struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u32 bit = ctl->private_value;

	value->value.integer.value[0] =
		!!(oxygen_read32(chip, OXYGEN_SPDIF_CONTROL) & bit);
	return 0;
}

static int spdif_bit_switch_put(struct snd_kcontrol *ctl,
				struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u32 bit = ctl->private_value;
	u32 oldreg, newreg;
	int changed;

	spin_lock_irq(&chip->reg_lock);
	oldreg = oxygen_read32(chip, OXYGEN_SPDIF_CONTROL);
	if (value->value.integer.value[0])
		newreg = oldreg | bit;
	else
		newreg = oldreg & ~bit;
	changed = newreg != oldreg;
	if (changed)
		oxygen_write32(chip, OXYGEN_SPDIF_CONTROL, newreg);
	spin_unlock_irq(&chip->reg_lock);
	return changed;
}

static int monitor_volume_info(struct snd_kcontrol *ctl,
			       struct snd_ctl_elem_info *info)
{
	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	info->count = 1;
	info->value.integer.min = 0;
	info->value.integer.max = 1;
	return 0;
}

static int monitor_get(struct snd_kcontrol *ctl,
		       struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u8 bit = ctl->private_value;
	int invert = ctl->private_value & (1 << 8);

	value->value.integer.value[0] =
		!!invert ^ !!(oxygen_read8(chip, OXYGEN_ADC_MONITOR) & bit);
	return 0;
}

static int monitor_put(struct snd_kcontrol *ctl,
		       struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u8 bit = ctl->private_value;
	int invert = ctl->private_value & (1 << 8);
	u8 oldreg, newreg;
	int changed;

	spin_lock_irq(&chip->reg_lock);
	oldreg = oxygen_read8(chip, OXYGEN_ADC_MONITOR);
	if ((!!value->value.integer.value[0] ^ !!invert) != 0)
		newreg = oldreg | bit;
	else
		newreg = oldreg & ~bit;
	changed = newreg != oldreg;
	if (changed)
		oxygen_write8(chip, OXYGEN_ADC_MONITOR, newreg);
	spin_unlock_irq(&chip->reg_lock);
	return changed;
}

static int ac97_switch_get(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int codec = (ctl->private_value >> 24) & 1;
	unsigned int index = ctl->private_value & 0xff;
	unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
	int invert = ctl->private_value & (1 << 16);
	u16 reg;

	mutex_lock(&chip->mutex);
	reg = oxygen_read_ac97(chip, codec, index);
	mutex_unlock(&chip->mutex);
	if (!(reg & (1 << bitnr)) ^ !invert)
		value->value.integer.value[0] = 1;
	else
		value->value.integer.value[0] = 0;
	return 0;
}

static void mute_ac97_ctl(struct oxygen *chip, unsigned int control)
{
	unsigned int priv_idx;
	u16 value;

	if (!chip->controls[control])
		return;
	priv_idx = chip->controls[control]->private_value & 0xff;
	value = oxygen_read_ac97(chip, 0, priv_idx);
	if (!(value & 0x8000)) {
		oxygen_write_ac97(chip, 0, priv_idx, value | 0x8000);
		if (chip->model.ac97_switch)
			chip->model.ac97_switch(chip, priv_idx, 0x8000);
		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_VALUE,
			       &chip->controls[control]->id);
	}
}

static int ac97_switch_put(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int codec = (ctl->private_value >> 24) & 1;
	unsigned int index = ctl->private_value & 0xff;
	unsigned int bitnr = (ctl->private_value >> 8) & 0xff;
	int invert = ctl->private_value & (1 << 16);
	u16 oldreg, newreg;
	int change;

	mutex_lock(&chip->mutex);
	oldreg = oxygen_read_ac97(chip, codec, index);
	newreg = oldreg;
	if (!value->value.integer.value[0] ^ !invert)
		newreg |= 1 << bitnr;
	else
		newreg &= ~(1 << bitnr);
	change = newreg != oldreg;
	if (change) {
		oxygen_write_ac97(chip, codec, index, newreg);
		if (codec == 0 && chip->model.ac97_switch)
			chip->model.ac97_switch(chip, index, newreg & 0x8000);
		if (index == AC97_LINE) {
			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
						 newreg & 0x8000 ?
						 CM9780_GPO0 : 0, CM9780_GPO0);
			if (!(newreg & 0x8000)) {
				mute_ac97_ctl(chip, CONTROL_MIC_CAPTURE_SWITCH);
				mute_ac97_ctl(chip, CONTROL_CD_CAPTURE_SWITCH);
				mute_ac97_ctl(chip, CONTROL_AUX_CAPTURE_SWITCH);
			}
		} else if ((index == AC97_MIC || index == AC97_CD ||
			    index == AC97_VIDEO || index == AC97_AUX) &&
			   bitnr == 15 && !(newreg & 0x8000)) {
			mute_ac97_ctl(chip, CONTROL_LINE_CAPTURE_SWITCH);
			oxygen_write_ac97_masked(chip, 0, CM9780_GPIO_STATUS,
						 CM9780_GPO0, CM9780_GPO0);
		}
	}
	mutex_unlock(&chip->mutex);
	return change;
}

static int ac97_volume_info(struct snd_kcontrol *ctl,
			    struct snd_ctl_elem_info *info)
{
	int stereo = (ctl->private_value >> 16) & 1;

	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	info->count = stereo ? 2 : 1;
	info->value.integer.min = 0;
	info->value.integer.max = 0x1f;
	return 0;
}

static int ac97_volume_get(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int codec = (ctl->private_value >> 24) & 1;
	int stereo = (ctl->private_value >> 16) & 1;
	unsigned int index = ctl->private_value & 0xff;
	u16 reg;

	mutex_lock(&chip->mutex);
	reg = oxygen_read_ac97(chip, codec, index);
	mutex_unlock(&chip->mutex);
	if (!stereo) {
		value->value.integer.value[0] = 31 - (reg & 0x1f);
	} else {
		value->value.integer.value[0] = 31 - ((reg >> 8) & 0x1f);
		value->value.integer.value[1] = 31 - (reg & 0x1f);
	}
	return 0;
}

static int ac97_volume_put(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int codec = (ctl->private_value >> 24) & 1;
	int stereo = (ctl->private_value >> 16) & 1;
	unsigned int index = ctl->private_value & 0xff;
	u16 oldreg, newreg;
	int change;

	mutex_lock(&chip->mutex);
	oldreg = oxygen_read_ac97(chip, codec, index);
	if (!stereo) {
		newreg = oldreg & ~0x1f;
		newreg |= 31 - (value->value.integer.value[0] & 0x1f);
	} else {
		newreg = oldreg & ~0x1f1f;
		newreg |= (31 - (value->value.integer.value[0] & 0x1f)) << 8;
		newreg |= 31 - (value->value.integer.value[1] & 0x1f);
	}
	change = newreg != oldreg;
	if (change)
		oxygen_write_ac97(chip, codec, index, newreg);
	mutex_unlock(&chip->mutex);
	return change;
}

static int mic_fmic_source_info(struct snd_kcontrol *ctl,
			   struct snd_ctl_elem_info *info)
{
	static const char *const names[] = { "Mic Jack", "Front Panel" };

	return snd_ctl_enum_info(info, 1, 2, names);
}

static int mic_fmic_source_get(struct snd_kcontrol *ctl,
			       struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;

	mutex_lock(&chip->mutex);
	value->value.enumerated.item[0] =
		!!(oxygen_read_ac97(chip, 0, CM9780_JACK) & CM9780_FMIC2MIC);
	mutex_unlock(&chip->mutex);
	return 0;
}

static int mic_fmic_source_put(struct snd_kcontrol *ctl,
			       struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u16 oldreg, newreg;
	int change;

	mutex_lock(&chip->mutex);
	oldreg = oxygen_read_ac97(chip, 0, CM9780_JACK);
	if (value->value.enumerated.item[0])
		newreg = oldreg | CM9780_FMIC2MIC;
	else
		newreg = oldreg & ~CM9780_FMIC2MIC;
	change = newreg != oldreg;
	if (change)
		oxygen_write_ac97(chip, 0, CM9780_JACK, newreg);
	mutex_unlock(&chip->mutex);
	return change;
}

static int ac97_fp_rec_volume_info(struct snd_kcontrol *ctl,
				   struct snd_ctl_elem_info *info)
{
	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	info->count = 2;
	info->value.integer.min = 0;
	info->value.integer.max = 7;
	return 0;
}

static int ac97_fp_rec_volume_get(struct snd_kcontrol *ctl,
				  struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u16 reg;

	mutex_lock(&chip->mutex);
	reg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
	mutex_unlock(&chip->mutex);
	value->value.integer.value[0] = reg & 7;
	value->value.integer.value[1] = (reg >> 8) & 7;
	return 0;
}

static int ac97_fp_rec_volume_put(struct snd_kcontrol *ctl,
				  struct snd_ctl_elem_value *value)
{
	struct oxygen *chip = ctl->private_data;
	u16 oldreg, newreg;
	int change;

	mutex_lock(&chip->mutex);
	oldreg = oxygen_read_ac97(chip, 1, AC97_REC_GAIN);
	newreg = oldreg & ~0x0707;
	newreg = newreg | (value->value.integer.value[0] & 7);
	newreg = newreg | ((value->value.integer.value[0] & 7) << 8);
	change = newreg != oldreg;
	if (change)
		oxygen_write_ac97(chip, 1, AC97_REC_GAIN, newreg);
	mutex_unlock(&chip->mutex);
	return change;
}

#define AC97_SWITCH(xname, codec, index, bitnr, invert) { \
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
		.name = xname, \
		.info = snd_ctl_boolean_mono_info, \
		.get = ac97_switch_get, \
		.put = ac97_switch_put, \
		.private_value = ((codec) << 24) | ((invert) << 16) | \
				 ((bitnr) << 8) | (index), \
	}
#define AC97_VOLUME(xname, codec, index, stereo) { \
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
		.name = xname, \
		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
			  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
		.info = ac97_volume_info, \
		.get = ac97_volume_get, \
		.put = ac97_volume_put, \
		.tlv = { .p = ac97_db_scale, }, \
		.private_value = ((codec) << 24) | ((stereo) << 16) | (index), \
	}

static DECLARE_TLV_DB_SCALE(monitor_db_scale, -600, 600, 0);
static DECLARE_TLV_DB_SCALE(ac97_db_scale, -3450, 150, 0);
static DECLARE_TLV_DB_SCALE(ac97_rec_db_scale, 0, 150, 0);

static const struct snd_kcontrol_new controls[] = {
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Master Playback Volume",
		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
		.info = dac_volume_info,
		.get = dac_volume_get,
		.put = dac_volume_put,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Master Playback Switch",
		.info = snd_ctl_boolean_mono_info,
		.get = dac_mute_get,
		.put = dac_mute_put,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Stereo Upmixing",
		.info = upmix_info,
		.get = upmix_get,
		.put = upmix_put,
	},
};

static const struct snd_kcontrol_new spdif_output_controls[] = {
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
		.info = snd_ctl_boolean_mono_info,
		.get = spdif_switch_get,
		.put = spdif_switch_put,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.device = 1,
		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, DEFAULT),
		.info = spdif_info,
		.get = spdif_default_get,
		.put = spdif_default_put,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.device = 1,
		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, CON_MASK),
		.access = SNDRV_CTL_ELEM_ACCESS_READ,
		.info = spdif_info,
		.get = spdif_mask_get,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.device = 1,
		.name = SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
			  SNDRV_CTL_ELEM_ACCESS_INACTIVE,
		.info = spdif_info,
		.get = spdif_pcm_get,
		.put = spdif_pcm_put,
	},
};

static const struct snd_kcontrol_new spdif_input_controls[] = {
	{
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.device = 1,
		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, MASK),
		.access = SNDRV_CTL_ELEM_ACCESS_READ,
		.info = spdif_info,
		.get = spdif_input_mask_get,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_PCM,
		.device = 1,
		.name = SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
		.access = SNDRV_CTL_ELEM_ACCESS_READ,
		.info = spdif_info,
		.get = spdif_input_default_get,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = SNDRV_CTL_NAME_IEC958("Loopback ", NONE, SWITCH),
		.info = snd_ctl_boolean_mono_info,
		.get = spdif_bit_switch_get,
		.put = spdif_bit_switch_put,
		.private_value = OXYGEN_SPDIF_LOOPBACK,
	},
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = SNDRV_CTL_NAME_IEC958("Validity Check ",CAPTURE,SWITCH),
		.info = snd_ctl_boolean_mono_info,
		.get = spdif_bit_switch_get,
		.put = spdif_bit_switch_put,
		.private_value = OXYGEN_SPDIF_SPDVALID,
	},
};

static const struct {
	unsigned int pcm_dev;
	struct snd_kcontrol_new controls[2];
} monitor_controls[] = {
	{
		.pcm_dev = CAPTURE_0_FROM_I2S_1,
		.controls = {
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Switch",
				.info = snd_ctl_boolean_mono_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_A,
			},
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Volume",
				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
				.info = monitor_volume_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_A_HALF_VOL
						| (1 << 8),
				.tlv = { .p = monitor_db_scale, },
			},
		},
	},
	{
		.pcm_dev = CAPTURE_0_FROM_I2S_2,
		.controls = {
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Switch",
				.info = snd_ctl_boolean_mono_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_B,
			},
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Volume",
				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
				.info = monitor_volume_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
						| (1 << 8),
				.tlv = { .p = monitor_db_scale, },
			},
		},
	},
	{
		.pcm_dev = CAPTURE_2_FROM_I2S_2,
		.controls = {
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Switch",
				.index = 1,
				.info = snd_ctl_boolean_mono_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_B,
			},
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Volume",
				.index = 1,
				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
				.info = monitor_volume_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_B_HALF_VOL
						| (1 << 8),
				.tlv = { .p = monitor_db_scale, },
			},
		},
	},
	{
		.pcm_dev = CAPTURE_3_FROM_I2S_3,
		.controls = {
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Switch",
				.index = 2,
				.info = snd_ctl_boolean_mono_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_C,
			},
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Analog Input Monitor Playback Volume",
				.index = 2,
				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
				.info = monitor_volume_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL
						| (1 << 8),
				.tlv = { .p = monitor_db_scale, },
			},
		},
	},
	{
		.pcm_dev = CAPTURE_1_FROM_SPDIF,
		.controls = {
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Digital Input Monitor Playback Switch",
				.info = snd_ctl_boolean_mono_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_C,
			},
			{
				.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
				.name = "Digital Input Monitor Playback Volume",
				.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
					  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
				.info = monitor_volume_info,
				.get = monitor_get,
				.put = monitor_put,
				.private_value = OXYGEN_ADC_MONITOR_C_HALF_VOL
						| (1 << 8),
				.tlv = { .p = monitor_db_scale, },
			},
		},
	},
};

static const struct snd_kcontrol_new ac97_controls[] = {
	AC97_VOLUME("Mic Capture Volume", 0, AC97_MIC, 0),
	AC97_SWITCH("Mic Capture Switch", 0, AC97_MIC, 15, 1),
	AC97_SWITCH("Mic Boost (+20dB)", 0, AC97_MIC, 6, 0),
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Mic Source Capture Enum",
		.info = mic_fmic_source_info,
		.get = mic_fmic_source_get,
		.put = mic_fmic_source_put,
	},
	AC97_SWITCH("Line Capture Switch", 0, AC97_LINE, 15, 1),
	AC97_VOLUME("CD Capture Volume", 0, AC97_CD, 1),
	AC97_SWITCH("CD Capture Switch", 0, AC97_CD, 15, 1),
	AC97_VOLUME("Aux Capture Volume", 0, AC97_AUX, 1),
	AC97_SWITCH("Aux Capture Switch", 0, AC97_AUX, 15, 1),
};

static const struct snd_kcontrol_new ac97_fp_controls[] = {
	AC97_VOLUME("Front Panel Playback Volume", 1, AC97_HEADPHONE, 1),
	AC97_SWITCH("Front Panel Playback Switch", 1, AC97_HEADPHONE, 15, 1),
	{
		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
		.name = "Front Panel Capture Volume",
		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
			  SNDRV_CTL_ELEM_ACCESS_TLV_READ,
		.info = ac97_fp_rec_volume_info,
		.get = ac97_fp_rec_volume_get,
		.put = ac97_fp_rec_volume_put,
		.tlv = { .p = ac97_rec_db_scale, },
	},
	AC97_SWITCH("Front Panel Capture Switch", 1, AC97_REC_GAIN, 15, 1),
};

static void oxygen_any_ctl_free(struct snd_kcontrol *ctl)
{
	struct oxygen *chip = ctl->private_data;
	unsigned int i;

	/* I'm too lazy to write a function for each control :-) */
	for (i = 0; i < ARRAY_SIZE(chip->controls); ++i)
		chip->controls[i] = NULL;
}

static int add_controls(struct oxygen *chip,
			const struct snd_kcontrol_new controls[],
			unsigned int count)
{
	static const char *const known_ctl_names[CONTROL_COUNT] = {
		[CONTROL_SPDIF_PCM] =
			SNDRV_CTL_NAME_IEC958("", PLAYBACK, PCM_STREAM),
		[CONTROL_SPDIF_INPUT_BITS] =
			SNDRV_CTL_NAME_IEC958("", CAPTURE, DEFAULT),
		[CONTROL_MIC_CAPTURE_SWITCH] = "Mic Capture Switch",
		[CONTROL_LINE_CAPTURE_SWITCH] = "Line Capture Switch",
		[CONTROL_CD_CAPTURE_SWITCH] = "CD Capture Switch",
		[CONTROL_AUX_CAPTURE_SWITCH] = "Aux Capture Switch",
	};
	unsigned int i, j;
	struct snd_kcontrol_new template;
	struct snd_kcontrol *ctl;
	int err;

	for (i = 0; i < count; ++i) {
		template = controls[i];
		if (chip->model.control_filter) {
			err = chip->model.control_filter(&template);
			if (err < 0)
				return err;
			if (err == 1)
				continue;
		}
		if (!strcmp(template.name, "Stereo Upmixing") &&
		    chip->model.dac_channels_pcm == 2)
			continue;
		if (!strcmp(template.name, "Mic Source Capture Enum") &&
		    !(chip->model.device_config & AC97_FMIC_SWITCH))
			continue;
		if (!strncmp(template.name, "CD Capture ", 11) &&
		    !(chip->model.device_config & AC97_CD_INPUT))
			continue;
		if (!strcmp(template.name, "Master Playback Volume") &&
		    chip->model.dac_tlv) {
			template.tlv.p = chip->model.dac_tlv;
			template.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
		}
		ctl = snd_ctl_new1(&template, chip);
		if (!ctl)
			return -ENOMEM;
		err = snd_ctl_add(chip->card, ctl);
		if (err < 0)
			return err;
		for (j = 0; j < CONTROL_COUNT; ++j)
			if (!strcmp(ctl->id.name, known_ctl_names[j])) {
				chip->controls[j] = ctl;
				ctl->private_free = oxygen_any_ctl_free;
			}
	}
	return 0;
}

int oxygen_mixer_init(struct oxygen *chip)
{
	unsigned int i;
	int err;

	err = add_controls(chip, controls, ARRAY_SIZE(controls));
	if (err < 0)
		return err;
	if (chip->model.device_config & PLAYBACK_1_TO_SPDIF) {
		err = add_controls(chip, spdif_output_controls,
				   ARRAY_SIZE(spdif_output_controls));
		if (err < 0)
			return err;
	}
	if (chip->model.device_config & CAPTURE_1_FROM_SPDIF) {
		err = add_controls(chip, spdif_input_controls,
				   ARRAY_SIZE(spdif_input_controls));
		if (err < 0)
			return err;
	}
	for (i = 0; i < ARRAY_SIZE(monitor_controls); ++i) {
		if (!(chip->model.device_config & monitor_controls[i].pcm_dev))
			continue;
		err = add_controls(chip, monitor_controls[i].controls,
				   ARRAY_SIZE(monitor_controls[i].controls));
		if (err < 0)
			return err;
	}
	if (chip->has_ac97_0) {
		err = add_controls(chip, ac97_controls,
				   ARRAY_SIZE(ac97_controls));
		if (err < 0)
			return err;
	}
	if (chip->has_ac97_1) {
		err = add_controls(chip, ac97_fp_controls,
				   ARRAY_SIZE(ac97_fp_controls));
		if (err < 0)
			return err;
	}
	return chip->model.mixer_init ? chip->model.mixer_init(chip) : 0;
}