Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
L
linux-davinci
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
linux
linux-davinci
Commits
88501ce1
Commit
88501ce1
authored
Jan 18, 2010
by
Takashi Iwai
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote branch 'alsa/devel' into topic/misc
parents
408bffd0
d1db38c0
Changes
9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1292 additions
and
16 deletions
+1292
-16
Documentation/sound/alsa/ALSA-Configuration.txt
Documentation/sound/alsa/ALSA-Configuration.txt
+1
-1
sound/core/seq/seq_timer.c
sound/core/seq/seq_timer.c
+13
-14
sound/pci/Kconfig
sound/pci/Kconfig
+1
-0
sound/pci/oxygen/Makefile
sound/pci/oxygen/Makefile
+1
-1
sound/pci/oxygen/virtuoso.c
sound/pci/oxygen/virtuoso.c
+3
-0
sound/pci/oxygen/wm8766.h
sound/pci/oxygen/wm8766.h
+73
-0
sound/pci/oxygen/wm8776.h
sound/pci/oxygen/wm8776.h
+177
-0
sound/pci/oxygen/xonar.h
sound/pci/oxygen/xonar.h
+2
-0
sound/pci/oxygen/xonar_wm87x6.c
sound/pci/oxygen/xonar_wm87x6.c
+1021
-0
No files found.
Documentation/sound/alsa/ALSA-Configuration.txt
View file @
88501ce1
...
@@ -1948,7 +1948,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
...
@@ -1948,7 +1948,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed.
-------------------
-------------------
Module for sound cards based on the Asus AV100/AV200 chips,
Module for sound cards based on the Asus AV100/AV200 chips,
i.e., Xonar D1, DX, D2, D2X, HDAV1.3 (Deluxe), Essence ST
i.e., Xonar D1, DX, D2, D2X,
DS,
HDAV1.3 (Deluxe), Essence ST
(Deluxe) and Essence STX.
(Deluxe) and Essence STX.
This module supports autoprobe and multiple cards.
This module supports autoprobe and multiple cards.
...
...
sound/core/seq/seq_timer.c
View file @
88501ce1
...
@@ -33,22 +33,21 @@
...
@@ -33,22 +33,21 @@
#define SKEW_BASE 0x10000
/* 16bit shift */
#define SKEW_BASE 0x10000
/* 16bit shift */
static
void
snd_seq_timer_set_tick_resolution
(
struct
snd_seq_timer_tick
*
tick
,
static
void
snd_seq_timer_set_tick_resolution
(
struct
snd_seq_timer
*
tmr
)
int
tempo
,
int
ppq
)
{
{
if
(
tempo
<
1000000
)
if
(
t
mr
->
t
empo
<
1000000
)
t
ick
->
resolution
=
(
tempo
*
1000
)
/
ppq
;
t
mr
->
tick
.
resolution
=
(
tmr
->
tempo
*
1000
)
/
tmr
->
ppq
;
else
{
else
{
/* might overflow.. */
/* might overflow.. */
unsigned
int
s
;
unsigned
int
s
;
s
=
t
empo
%
ppq
;
s
=
t
mr
->
tempo
%
tmr
->
ppq
;
s
=
(
s
*
1000
)
/
ppq
;
s
=
(
s
*
1000
)
/
tmr
->
ppq
;
t
ick
->
resolution
=
(
tempo
/
ppq
)
*
1000
;
t
mr
->
tick
.
resolution
=
(
tmr
->
tempo
/
tmr
->
ppq
)
*
1000
;
t
ick
->
resolution
+=
s
;
t
mr
->
tick
.
resolution
+=
s
;
}
}
if
(
t
ick
->
resolution
<=
0
)
if
(
t
mr
->
tick
.
resolution
<=
0
)
t
ick
->
resolution
=
1
;
t
mr
->
tick
.
resolution
=
1
;
snd_seq_timer_update_tick
(
tick
,
0
);
snd_seq_timer_update_tick
(
&
tmr
->
tick
,
0
);
}
}
/* create new timer (constructor) */
/* create new timer (constructor) */
...
@@ -96,7 +95,7 @@ void snd_seq_timer_defaults(struct snd_seq_timer * tmr)
...
@@ -96,7 +95,7 @@ void snd_seq_timer_defaults(struct snd_seq_timer * tmr)
/* setup defaults */
/* setup defaults */
tmr
->
ppq
=
96
;
/* 96 PPQ */
tmr
->
ppq
=
96
;
/* 96 PPQ */
tmr
->
tempo
=
500000
;
/* 120 BPM */
tmr
->
tempo
=
500000
;
/* 120 BPM */
snd_seq_timer_set_tick_resolution
(
&
tmr
->
tick
,
tmr
->
tempo
,
tmr
->
ppq
);
snd_seq_timer_set_tick_resolution
(
tmr
);
tmr
->
running
=
0
;
tmr
->
running
=
0
;
tmr
->
type
=
SNDRV_SEQ_TIMER_ALSA
;
tmr
->
type
=
SNDRV_SEQ_TIMER_ALSA
;
...
@@ -180,7 +179,7 @@ int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo)
...
@@ -180,7 +179,7 @@ int snd_seq_timer_set_tempo(struct snd_seq_timer * tmr, int tempo)
spin_lock_irqsave
(
&
tmr
->
lock
,
flags
);
spin_lock_irqsave
(
&
tmr
->
lock
,
flags
);
if
((
unsigned
int
)
tempo
!=
tmr
->
tempo
)
{
if
((
unsigned
int
)
tempo
!=
tmr
->
tempo
)
{
tmr
->
tempo
=
tempo
;
tmr
->
tempo
=
tempo
;
snd_seq_timer_set_tick_resolution
(
&
tmr
->
tick
,
tmr
->
tempo
,
tmr
->
ppq
);
snd_seq_timer_set_tick_resolution
(
tmr
);
}
}
spin_unlock_irqrestore
(
&
tmr
->
lock
,
flags
);
spin_unlock_irqrestore
(
&
tmr
->
lock
,
flags
);
return
0
;
return
0
;
...
@@ -205,7 +204,7 @@ int snd_seq_timer_set_ppq(struct snd_seq_timer * tmr, int ppq)
...
@@ -205,7 +204,7 @@ int snd_seq_timer_set_ppq(struct snd_seq_timer * tmr, int ppq)
}
}
tmr
->
ppq
=
ppq
;
tmr
->
ppq
=
ppq
;
snd_seq_timer_set_tick_resolution
(
&
tmr
->
tick
,
tmr
->
tempo
,
tmr
->
ppq
);
snd_seq_timer_set_tick_resolution
(
tmr
);
spin_unlock_irqrestore
(
&
tmr
->
lock
,
flags
);
spin_unlock_irqrestore
(
&
tmr
->
lock
,
flags
);
return
0
;
return
0
;
}
}
...
...
sound/pci/Kconfig
View file @
88501ce1
...
@@ -789,6 +789,7 @@ config SND_VIRTUOSO
...
@@ -789,6 +789,7 @@ config SND_VIRTUOSO
Say Y here to include support for sound cards based on the
Say Y here to include support for sound cards based on the
Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X,
Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X,
Essence ST (Deluxe), and Essence STX.
Essence ST (Deluxe), and Essence STX.
Support for the DS is experimental.
Support for the HDAV1.3 (Deluxe) is very experimental.
Support for the HDAV1.3 (Deluxe) is very experimental.
To compile this driver as a module, choose M here: the module
To compile this driver as a module, choose M here: the module
...
...
sound/pci/oxygen/Makefile
View file @
88501ce1
...
@@ -2,7 +2,7 @@ snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
...
@@ -2,7 +2,7 @@ snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o
snd-hifier-objs
:=
hifier.o
snd-hifier-objs
:=
hifier.o
snd-oxygen-objs
:=
oxygen.o
snd-oxygen-objs
:=
oxygen.o
snd-virtuoso-objs
:=
virtuoso.o xonar_lib.o
\
snd-virtuoso-objs
:=
virtuoso.o xonar_lib.o
\
xonar_pcm179x.o xonar_cs43xx.o xonar_hdmi.o
xonar_pcm179x.o xonar_cs43xx.o xonar_
wm87x6.o xonar_
hdmi.o
obj-$(CONFIG_SND_OXYGEN_LIB)
+=
snd-oxygen-lib.o
obj-$(CONFIG_SND_OXYGEN_LIB)
+=
snd-oxygen-lib.o
obj-$(CONFIG_SND_HIFIER)
+=
snd-hifier.o
obj-$(CONFIG_SND_HIFIER)
+=
snd-hifier.o
...
...
sound/pci/oxygen/virtuoso.c
View file @
88501ce1
...
@@ -49,6 +49,7 @@ static struct pci_device_id xonar_ids[] __devinitdata = {
...
@@ -49,6 +49,7 @@ static struct pci_device_id xonar_ids[] __devinitdata = {
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x834f
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x834f
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x835c
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x835c
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x835d
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x835d
)
},
{
OXYGEN_PCI_SUBID
(
0x1043
,
0x838e
)
},
{
OXYGEN_PCI_SUBID_BROKEN_EEPROM
},
{
OXYGEN_PCI_SUBID_BROKEN_EEPROM
},
{
}
{
}
};
};
...
@@ -61,6 +62,8 @@ static int __devinit get_xonar_model(struct oxygen *chip,
...
@@ -61,6 +62,8 @@ static int __devinit get_xonar_model(struct oxygen *chip,
return
0
;
return
0
;
if
(
get_xonar_cs43xx_model
(
chip
,
id
)
>=
0
)
if
(
get_xonar_cs43xx_model
(
chip
,
id
)
>=
0
)
return
0
;
return
0
;
if
(
get_xonar_wm87x6_model
(
chip
,
id
)
>=
0
)
return
0
;
return
-
EINVAL
;
return
-
EINVAL
;
}
}
...
...
sound/pci/oxygen/wm8766.h
0 → 100644
View file @
88501ce1
#ifndef WM8766_H_INCLUDED
#define WM8766_H_INCLUDED
#define WM8766_LDA1 0x00
#define WM8766_RDA1 0x01
#define WM8766_DAC_CTRL 0x02
#define WM8766_INT_CTRL 0x03
#define WM8766_LDA2 0x04
#define WM8766_RDA2 0x05
#define WM8766_LDA3 0x06
#define WM8766_RDA3 0x07
#define WM8766_MASTDA 0x08
#define WM8766_DAC_CTRL2 0x09
#define WM8766_DAC_CTRL3 0x0a
#define WM8766_MUTE1 0x0c
#define WM8766_MUTE2 0x0f
#define WM8766_RESET 0x1f
/* LDAx/RDAx/MASTDA */
#define WM8766_ATT_MASK 0x0ff
#define WM8766_UPDATE 0x100
/* DAC_CTRL */
#define WM8766_MUTEALL 0x001
#define WM8766_DEEMPALL 0x002
#define WM8766_PWDN 0x004
#define WM8766_ATC 0x008
#define WM8766_IZD 0x010
#define WM8766_PL_LEFT_MASK 0x060
#define WM8766_PL_LEFT_MUTE 0x000
#define WM8766_PL_LEFT_LEFT 0x020
#define WM8766_PL_LEFT_RIGHT 0x040
#define WM8766_PL_LEFT_LRMIX 0x060
#define WM8766_PL_RIGHT_MASK 0x180
#define WM8766_PL_RIGHT_MUTE 0x000
#define WM8766_PL_RIGHT_LEFT 0x080
#define WM8766_PL_RIGHT_RIGHT 0x100
#define WM8766_PL_RIGHT_LRMIX 0x180
/* INT_CTRL */
#define WM8766_FMT_MASK 0x003
#define WM8766_FMT_RJUST 0x000
#define WM8766_FMT_LJUST 0x001
#define WM8766_FMT_I2S 0x002
#define WM8766_FMT_DSP 0x003
#define WM8766_LRP 0x004
#define WM8766_BCP 0x008
#define WM8766_IWL_MASK 0x030
#define WM8766_IWL_16 0x000
#define WM8766_IWL_20 0x010
#define WM8766_IWL_24 0x020
#define WM8766_IWL_32 0x030
#define WM8766_PHASE_MASK 0x1c0
/* DAC_CTRL2 */
#define WM8766_ZCD 0x001
#define WM8766_DZFM_MASK 0x006
#define WM8766_DMUTE_MASK 0x038
#define WM8766_DEEMP_MASK 0x1c0
/* DAC_CTRL3 */
#define WM8766_DACPD_MASK 0x00e
#define WM8766_PWRDNALL 0x010
#define WM8766_MS 0x020
#define WM8766_RATE_MASK 0x1c0
#define WM8766_RATE_128 0x000
#define WM8766_RATE_192 0x040
#define WM8766_RATE_256 0x080
#define WM8766_RATE_384 0x0c0
#define WM8766_RATE_512 0x100
#define WM8766_RATE_768 0x140
/* MUTE1 */
#define WM8766_MPD1 0x040
/* MUTE2 */
#define WM8766_MPD2 0x020
#endif
sound/pci/oxygen/wm8776.h
0 → 100644
View file @
88501ce1
#ifndef WM8776_H_INCLUDED
#define WM8776_H_INCLUDED
/*
* the following register names are from:
* wm8776.h -- WM8776 ASoC driver
*
* Copyright 2009 Wolfson Microelectronics plc
*
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#define WM8776_HPLVOL 0x00
#define WM8776_HPRVOL 0x01
#define WM8776_HPMASTER 0x02
#define WM8776_DACLVOL 0x03
#define WM8776_DACRVOL 0x04
#define WM8776_DACMASTER 0x05
#define WM8776_PHASESWAP 0x06
#define WM8776_DACCTRL1 0x07
#define WM8776_DACMUTE 0x08
#define WM8776_DACCTRL2 0x09
#define WM8776_DACIFCTRL 0x0a
#define WM8776_ADCIFCTRL 0x0b
#define WM8776_MSTRCTRL 0x0c
#define WM8776_PWRDOWN 0x0d
#define WM8776_ADCLVOL 0x0e
#define WM8776_ADCRVOL 0x0f
#define WM8776_ALCCTRL1 0x10
#define WM8776_ALCCTRL2 0x11
#define WM8776_ALCCTRL3 0x12
#define WM8776_NOISEGATE 0x13
#define WM8776_LIMITER 0x14
#define WM8776_ADCMUX 0x15
#define WM8776_OUTMUX 0x16
#define WM8776_RESET 0x17
/* HPLVOL/HPRVOL/HPMASTER */
#define WM8776_HPATT_MASK 0x07f
#define WM8776_HPZCEN 0x080
#define WM8776_UPDATE 0x100
/* DACLVOL/DACRVOL/DACMASTER */
#define WM8776_DATT_MASK 0x0ff
/*#define WM8776_UPDATE 0x100*/
/* PHASESWAP */
#define WM8776_PH_MASK 0x003
/* DACCTRL1 */
#define WM8776_DZCEN 0x001
#define WM8776_ATC 0x002
#define WM8776_IZD 0x004
#define WM8776_TOD 0x008
#define WM8776_PL_LEFT_MASK 0x030
#define WM8776_PL_LEFT_MUTE 0x000
#define WM8776_PL_LEFT_LEFT 0x010
#define WM8776_PL_LEFT_RIGHT 0x020
#define WM8776_PL_LEFT_LRMIX 0x030
#define WM8776_PL_RIGHT_MASK 0x0c0
#define WM8776_PL_RIGHT_MUTE 0x000
#define WM8776_PL_RIGHT_LEFT 0x040
#define WM8776_PL_RIGHT_RIGHT 0x080
#define WM8776_PL_RIGHT_LRMIX 0x0c0
/* DACMUTE */
#define WM8776_DMUTE 0x001
/* DACCTRL2 */
#define WM8776_DEEMPH 0x001
#define WM8776_DZFM_MASK 0x006
#define WM8776_DZFM_NONE 0x000
#define WM8776_DZFM_LR 0x002
#define WM8776_DZFM_BOTH 0x004
#define WM8776_DZFM_EITHER 0x006
/* DACIFCTRL */
#define WM8776_DACFMT_MASK 0x003
#define WM8776_DACFMT_RJUST 0x000
#define WM8776_DACFMT_LJUST 0x001
#define WM8776_DACFMT_I2S 0x002
#define WM8776_DACFMT_DSP 0x003
#define WM8776_DACLRP 0x004
#define WM8776_DACBCP 0x008
#define WM8776_DACWL_MASK 0x030
#define WM8776_DACWL_16 0x000
#define WM8776_DACWL_20 0x010
#define WM8776_DACWL_24 0x020
#define WM8776_DACWL_32 0x030
/* ADCIFCTRL */
#define WM8776_ADCFMT_MASK 0x003
#define WM8776_ADCFMT_RJUST 0x000
#define WM8776_ADCFMT_LJUST 0x001
#define WM8776_ADCFMT_I2S 0x002
#define WM8776_ADCFMT_DSP 0x003
#define WM8776_ADCLRP 0x004
#define WM8776_ADCBCP 0x008
#define WM8776_ADCWL_MASK 0x030
#define WM8776_ADCWL_16 0x000
#define WM8776_ADCWL_20 0x010
#define WM8776_ADCWL_24 0x020
#define WM8776_ADCWL_32 0x030
#define WM8776_ADCMCLK 0x040
#define WM8776_ADCHPD 0x100
/* MSTRCTRL */
#define WM8776_ADCRATE_MASK 0x007
#define WM8776_ADCRATE_256 0x002
#define WM8776_ADCRATE_384 0x003
#define WM8776_ADCRATE_512 0x004
#define WM8776_ADCRATE_768 0x005
#define WM8776_ADCOSR 0x008
#define WM8776_DACRATE_MASK 0x070
#define WM8776_DACRATE_128 0x000
#define WM8776_DACRATE_192 0x010
#define WM8776_DACRATE_256 0x020
#define WM8776_DACRATE_384 0x030
#define WM8776_DACRATE_512 0x040
#define WM8776_DACRATE_768 0x050
#define WM8776_DACMS 0x080
#define WM8776_ADCMS 0x100
/* PWRDOWN */
#define WM8776_PDWN 0x001
#define WM8776_ADCPD 0x002
#define WM8776_DACPD 0x004
#define WM8776_HPPD 0x008
#define WM8776_AINPD 0x040
/* ADCLVOL/ADCRVOL */
#define WM8776_AGMASK 0x0ff
#define WM8776_ZCA 0x100
/* ALCCTRL1 */
#define WM8776_LCT_MASK 0x00f
#define WM8776_MAXGAIN_MASK 0x070
#define WM8776_LCSEL_MASK 0x180
#define WM8776_LCSEL_LIMITER 0x000
#define WM8776_LCSEL_ALC_RIGHT 0x080
#define WM8776_LCSEL_ALC_LEFT 0x100
#define WM8776_LCSEL_ALC_STEREO 0x180
/* ALCCTRL2 */
#define WM8776_HLD_MASK 0x00f
#define WM8776_ALCZC 0x080
#define WM8776_LCEN 0x100
/* ALCCTRL3 */
#define WM8776_ATK_MASK 0x00f
#define WM8776_DCY_MASK 0x0f0
/* NOISEGATE */
#define WM8776_NGAT 0x001
#define WM8776_NGTH_MASK 0x01c
/* LIMITER */
#define WM8776_MAXATTEN_MASK 0x00f
#define WM8776_TRANWIN_MASK 0x070
/* ADCMUX */
#define WM8776_AMX_MASK 0x01f
#define WM8776_MUTERA 0x040
#define WM8776_MUTELA 0x080
#define WM8776_LRBOTH 0x100
/* OUTMUX */
#define WM8776_MX_DAC 0x001
#define WM8776_MX_AUX 0x002
#define WM8776_MX_BYPASS 0x004
#endif
sound/pci/oxygen/xonar.h
View file @
88501ce1
...
@@ -35,6 +35,8 @@ int get_xonar_pcm179x_model(struct oxygen *chip,
...
@@ -35,6 +35,8 @@ int get_xonar_pcm179x_model(struct oxygen *chip,
const
struct
pci_device_id
*
id
);
const
struct
pci_device_id
*
id
);
int
get_xonar_cs43xx_model
(
struct
oxygen
*
chip
,
int
get_xonar_cs43xx_model
(
struct
oxygen
*
chip
,
const
struct
pci_device_id
*
id
);
const
struct
pci_device_id
*
id
);
int
get_xonar_wm87x6_model
(
struct
oxygen
*
chip
,
const
struct
pci_device_id
*
id
);
/* HDMI helper functions */
/* HDMI helper functions */
...
...
sound/pci/oxygen/xonar_wm87x6.c
0 → 100644
View file @
88501ce1
/*
* card driver for models with WM8776/WM8766 DACs (Xonar DS)
*
* 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, see <http://www.gnu.org/licenses/>.
*/
/*
* Xonar DS
* --------
*
* CMI8788:
*
* SPI 0 -> WM8766 (surround, center/LFE, back)
* SPI 1 -> WM8776 (front, input)
*
* GPIO 4 <- headphone detect
* GPIO 6 -> route input jack to input 1/2 (1/0)
* GPIO 7 -> enable output to speakers
* GPIO 8 -> enable output to speakers
*/
#include <linux/pci.h>
#include <linux/delay.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include "xonar.h"
#include "wm8776.h"
#include "wm8766.h"
#define GPIO_DS_HP_DETECT 0x0010
#define GPIO_DS_INPUT_ROUTE 0x0040
#define GPIO_DS_OUTPUT_ENABLE 0x0180
#define LC_CONTROL_LIMITER 0x40000000
#define LC_CONTROL_ALC 0x20000000
struct
xonar_wm87x6
{
struct
xonar_generic
generic
;
u16
wm8776_regs
[
0x17
];
u16
wm8766_regs
[
0x10
];
struct
snd_kcontrol
*
lc_controls
[
13
];
};
static
void
wm8776_write
(
struct
oxygen
*
chip
,
unsigned
int
reg
,
unsigned
int
value
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
oxygen_write_spi
(
chip
,
OXYGEN_SPI_TRIGGER
|
OXYGEN_SPI_DATA_LENGTH_2
|
OXYGEN_SPI_CLOCK_160
|
(
1
<<
OXYGEN_SPI_CODEC_SHIFT
)
|
OXYGEN_SPI_CEN_LATCH_CLOCK_LO
,
(
reg
<<
9
)
|
value
);
if
(
reg
<
ARRAY_SIZE
(
data
->
wm8776_regs
))
{
if
(
reg
>=
WM8776_HPLVOL
||
reg
<=
WM8776_DACMASTER
)
value
&=
~
WM8776_UPDATE
;
data
->
wm8776_regs
[
reg
]
=
value
;
}
}
static
void
wm8776_write_cached
(
struct
oxygen
*
chip
,
unsigned
int
reg
,
unsigned
int
value
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
if
(
reg
>=
ARRAY_SIZE
(
data
->
wm8776_regs
)
||
value
!=
data
->
wm8776_regs
[
reg
])
wm8776_write
(
chip
,
reg
,
value
);
}
static
void
wm8766_write
(
struct
oxygen
*
chip
,
unsigned
int
reg
,
unsigned
int
value
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
oxygen_write_spi
(
chip
,
OXYGEN_SPI_TRIGGER
|
OXYGEN_SPI_DATA_LENGTH_2
|
OXYGEN_SPI_CLOCK_160
|
(
0
<<
OXYGEN_SPI_CODEC_SHIFT
)
|
OXYGEN_SPI_CEN_LATCH_CLOCK_LO
,
(
reg
<<
9
)
|
value
);
if
(
reg
<
ARRAY_SIZE
(
data
->
wm8766_regs
))
data
->
wm8766_regs
[
reg
]
=
value
;
}
static
void
wm8766_write_cached
(
struct
oxygen
*
chip
,
unsigned
int
reg
,
unsigned
int
value
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
if
(
reg
>=
ARRAY_SIZE
(
data
->
wm8766_regs
)
||
value
!=
data
->
wm8766_regs
[
reg
])
{
if
((
reg
>=
WM8766_LDA1
&&
reg
<=
WM8766_RDA1
)
||
(
reg
>=
WM8766_LDA2
&&
reg
<=
WM8766_MASTDA
))
value
&=
~
WM8766_UPDATE
;
wm8766_write
(
chip
,
reg
,
value
);
}
}
static
void
wm8776_registers_init
(
struct
oxygen
*
chip
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
wm8776_write
(
chip
,
WM8776_RESET
,
0
);
wm8776_write
(
chip
,
WM8776_DACCTRL1
,
WM8776_DZCEN
|
WM8776_PL_LEFT_LEFT
|
WM8776_PL_RIGHT_RIGHT
);
wm8776_write
(
chip
,
WM8776_DACMUTE
,
chip
->
dac_mute
?
WM8776_DMUTE
:
0
);
wm8776_write
(
chip
,
WM8776_DACIFCTRL
,
WM8776_DACFMT_LJUST
|
WM8776_DACWL_24
);
wm8776_write
(
chip
,
WM8776_ADCIFCTRL
,
data
->
wm8776_regs
[
WM8776_ADCIFCTRL
]);
wm8776_write
(
chip
,
WM8776_MSTRCTRL
,
data
->
wm8776_regs
[
WM8776_MSTRCTRL
]);
wm8776_write
(
chip
,
WM8776_PWRDOWN
,
data
->
wm8776_regs
[
WM8776_PWRDOWN
]);
wm8776_write
(
chip
,
WM8776_HPLVOL
,
data
->
wm8776_regs
[
WM8776_HPLVOL
]);
wm8776_write
(
chip
,
WM8776_HPRVOL
,
data
->
wm8776_regs
[
WM8776_HPRVOL
]
|
WM8776_UPDATE
);
wm8776_write
(
chip
,
WM8776_ADCLVOL
,
data
->
wm8776_regs
[
WM8776_ADCLVOL
]);
wm8776_write
(
chip
,
WM8776_ADCRVOL
,
data
->
wm8776_regs
[
WM8776_ADCRVOL
]);
wm8776_write
(
chip
,
WM8776_ADCMUX
,
data
->
wm8776_regs
[
WM8776_ADCMUX
]);
wm8776_write
(
chip
,
WM8776_DACLVOL
,
chip
->
dac_volume
[
0
]);
wm8776_write
(
chip
,
WM8776_DACRVOL
,
chip
->
dac_volume
[
1
]
|
WM8776_UPDATE
);
}
static
void
wm8766_registers_init
(
struct
oxygen
*
chip
)
{
wm8766_write
(
chip
,
WM8766_RESET
,
0
);
wm8766_write
(
chip
,
WM8766_INT_CTRL
,
WM8766_FMT_LJUST
|
WM8766_IWL_24
);
wm8766_write
(
chip
,
WM8766_DAC_CTRL2
,
WM8766_ZCD
|
(
chip
->
dac_mute
?
WM8766_DMUTE_MASK
:
0
));
wm8766_write
(
chip
,
WM8766_LDA1
,
chip
->
dac_volume
[
2
]);
wm8766_write
(
chip
,
WM8766_RDA1
,
chip
->
dac_volume
[
3
]);
wm8766_write
(
chip
,
WM8766_LDA2
,
chip
->
dac_volume
[
4
]);
wm8766_write
(
chip
,
WM8766_RDA2
,
chip
->
dac_volume
[
5
]);
wm8766_write
(
chip
,
WM8766_LDA3
,
chip
->
dac_volume
[
6
]);
wm8766_write
(
chip
,
WM8766_RDA3
,
chip
->
dac_volume
[
7
]
|
WM8766_UPDATE
);
}
static
void
wm8776_init
(
struct
oxygen
*
chip
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
data
->
wm8776_regs
[
WM8776_HPLVOL
]
=
(
0x79
-
60
)
|
WM8776_HPZCEN
;
data
->
wm8776_regs
[
WM8776_HPRVOL
]
=
(
0x79
-
60
)
|
WM8776_HPZCEN
;
data
->
wm8776_regs
[
WM8776_ADCIFCTRL
]
=
WM8776_ADCFMT_LJUST
|
WM8776_ADCWL_24
|
WM8776_ADCMCLK
;
data
->
wm8776_regs
[
WM8776_MSTRCTRL
]
=
WM8776_ADCRATE_256
|
WM8776_DACRATE_256
;
data
->
wm8776_regs
[
WM8776_PWRDOWN
]
=
WM8776_HPPD
;
data
->
wm8776_regs
[
WM8776_ADCLVOL
]
=
0xa5
|
WM8776_ZCA
;
data
->
wm8776_regs
[
WM8776_ADCRVOL
]
=
0xa5
|
WM8776_ZCA
;
data
->
wm8776_regs
[
WM8776_ADCMUX
]
=
0x001
;
wm8776_registers_init
(
chip
);
}
static
void
xonar_ds_init
(
struct
oxygen
*
chip
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
data
->
generic
.
anti_pop_delay
=
300
;
data
->
generic
.
output_enable_bit
=
GPIO_DS_OUTPUT_ENABLE
;
wm8776_init
(
chip
);
wm8766_registers_init
(
chip
);
oxygen_write16_masked
(
chip
,
OXYGEN_GPIO_CONTROL
,
GPIO_DS_INPUT_ROUTE
,
GPIO_DS_HP_DETECT
|
GPIO_DS_INPUT_ROUTE
);
oxygen_set_bits16
(
chip
,
OXYGEN_GPIO_DATA
,
GPIO_DS_INPUT_ROUTE
);
oxygen_set_bits16
(
chip
,
OXYGEN_GPIO_INTERRUPT_MASK
,
GPIO_DS_HP_DETECT
);
chip
->
interrupt_mask
|=
OXYGEN_INT_GPIO
;
xonar_enable_output
(
chip
);
snd_component_add
(
chip
->
card
,
"WM8776"
);
snd_component_add
(
chip
->
card
,
"WM8766"
);
}
static
void
xonar_ds_cleanup
(
struct
oxygen
*
chip
)
{
xonar_disable_output
(
chip
);
}
static
void
xonar_ds_suspend
(
struct
oxygen
*
chip
)
{
xonar_ds_cleanup
(
chip
);
}
static
void
xonar_ds_resume
(
struct
oxygen
*
chip
)
{
wm8776_registers_init
(
chip
);
wm8766_registers_init
(
chip
);
xonar_enable_output
(
chip
);
}
static
void
wm8776_adc_hardware_filter
(
unsigned
int
channel
,
struct
snd_pcm_hardware
*
hardware
)
{
if
(
channel
==
PCM_A
)
{
hardware
->
rates
=
SNDRV_PCM_RATE_32000
|
SNDRV_PCM_RATE_44100
|
SNDRV_PCM_RATE_48000
|
SNDRV_PCM_RATE_64000
|
SNDRV_PCM_RATE_88200
|
SNDRV_PCM_RATE_96000
;
hardware
->
rate_max
=
96000
;
}
}
static
void
set_wm87x6_dac_params
(
struct
oxygen
*
chip
,
struct
snd_pcm_hw_params
*
params
)
{
}
static
void
set_wm8776_adc_params
(
struct
oxygen
*
chip
,
struct
snd_pcm_hw_params
*
params
)
{
u16
reg
;
reg
=
WM8776_ADCRATE_256
|
WM8776_DACRATE_256
;
if
(
params_rate
(
params
)
>
48000
)
reg
|=
WM8776_ADCOSR
;
wm8776_write_cached
(
chip
,
WM8776_MSTRCTRL
,
reg
);
}
static
void
update_wm8776_volume
(
struct
oxygen
*
chip
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
u8
to_change
;
if
(
chip
->
dac_volume
[
0
]
==
chip
->
dac_volume
[
1
])
{
if
(
chip
->
dac_volume
[
0
]
!=
data
->
wm8776_regs
[
WM8776_DACLVOL
]
||
chip
->
dac_volume
[
1
]
!=
data
->
wm8776_regs
[
WM8776_DACRVOL
])
{
wm8776_write
(
chip
,
WM8776_DACMASTER
,
chip
->
dac_volume
[
0
]
|
WM8776_UPDATE
);
data
->
wm8776_regs
[
WM8776_DACLVOL
]
=
chip
->
dac_volume
[
0
];
data
->
wm8776_regs
[
WM8776_DACRVOL
]
=
chip
->
dac_volume
[
0
];
}
}
else
{
to_change
=
(
chip
->
dac_volume
[
0
]
!=
data
->
wm8776_regs
[
WM8776_DACLVOL
])
<<
0
;
to_change
|=
(
chip
->
dac_volume
[
1
]
!=
data
->
wm8776_regs
[
WM8776_DACLVOL
])
<<
1
;
if
(
to_change
&
1
)
wm8776_write
(
chip
,
WM8776_DACLVOL
,
chip
->
dac_volume
[
0
]
|
((
to_change
&
2
)
?
0
:
WM8776_UPDATE
));
if
(
to_change
&
2
)
wm8776_write
(
chip
,
WM8776_DACRVOL
,
chip
->
dac_volume
[
1
]
|
WM8776_UPDATE
);
}
}
static
void
update_wm87x6_volume
(
struct
oxygen
*
chip
)
{
static
const
u8
wm8766_regs
[
6
]
=
{
WM8766_LDA1
,
WM8766_RDA1
,
WM8766_LDA2
,
WM8766_RDA2
,
WM8766_LDA3
,
WM8766_RDA3
,
};
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
i
;
u8
to_change
;
update_wm8776_volume
(
chip
);
if
(
chip
->
dac_volume
[
2
]
==
chip
->
dac_volume
[
3
]
&&
chip
->
dac_volume
[
2
]
==
chip
->
dac_volume
[
4
]
&&
chip
->
dac_volume
[
2
]
==
chip
->
dac_volume
[
5
]
&&
chip
->
dac_volume
[
2
]
==
chip
->
dac_volume
[
6
]
&&
chip
->
dac_volume
[
2
]
==
chip
->
dac_volume
[
7
])
{
to_change
=
0
;
for
(
i
=
0
;
i
<
6
;
++
i
)
if
(
chip
->
dac_volume
[
2
]
!=
data
->
wm8766_regs
[
wm8766_regs
[
i
]])
to_change
=
1
;
if
(
to_change
)
{
wm8766_write
(
chip
,
WM8766_MASTDA
,
chip
->
dac_volume
[
2
]
|
WM8766_UPDATE
);
for
(
i
=
0
;
i
<
6
;
++
i
)
data
->
wm8766_regs
[
wm8766_regs
[
i
]]
=
chip
->
dac_volume
[
2
];
}
}
else
{
to_change
=
0
;
for
(
i
=
0
;
i
<
6
;
++
i
)
to_change
|=
(
chip
->
dac_volume
[
2
+
i
]
!=
data
->
wm8766_regs
[
wm8766_regs
[
i
]])
<<
i
;
for
(
i
=
0
;
i
<
6
;
++
i
)
if
(
to_change
&
(
1
<<
i
))
wm8766_write
(
chip
,
wm8766_regs
[
i
],
chip
->
dac_volume
[
2
+
i
]
|
((
to_change
&
(
0x3e
<<
i
))
?
0
:
WM8766_UPDATE
));
}
}
static
void
update_wm8776_mute
(
struct
oxygen
*
chip
)
{
wm8776_write_cached
(
chip
,
WM8776_DACMUTE
,
chip
->
dac_mute
?
WM8776_DMUTE
:
0
);
}
static
void
update_wm87x6_mute
(
struct
oxygen
*
chip
)
{
update_wm8776_mute
(
chip
);
wm8766_write_cached
(
chip
,
WM8766_DAC_CTRL2
,
WM8766_ZCD
|
(
chip
->
dac_mute
?
WM8766_DMUTE_MASK
:
0
));
}
static
void
xonar_ds_gpio_changed
(
struct
oxygen
*
chip
)
{
u16
bits
;
bits
=
oxygen_read16
(
chip
,
OXYGEN_GPIO_DATA
);
snd_printk
(
KERN_INFO
"HP detect: %d
\n
"
,
!!
(
bits
&
GPIO_DS_HP_DETECT
));
}
static
int
wm8776_bit_switch_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
u16
bit
=
ctl
->
private_value
&
0xffff
;
unsigned
int
reg_index
=
(
ctl
->
private_value
>>
16
)
&
0xff
;
bool
invert
=
(
ctl
->
private_value
>>
24
)
&
1
;
value
->
value
.
integer
.
value
[
0
]
=
((
data
->
wm8776_regs
[
reg_index
]
&
bit
)
!=
0
)
^
invert
;
return
0
;
}
static
int
wm8776_bit_switch_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
u16
bit
=
ctl
->
private_value
&
0xffff
;
u16
reg_value
;
unsigned
int
reg_index
=
(
ctl
->
private_value
>>
16
)
&
0xff
;
bool
invert
=
(
ctl
->
private_value
>>
24
)
&
1
;
int
changed
;
mutex_lock
(
&
chip
->
mutex
);
reg_value
=
data
->
wm8776_regs
[
reg_index
]
&
~
bit
;
if
(
value
->
value
.
integer
.
value
[
0
]
^
invert
)
reg_value
|=
bit
;
changed
=
reg_value
!=
data
->
wm8776_regs
[
reg_index
];
if
(
changed
)
wm8776_write
(
chip
,
reg_index
,
reg_value
);
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
static
int
wm8776_field_enum_info
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_info
*
info
)
{
static
const
char
*
const
hld
[
16
]
=
{
"0 ms"
,
"2.67 ms"
,
"5.33 ms"
,
"10.6 ms"
,
"21.3 ms"
,
"42.7 ms"
,
"85.3 ms"
,
"171 ms"
,
"341 ms"
,
"683 ms"
,
"1.37 s"
,
"2.73 s"
,
"5.46 s"
,
"10.9 s"
,
"21.8 s"
,
"43.7 s"
,
};
static
const
char
*
const
atk_lim
[
11
]
=
{
"0.25 ms"
,
"0.5 ms"
,
"1 ms"
,
"2 ms"
,
"4 ms"
,
"8 ms"
,
"16 ms"
,
"32 ms"
,
"64 ms"
,
"128 ms"
,
"256 ms"
,
};
static
const
char
*
const
atk_alc
[
11
]
=
{
"8.40 ms"
,
"16.8 ms"
,
"33.6 ms"
,
"67.2 ms"
,
"134 ms"
,
"269 ms"
,
"538 ms"
,
"1.08 s"
,
"2.15 s"
,
"4.3 s"
,
"8.6 s"
,
};
static
const
char
*
const
dcy_lim
[
11
]
=
{
"1.2 ms"
,
"2.4 ms"
,
"4.8 ms"
,
"9.6 ms"
,
"19.2 ms"
,
"38.4 ms"
,
"76.8 ms"
,
"154 ms"
,
"307 ms"
,
"614 ms"
,
"1.23 s"
,
};
static
const
char
*
const
dcy_alc
[
11
]
=
{
"33.5 ms"
,
"67.0 ms"
,
"134 ms"
,
"268 ms"
,
"536 ms"
,
"1.07 s"
,
"2.14 s"
,
"4.29 s"
,
"8.58 s"
,
"17.2 s"
,
"34.3 s"
,
};
static
const
char
*
const
tranwin
[
8
]
=
{
"0 us"
,
"62.5 us"
,
"125 us"
,
"250 us"
,
"500 us"
,
"1 ms"
,
"2 ms"
,
"4 ms"
,
};
u8
max
;
const
char
*
const
*
names
;
max
=
(
ctl
->
private_value
>>
12
)
&
0xf
;
info
->
type
=
SNDRV_CTL_ELEM_TYPE_ENUMERATED
;
info
->
count
=
1
;
info
->
value
.
enumerated
.
items
=
max
+
1
;
if
(
info
->
value
.
enumerated
.
item
>
max
)
info
->
value
.
enumerated
.
item
=
max
;
switch
((
ctl
->
private_value
>>
24
)
&
0x1f
)
{
case
WM8776_ALCCTRL2
:
names
=
hld
;
break
;
case
WM8776_ALCCTRL3
:
if
(((
ctl
->
private_value
>>
20
)
&
0xf
)
==
0
)
{
if
(
ctl
->
private_value
&
LC_CONTROL_LIMITER
)
names
=
atk_lim
;
else
names
=
atk_alc
;
}
else
{
if
(
ctl
->
private_value
&
LC_CONTROL_LIMITER
)
names
=
dcy_lim
;
else
names
=
dcy_alc
;
}
break
;
case
WM8776_LIMITER
:
names
=
tranwin
;
break
;
default:
return
-
ENXIO
;
}
strcpy
(
info
->
value
.
enumerated
.
name
,
names
[
info
->
value
.
enumerated
.
item
]);
return
0
;
}
static
int
wm8776_field_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
=
(
ctl
->
private_value
>>
8
)
&
0xf
;
info
->
value
.
integer
.
max
=
(
ctl
->
private_value
>>
12
)
&
0xf
;
return
0
;
}
static
void
wm8776_field_set_from_ctl
(
struct
snd_kcontrol
*
ctl
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
value
,
reg_index
,
mode
;
u8
min
,
max
,
shift
;
u16
mask
,
reg_value
;
bool
invert
;
if
((
data
->
wm8776_regs
[
WM8776_ALCCTRL1
]
&
WM8776_LCSEL_MASK
)
==
WM8776_LCSEL_LIMITER
)
mode
=
LC_CONTROL_LIMITER
;
else
mode
=
LC_CONTROL_ALC
;
if
(
!
(
ctl
->
private_value
&
mode
))
return
;
value
=
ctl
->
private_value
&
0xf
;
min
=
(
ctl
->
private_value
>>
8
)
&
0xf
;
max
=
(
ctl
->
private_value
>>
12
)
&
0xf
;
mask
=
(
ctl
->
private_value
>>
16
)
&
0xf
;
shift
=
(
ctl
->
private_value
>>
20
)
&
0xf
;
reg_index
=
(
ctl
->
private_value
>>
24
)
&
0x1f
;
invert
=
(
ctl
->
private_value
>>
29
)
&
0x1
;
if
(
invert
)
value
=
max
-
(
value
-
min
);
reg_value
=
data
->
wm8776_regs
[
reg_index
];
reg_value
&=
~
(
mask
<<
shift
);
reg_value
|=
value
<<
shift
;
wm8776_write_cached
(
chip
,
reg_index
,
reg_value
);
}
static
int
wm8776_field_set
(
struct
snd_kcontrol
*
ctl
,
unsigned
int
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
u8
min
,
max
;
int
changed
;
min
=
(
ctl
->
private_value
>>
8
)
&
0xf
;
max
=
(
ctl
->
private_value
>>
12
)
&
0xf
;
if
(
value
<
min
||
value
>
max
)
return
-
EINVAL
;
mutex_lock
(
&
chip
->
mutex
);
changed
=
value
!=
(
ctl
->
private_value
&
0xf
);
if
(
changed
)
{
ctl
->
private_value
=
(
ctl
->
private_value
&
~
0xf
)
|
value
;
wm8776_field_set_from_ctl
(
ctl
);
}
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
static
int
wm8776_field_enum_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
value
->
value
.
enumerated
.
item
[
0
]
=
ctl
->
private_value
&
0xf
;
return
0
;
}
static
int
wm8776_field_volume_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
value
->
value
.
integer
.
value
[
0
]
=
ctl
->
private_value
&
0xf
;
return
0
;
}
static
int
wm8776_field_enum_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
return
wm8776_field_set
(
ctl
,
value
->
value
.
enumerated
.
item
[
0
]);
}
static
int
wm8776_field_volume_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
return
wm8776_field_set
(
ctl
,
value
->
value
.
integer
.
value
[
0
]);
}
static
int
wm8776_hp_vol_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
=
0x79
-
60
;
info
->
value
.
integer
.
max
=
0x7f
;
return
0
;
}
static
int
wm8776_hp_vol_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
mutex_lock
(
&
chip
->
mutex
);
value
->
value
.
integer
.
value
[
0
]
=
data
->
wm8776_regs
[
WM8776_HPLVOL
]
&
WM8776_HPATT_MASK
;
value
->
value
.
integer
.
value
[
1
]
=
data
->
wm8776_regs
[
WM8776_HPRVOL
]
&
WM8776_HPATT_MASK
;
mutex_unlock
(
&
chip
->
mutex
);
return
0
;
}
static
int
wm8776_hp_vol_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
u8
to_update
;
mutex_lock
(
&
chip
->
mutex
);
to_update
=
(
value
->
value
.
integer
.
value
[
0
]
!=
(
data
->
wm8776_regs
[
WM8776_HPLVOL
]
&
WM8776_HPATT_MASK
))
<<
0
;
to_update
|=
(
value
->
value
.
integer
.
value
[
1
]
!=
(
data
->
wm8776_regs
[
WM8776_HPRVOL
]
&
WM8776_HPATT_MASK
))
<<
1
;
if
(
value
->
value
.
integer
.
value
[
0
]
==
value
->
value
.
integer
.
value
[
1
])
{
if
(
to_update
)
{
wm8776_write
(
chip
,
WM8776_HPMASTER
,
value
->
value
.
integer
.
value
[
0
]
|
WM8776_HPZCEN
|
WM8776_UPDATE
);
data
->
wm8776_regs
[
WM8776_HPLVOL
]
=
value
->
value
.
integer
.
value
[
0
]
|
WM8776_HPZCEN
;
data
->
wm8776_regs
[
WM8776_HPRVOL
]
=
value
->
value
.
integer
.
value
[
0
]
|
WM8776_HPZCEN
;
}
}
else
{
if
(
to_update
&
1
)
wm8776_write
(
chip
,
WM8776_HPLVOL
,
value
->
value
.
integer
.
value
[
0
]
|
WM8776_HPZCEN
|
((
to_update
&
2
)
?
0
:
WM8776_UPDATE
));
if
(
to_update
&
2
)
wm8776_write
(
chip
,
WM8776_HPRVOL
,
value
->
value
.
integer
.
value
[
1
]
|
WM8776_HPZCEN
|
WM8776_UPDATE
);
}
mutex_unlock
(
&
chip
->
mutex
);
return
to_update
!=
0
;
}
static
int
wm8776_input_mux_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
mux_bit
=
ctl
->
private_value
;
value
->
value
.
integer
.
value
[
0
]
=
!!
(
data
->
wm8776_regs
[
WM8776_ADCMUX
]
&
mux_bit
);
return
0
;
}
static
int
wm8776_input_mux_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
mux_bit
=
ctl
->
private_value
;
u16
reg
;
int
changed
;
mutex_lock
(
&
chip
->
mutex
);
reg
=
data
->
wm8776_regs
[
WM8776_ADCMUX
];
if
(
value
->
value
.
integer
.
value
[
0
])
{
reg
&=
~
0x003
;
reg
|=
mux_bit
;
}
else
reg
&=
~
mux_bit
;
changed
=
reg
!=
data
->
wm8776_regs
[
WM8776_ADCMUX
];
if
(
changed
)
{
oxygen_write16_masked
(
chip
,
OXYGEN_GPIO_DATA
,
reg
&
1
?
GPIO_DS_INPUT_ROUTE
:
0
,
GPIO_DS_INPUT_ROUTE
);
wm8776_write
(
chip
,
WM8776_ADCMUX
,
reg
);
}
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
static
int
wm8776_input_vol_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
=
0xa5
;
info
->
value
.
integer
.
max
=
0xff
;
return
0
;
}
static
int
wm8776_input_vol_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
mutex_lock
(
&
chip
->
mutex
);
value
->
value
.
integer
.
value
[
0
]
=
data
->
wm8776_regs
[
WM8776_ADCLVOL
]
&
WM8776_AGMASK
;
value
->
value
.
integer
.
value
[
1
]
=
data
->
wm8776_regs
[
WM8776_ADCRVOL
]
&
WM8776_AGMASK
;
mutex_unlock
(
&
chip
->
mutex
);
return
0
;
}
static
int
wm8776_input_vol_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
int
changed
=
0
;
mutex_lock
(
&
chip
->
mutex
);
changed
=
(
value
->
value
.
integer
.
value
[
0
]
!=
(
data
->
wm8776_regs
[
WM8776_ADCLVOL
]
&
WM8776_AGMASK
))
||
(
value
->
value
.
integer
.
value
[
1
]
!=
(
data
->
wm8776_regs
[
WM8776_ADCRVOL
]
&
WM8776_AGMASK
));
wm8776_write_cached
(
chip
,
WM8776_ADCLVOL
,
value
->
value
.
integer
.
value
[
0
]
|
WM8776_ZCA
);
wm8776_write_cached
(
chip
,
WM8776_ADCRVOL
,
value
->
value
.
integer
.
value
[
1
]
|
WM8776_ZCA
);
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
static
int
wm8776_level_control_info
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_info
*
info
)
{
static
const
char
*
const
names
[
3
]
=
{
"None"
,
"Peak Limiter"
,
"Automatic Level Control"
};
info
->
type
=
SNDRV_CTL_ELEM_TYPE_ENUMERATED
;
info
->
count
=
1
;
info
->
value
.
enumerated
.
items
=
3
;
if
(
info
->
value
.
enumerated
.
item
>=
3
)
info
->
value
.
enumerated
.
item
=
2
;
strcpy
(
info
->
value
.
enumerated
.
name
,
names
[
info
->
value
.
enumerated
.
item
]);
return
0
;
}
static
int
wm8776_level_control_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
if
(
!
(
data
->
wm8776_regs
[
WM8776_ALCCTRL2
]
&
WM8776_LCEN
))
value
->
value
.
enumerated
.
item
[
0
]
=
0
;
else
if
((
data
->
wm8776_regs
[
WM8776_ALCCTRL1
]
&
WM8776_LCSEL_MASK
)
==
WM8776_LCSEL_LIMITER
)
value
->
value
.
enumerated
.
item
[
0
]
=
1
;
else
value
->
value
.
enumerated
.
item
[
0
]
=
2
;
return
0
;
}
static
void
activate_control
(
struct
oxygen
*
chip
,
struct
snd_kcontrol
*
ctl
,
unsigned
int
mode
)
{
unsigned
int
access
;
if
(
ctl
->
private_value
&
mode
)
access
=
0
;
else
access
=
SNDRV_CTL_ELEM_ACCESS_INACTIVE
;
if
((
ctl
->
vd
[
0
].
access
&
SNDRV_CTL_ELEM_ACCESS_INACTIVE
)
!=
access
)
{
ctl
->
vd
[
0
].
access
^=
SNDRV_CTL_ELEM_ACCESS_INACTIVE
;
snd_ctl_notify
(
chip
->
card
,
SNDRV_CTL_EVENT_MASK_INFO
,
&
ctl
->
id
);
}
}
static
int
wm8776_level_control_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
mode
=
0
,
i
;
u16
ctrl1
,
ctrl2
;
int
changed
;
if
(
value
->
value
.
enumerated
.
item
[
0
]
>=
3
)
return
-
EINVAL
;
mutex_lock
(
&
chip
->
mutex
);
changed
=
value
->
value
.
enumerated
.
item
[
0
]
!=
ctl
->
private_value
;
if
(
changed
)
{
ctl
->
private_value
=
value
->
value
.
enumerated
.
item
[
0
];
ctrl1
=
data
->
wm8776_regs
[
WM8776_ALCCTRL1
];
ctrl2
=
data
->
wm8776_regs
[
WM8776_ALCCTRL2
];
switch
(
value
->
value
.
enumerated
.
item
[
0
])
{
default:
wm8776_write_cached
(
chip
,
WM8776_ALCCTRL2
,
ctrl2
&
~
WM8776_LCEN
);
break
;
case
1
:
wm8776_write_cached
(
chip
,
WM8776_ALCCTRL1
,
(
ctrl1
&
~
WM8776_LCSEL_MASK
)
|
WM8776_LCSEL_LIMITER
);
wm8776_write_cached
(
chip
,
WM8776_ALCCTRL2
,
ctrl2
|
WM8776_LCEN
);
mode
=
LC_CONTROL_LIMITER
;
break
;
case
2
:
wm8776_write_cached
(
chip
,
WM8776_ALCCTRL1
,
(
ctrl1
&
~
WM8776_LCSEL_MASK
)
|
WM8776_LCSEL_ALC_STEREO
);
wm8776_write_cached
(
chip
,
WM8776_ALCCTRL2
,
ctrl2
|
WM8776_LCEN
);
mode
=
LC_CONTROL_ALC
;
break
;
}
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
data
->
lc_controls
);
++
i
)
activate_control
(
chip
,
data
->
lc_controls
[
i
],
mode
);
}
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
static
int
hpf_info
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_info
*
info
)
{
static
const
char
*
const
names
[
2
]
=
{
"None"
,
"High-pass Filter"
};
info
->
type
=
SNDRV_CTL_ELEM_TYPE_ENUMERATED
;
info
->
count
=
1
;
info
->
value
.
enumerated
.
items
=
2
;
if
(
info
->
value
.
enumerated
.
item
>=
2
)
info
->
value
.
enumerated
.
item
=
1
;
strcpy
(
info
->
value
.
enumerated
.
name
,
names
[
info
->
value
.
enumerated
.
item
]);
return
0
;
}
static
int
hpf_get
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
value
->
value
.
enumerated
.
item
[
0
]
=
!
(
data
->
wm8776_regs
[
WM8776_ADCIFCTRL
]
&
WM8776_ADCHPD
);
return
0
;
}
static
int
hpf_put
(
struct
snd_kcontrol
*
ctl
,
struct
snd_ctl_elem_value
*
value
)
{
struct
oxygen
*
chip
=
ctl
->
private_data
;
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
reg
;
int
changed
;
mutex_lock
(
&
chip
->
mutex
);
reg
=
data
->
wm8776_regs
[
WM8776_ADCIFCTRL
]
&
~
WM8776_ADCHPD
;
if
(
!
value
->
value
.
enumerated
.
item
[
0
])
reg
|=
WM8776_ADCHPD
;
changed
=
reg
!=
data
->
wm8776_regs
[
WM8776_ADCIFCTRL
];
if
(
changed
)
wm8776_write
(
chip
,
WM8776_ADCIFCTRL
,
reg
);
mutex_unlock
(
&
chip
->
mutex
);
return
changed
;
}
#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.info = snd_ctl_boolean_mono_info, \
.get = wm8776_bit_switch_get, \
.put = wm8776_bit_switch_put, \
.private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \
}
#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \
.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
.name = xname, \
.private_value = (initval) | ((min) << 8) | ((max) << 12) | \
((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags)
#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\
_WM8776_FIELD_CTL(xname " Capture Enum", \
reg, shift, init, min, max, mask, flags), \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_INACTIVE, \
.info = wm8776_field_enum_info, \
.get = wm8776_field_enum_get, \
.put = wm8776_field_enum_put, \
}
#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \
_WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
SNDRV_CTL_ELEM_ACCESS_INACTIVE | \
SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
.info = wm8776_field_volume_info, \
.get = wm8776_field_volume_get, \
.put = wm8776_field_volume_put, \
.tlv = { .p = tlv_p }, \
}
static
const
DECLARE_TLV_DB_SCALE
(
wm87x6_dac_db_scale
,
-
6000
,
50
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_adc_db_scale
,
-
2100
,
50
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_hp_db_scale
,
-
6000
,
100
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_lct_db_scale
,
-
1600
,
100
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_maxgain_db_scale
,
0
,
400
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_ngth_db_scale
,
-
7800
,
600
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_maxatten_lim_db_scale
,
-
1200
,
100
,
0
);
static
const
DECLARE_TLV_DB_SCALE
(
wm8776_maxatten_alc_db_scale
,
-
2100
,
400
,
0
);
static
const
struct
snd_kcontrol_new
ds_controls
[]
=
{
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"Headphone Playback Volume"
,
.
info
=
wm8776_hp_vol_info
,
.
get
=
wm8776_hp_vol_get
,
.
put
=
wm8776_hp_vol_put
,
.
tlv
=
{
.
p
=
wm8776_hp_db_scale
},
},
WM8776_BIT_SWITCH
(
"Headphone Playback Switch"
,
WM8776_PWRDOWN
,
WM8776_HPPD
,
1
,
0
),
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"Input Capture Volume"
,
.
info
=
wm8776_input_vol_info
,
.
get
=
wm8776_input_vol_get
,
.
put
=
wm8776_input_vol_put
,
.
tlv
=
{
.
p
=
wm8776_adc_db_scale
},
},
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"Line Capture Switch"
,
.
info
=
snd_ctl_boolean_mono_info
,
.
get
=
wm8776_input_mux_get
,
.
put
=
wm8776_input_mux_put
,
.
private_value
=
1
<<
0
,
},
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"Mic Capture Switch"
,
.
info
=
snd_ctl_boolean_mono_info
,
.
get
=
wm8776_input_mux_get
,
.
put
=
wm8776_input_mux_put
,
.
private_value
=
1
<<
1
,
},
WM8776_BIT_SWITCH
(
"Aux"
,
WM8776_ADCMUX
,
1
<<
2
,
0
,
0
),
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"ADC Filter Capture Enum"
,
.
info
=
hpf_info
,
.
get
=
hpf_get
,
.
put
=
hpf_put
,
},
{
.
iface
=
SNDRV_CTL_ELEM_IFACE_MIXER
,
.
name
=
"Level Control Capture Enum"
,
.
info
=
wm8776_level_control_info
,
.
get
=
wm8776_level_control_get
,
.
put
=
wm8776_level_control_put
,
.
private_value
=
0
,
},
};
static
const
struct
snd_kcontrol_new
lc_controls
[]
=
{
WM8776_FIELD_CTL_VOLUME
(
"Limiter Threshold"
,
WM8776_ALCCTRL1
,
0
,
11
,
0
,
15
,
0xf
,
LC_CONTROL_LIMITER
,
wm8776_lct_db_scale
),
WM8776_FIELD_CTL_ENUM
(
"Limiter Attack Time"
,
WM8776_ALCCTRL3
,
0
,
2
,
0
,
10
,
0xf
,
LC_CONTROL_LIMITER
),
WM8776_FIELD_CTL_ENUM
(
"Limiter Decay Time"
,
WM8776_ALCCTRL3
,
4
,
3
,
0
,
10
,
0xf
,
LC_CONTROL_LIMITER
),
WM8776_FIELD_CTL_ENUM
(
"Limiter Transient Window"
,
WM8776_LIMITER
,
4
,
2
,
0
,
7
,
0x7
,
LC_CONTROL_LIMITER
),
WM8776_FIELD_CTL_VOLUME
(
"Limiter Maximum Attenuation"
,
WM8776_LIMITER
,
0
,
6
,
3
,
12
,
0xf
,
LC_CONTROL_LIMITER
,
wm8776_maxatten_lim_db_scale
),
WM8776_FIELD_CTL_VOLUME
(
"ALC Target Level"
,
WM8776_ALCCTRL1
,
0
,
11
,
0
,
15
,
0xf
,
LC_CONTROL_ALC
,
wm8776_lct_db_scale
),
WM8776_FIELD_CTL_ENUM
(
"ALC Attack Time"
,
WM8776_ALCCTRL3
,
0
,
2
,
0
,
10
,
0xf
,
LC_CONTROL_ALC
),
WM8776_FIELD_CTL_ENUM
(
"ALC Decay Time"
,
WM8776_ALCCTRL3
,
4
,
3
,
0
,
10
,
0xf
,
LC_CONTROL_ALC
),
WM8776_FIELD_CTL_VOLUME
(
"ALC Maximum Gain"
,
WM8776_ALCCTRL1
,
4
,
7
,
1
,
7
,
0x7
,
LC_CONTROL_ALC
,
wm8776_maxgain_db_scale
),
WM8776_FIELD_CTL_VOLUME
(
"ALC Maximum Attenuation"
,
WM8776_LIMITER
,
0
,
10
,
10
,
15
,
0xf
,
LC_CONTROL_ALC
,
wm8776_maxatten_alc_db_scale
),
WM8776_FIELD_CTL_ENUM
(
"ALC Hold Time"
,
WM8776_ALCCTRL2
,
0
,
0
,
0
,
15
,
0xf
,
LC_CONTROL_ALC
),
WM8776_BIT_SWITCH
(
"Noise Gate Capture Switch"
,
WM8776_NOISEGATE
,
WM8776_NGAT
,
0
,
LC_CONTROL_ALC
),
WM8776_FIELD_CTL_VOLUME
(
"Noise Gate Threshold"
,
WM8776_NOISEGATE
,
2
,
0
,
0
,
7
,
0x7
,
LC_CONTROL_ALC
,
wm8776_ngth_db_scale
),
};
static
int
xonar_ds_control_filter
(
struct
snd_kcontrol_new
*
template
)
{
if
(
!
strncmp
(
template
->
name
,
"CD Capture "
,
11
))
return
1
;
/* no CD input */
return
0
;
}
static
int
xonar_ds_mixer_init
(
struct
oxygen
*
chip
)
{
struct
xonar_wm87x6
*
data
=
chip
->
model_data
;
unsigned
int
i
;
struct
snd_kcontrol
*
ctl
;
int
err
;
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
ds_controls
);
++
i
)
{
ctl
=
snd_ctl_new1
(
&
ds_controls
[
i
],
chip
);
if
(
!
ctl
)
return
-
ENOMEM
;
err
=
snd_ctl_add
(
chip
->
card
,
ctl
);
if
(
err
<
0
)
return
err
;
}
BUILD_BUG_ON
(
ARRAY_SIZE
(
lc_controls
)
!=
ARRAY_SIZE
(
data
->
lc_controls
));
for
(
i
=
0
;
i
<
ARRAY_SIZE
(
lc_controls
);
++
i
)
{
ctl
=
snd_ctl_new1
(
&
lc_controls
[
i
],
chip
);
if
(
!
ctl
)
return
-
ENOMEM
;
err
=
snd_ctl_add
(
chip
->
card
,
ctl
);
if
(
err
<
0
)
return
err
;
data
->
lc_controls
[
i
]
=
ctl
;
}
return
0
;
}
static
const
struct
oxygen_model
model_xonar_ds
=
{
.
shortname
=
"Xonar DS"
,
.
longname
=
"Asus Virtuoso 200"
,
.
chip
=
"AV200"
,
.
init
=
xonar_ds_init
,
.
control_filter
=
xonar_ds_control_filter
,
.
mixer_init
=
xonar_ds_mixer_init
,
.
cleanup
=
xonar_ds_cleanup
,
.
suspend
=
xonar_ds_suspend
,
.
resume
=
xonar_ds_resume
,
.
pcm_hardware_filter
=
wm8776_adc_hardware_filter
,
.
get_i2s_mclk
=
oxygen_default_i2s_mclk
,
.
set_dac_params
=
set_wm87x6_dac_params
,
.
set_adc_params
=
set_wm8776_adc_params
,
.
update_dac_volume
=
update_wm87x6_volume
,
.
update_dac_mute
=
update_wm87x6_mute
,
.
gpio_changed
=
xonar_ds_gpio_changed
,
.
dac_tlv
=
wm87x6_dac_db_scale
,
.
model_data_size
=
sizeof
(
struct
xonar_wm87x6
),
.
device_config
=
PLAYBACK_0_TO_I2S
|
PLAYBACK_1_TO_SPDIF
|
CAPTURE_0_FROM_I2S_1
,
.
dac_channels
=
8
,
.
dac_volume_min
=
255
-
2
*
60
,
.
dac_volume_max
=
255
,
.
function_flags
=
OXYGEN_FUNCTION_SPI
,
.
dac_i2s_format
=
OXYGEN_I2S_FORMAT_LJUST
,
.
adc_i2s_format
=
OXYGEN_I2S_FORMAT_LJUST
,
};
int
__devinit
get_xonar_wm87x6_model
(
struct
oxygen
*
chip
,
const
struct
pci_device_id
*
id
)
{
switch
(
id
->
subdevice
)
{
case
0x838e
:
chip
->
model
=
model_xonar_ds
;
break
;
default:
return
-
EINVAL
;
}
return
0
;
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment