2006-06-21 21:42:43 +08:00
|
|
|
/*
|
|
|
|
* Apple Onboard Audio driver for Onyx codec
|
|
|
|
*
|
|
|
|
* Copyright 2006 Johannes Berg <johannes@sipsolutions.net>
|
|
|
|
*
|
|
|
|
* GPL v2, can be found in COPYING.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* This is a driver for the pcm3052 codec chip (codenamed Onyx)
|
|
|
|
* that is present in newer Apple hardware (with digital output).
|
|
|
|
*
|
|
|
|
* The Onyx codec has the following connections (listed by the bit
|
|
|
|
* to be used in aoa_codec.connected):
|
|
|
|
* 0: analog output
|
|
|
|
* 1: digital output
|
|
|
|
* 2: line input
|
|
|
|
* 3: microphone input
|
|
|
|
* Note that even though I know of no machine that has for example
|
|
|
|
* the digital output connected but not the analog, I have handled
|
|
|
|
* all the different cases in the code so that this driver may serve
|
|
|
|
* as a good example of what to do.
|
|
|
|
*
|
|
|
|
* NOTE: This driver assumes that there's at most one chip to be
|
|
|
|
* used with one alsa card, in form of creating all kinds
|
|
|
|
* of mixer elements without regard for their existence.
|
|
|
|
* But snd-aoa assumes that there's at most one card, so
|
|
|
|
* this means you can only have one onyx on a system. This
|
|
|
|
* should probably be fixed by changing the assumption of
|
|
|
|
* having just a single card on a system, and making the
|
|
|
|
* 'card' pointer accessible to anyone who needs it instead
|
|
|
|
* of hiding it in the aoa_snd_* functions...
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/module.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/slab.h>
|
2006-06-21 21:42:43 +08:00
|
|
|
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
|
|
|
|
MODULE_LICENSE("GPL");
|
|
|
|
MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
|
|
|
|
|
2008-10-23 21:47:56 +08:00
|
|
|
#include "onyx.h"
|
2006-06-21 21:42:43 +08:00
|
|
|
#include "../aoa.h"
|
|
|
|
#include "../soundbus/soundbus.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define PFX "snd-aoa-codec-onyx: "
|
|
|
|
|
|
|
|
struct onyx {
|
|
|
|
/* cache registers 65 to 80, they are write-only! */
|
|
|
|
u8 cache[16];
|
2009-04-21 04:54:25 +08:00
|
|
|
struct i2c_client *i2c;
|
2006-06-21 21:42:43 +08:00
|
|
|
struct aoa_codec codec;
|
|
|
|
u32 initialised:1,
|
|
|
|
spdif_locked:1,
|
|
|
|
analog_locked:1,
|
|
|
|
original_mute:2;
|
|
|
|
int open_count;
|
|
|
|
struct codec_info *codec_info;
|
|
|
|
|
|
|
|
/* mutex serializes concurrent access to the device
|
|
|
|
* and this structure.
|
|
|
|
*/
|
|
|
|
struct mutex mutex;
|
|
|
|
};
|
|
|
|
#define codec_to_onyx(c) container_of(c, struct onyx, codec)
|
|
|
|
|
|
|
|
/* both return 0 if all ok, else on error */
|
|
|
|
static int onyx_read_register(struct onyx *onyx, u8 reg, u8 *value)
|
|
|
|
{
|
|
|
|
s32 v;
|
|
|
|
|
|
|
|
if (reg != ONYX_REG_CONTROL) {
|
|
|
|
*value = onyx->cache[reg-FIRSTREGISTER];
|
|
|
|
return 0;
|
|
|
|
}
|
2009-04-21 04:54:25 +08:00
|
|
|
v = i2c_smbus_read_byte_data(onyx->i2c, reg);
|
2006-06-21 21:42:43 +08:00
|
|
|
if (v < 0)
|
|
|
|
return -1;
|
|
|
|
*value = (u8)v;
|
|
|
|
onyx->cache[ONYX_REG_CONTROL-FIRSTREGISTER] = *value;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_write_register(struct onyx *onyx, u8 reg, u8 value)
|
|
|
|
{
|
|
|
|
int result;
|
|
|
|
|
2009-04-21 04:54:25 +08:00
|
|
|
result = i2c_smbus_write_byte_data(onyx->i2c, reg, value);
|
2006-06-21 21:42:43 +08:00
|
|
|
if (!result)
|
|
|
|
onyx->cache[reg-FIRSTREGISTER] = value;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* alsa stuff */
|
|
|
|
|
|
|
|
static int onyx_dev_register(struct snd_device *dev)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct snd_device_ops ops = {
|
|
|
|
.dev_register = onyx_dev_register,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* this is necessary because most alsa mixer programs
|
|
|
|
* can't properly handle the negative range */
|
|
|
|
#define VOLUME_RANGE_SHIFT 128
|
|
|
|
|
|
|
|
static int onyx_snd_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 = -128 + VOLUME_RANGE_SHIFT;
|
|
|
|
uinfo->value.integer.max = -1 + VOLUME_RANGE_SHIFT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_vol_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
s8 l, r;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] = l + VOLUME_RANGE_SHIFT;
|
|
|
|
ucontrol->value.integer.value[1] = r + VOLUME_RANGE_SHIFT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_vol_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
s8 l, r;
|
|
|
|
|
2007-11-15 23:16:32 +08:00
|
|
|
if (ucontrol->value.integer.value[0] < -128 + VOLUME_RANGE_SHIFT ||
|
|
|
|
ucontrol->value.integer.value[0] > -1 + VOLUME_RANGE_SHIFT)
|
|
|
|
return -EINVAL;
|
|
|
|
if (ucontrol->value.integer.value[1] < -128 + VOLUME_RANGE_SHIFT ||
|
|
|
|
ucontrol->value.integer.value[1] > -1 + VOLUME_RANGE_SHIFT)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2006-06-21 21:42:43 +08:00
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_LEFT, &l);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT, &r);
|
|
|
|
|
|
|
|
if (l + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[0] &&
|
|
|
|
r + VOLUME_RANGE_SHIFT == ucontrol->value.integer.value[1]) {
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_LEFT,
|
|
|
|
ucontrol->value.integer.value[0]
|
|
|
|
- VOLUME_RANGE_SHIFT);
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DAC_ATTEN_RIGHT,
|
|
|
|
ucontrol->value.integer.value[1]
|
|
|
|
- VOLUME_RANGE_SHIFT);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new volume_control = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Master Playback Volume",
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
|
|
.info = onyx_snd_vol_info,
|
|
|
|
.get = onyx_snd_vol_get,
|
|
|
|
.put = onyx_snd_vol_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* like above, this is necessary because a lot
|
|
|
|
* of alsa mixer programs don't handle ranges
|
|
|
|
* that don't start at 0 properly.
|
|
|
|
* even alsamixer is one of them... */
|
|
|
|
#define INPUTGAIN_RANGE_SHIFT (-3)
|
|
|
|
|
|
|
|
static int onyx_snd_inputgain_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
|
|
uinfo->count = 1;
|
|
|
|
uinfo->value.integer.min = 3 + INPUTGAIN_RANGE_SHIFT;
|
|
|
|
uinfo->value.integer.max = 28 + INPUTGAIN_RANGE_SHIFT;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_inputgain_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 ig;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &ig);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
|
|
(ig & ONYX_ADC_PGA_GAIN_MASK) + INPUTGAIN_RANGE_SHIFT;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_inputgain_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 v, n;
|
|
|
|
|
2007-11-15 23:16:32 +08:00
|
|
|
if (ucontrol->value.integer.value[0] < 3 + INPUTGAIN_RANGE_SHIFT ||
|
|
|
|
ucontrol->value.integer.value[0] > 28 + INPUTGAIN_RANGE_SHIFT)
|
|
|
|
return -EINVAL;
|
2006-06-21 21:42:43 +08:00
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
|
|
n = v;
|
|
|
|
n &= ~ONYX_ADC_PGA_GAIN_MASK;
|
|
|
|
n |= (ucontrol->value.integer.value[0] - INPUTGAIN_RANGE_SHIFT)
|
|
|
|
& ONYX_ADC_PGA_GAIN_MASK;
|
|
|
|
onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, n);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return n != v;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new inputgain_control = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Master Capture Volume",
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
|
|
.info = onyx_snd_inputgain_info,
|
|
|
|
.get = onyx_snd_inputgain_get,
|
|
|
|
.put = onyx_snd_inputgain_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int onyx_snd_capture_source_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
2014-05-27 15:12:18 +08:00
|
|
|
static const char * const texts[] = { "Line-In", "Microphone" };
|
2006-06-21 21:42:43 +08:00
|
|
|
|
2014-10-21 00:10:39 +08:00
|
|
|
return snd_ctl_enum_info(uinfo, 1, 2, texts);
|
2006-06-21 21:42:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_capture_source_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
s8 v;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
ucontrol->value.enumerated.item[0] = !!(v&ONYX_ADC_INPUT_MIC);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void onyx_set_capture_source(struct onyx *onyx, int mic)
|
|
|
|
{
|
|
|
|
s8 v;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_ADC_CONTROL, &v);
|
|
|
|
v &= ~ONYX_ADC_INPUT_MIC;
|
|
|
|
if (mic)
|
|
|
|
v |= ONYX_ADC_INPUT_MIC;
|
|
|
|
onyx_write_register(onyx, ONYX_REG_ADC_CONTROL, v);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_capture_source_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
2007-11-15 23:16:32 +08:00
|
|
|
if (ucontrol->value.enumerated.item[0] > 1)
|
|
|
|
return -EINVAL;
|
2006-06-21 21:42:43 +08:00
|
|
|
onyx_set_capture_source(snd_kcontrol_chip(kcontrol),
|
|
|
|
ucontrol->value.enumerated.item[0]);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new capture_source_control = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
/* If we name this 'Input Source', it properly shows up in
|
2008-10-23 21:47:56 +08:00
|
|
|
* alsamixer as a selection, * but it's shown under the
|
2006-06-21 21:42:43 +08:00
|
|
|
* 'Playback' category.
|
|
|
|
* If I name it 'Capture Source', it shows up in strange
|
|
|
|
* ways (two bools of which one can be selected at a
|
|
|
|
* time) but at least it's shown in the 'Capture'
|
|
|
|
* category.
|
|
|
|
* I was told that this was due to backward compatibility,
|
|
|
|
* but I don't understand then why the mangling is *not*
|
|
|
|
* done when I name it "Input Source".....
|
|
|
|
*/
|
|
|
|
.name = "Capture Source",
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
|
|
.info = onyx_snd_capture_source_info,
|
|
|
|
.get = onyx_snd_capture_source_get,
|
|
|
|
.put = onyx_snd_capture_source_put,
|
|
|
|
};
|
|
|
|
|
2007-07-23 21:42:26 +08:00
|
|
|
#define onyx_snd_mute_info snd_ctl_boolean_stereo_info
|
2006-06-21 21:42:43 +08:00
|
|
|
|
|
|
|
static int onyx_snd_mute_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 c;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &c);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] = !(c & ONYX_MUTE_LEFT);
|
|
|
|
ucontrol->value.integer.value[1] = !(c & ONYX_MUTE_RIGHT);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_mute_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 v = 0, c = 0;
|
|
|
|
int err = -EBUSY;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
if (onyx->analog_locked)
|
|
|
|
goto out_unlock;
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
|
|
|
|
c = v;
|
|
|
|
c &= ~(ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT);
|
|
|
|
if (!ucontrol->value.integer.value[0])
|
|
|
|
c |= ONYX_MUTE_LEFT;
|
|
|
|
if (!ucontrol->value.integer.value[1])
|
|
|
|
c |= ONYX_MUTE_RIGHT;
|
|
|
|
err = onyx_write_register(onyx, ONYX_REG_DAC_CONTROL, c);
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return !err ? (v != c) : err;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new mute_control = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
|
|
|
|
.name = "Master Playback Switch",
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
|
|
.info = onyx_snd_mute_info,
|
|
|
|
.get = onyx_snd_mute_get,
|
|
|
|
.put = onyx_snd_mute_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2007-07-23 21:42:26 +08:00
|
|
|
#define onyx_snd_single_bit_info snd_ctl_boolean_mono_info
|
2006-06-21 21:42:43 +08:00
|
|
|
|
|
|
|
#define FLAG_POLARITY_INVERT 1
|
|
|
|
#define FLAG_SPDIFLOCK 2
|
|
|
|
|
|
|
|
static int onyx_snd_single_bit_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 c;
|
|
|
|
long int pv = kcontrol->private_value;
|
|
|
|
u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
|
|
|
|
u8 address = (pv >> 8) & 0xff;
|
|
|
|
u8 mask = pv & 0xff;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, address, &c);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
ucontrol->value.integer.value[0] = !!(c & mask) ^ polarity;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_snd_single_bit_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 v = 0, c = 0;
|
|
|
|
int err;
|
|
|
|
long int pv = kcontrol->private_value;
|
|
|
|
u8 polarity = (pv >> 16) & FLAG_POLARITY_INVERT;
|
|
|
|
u8 spdiflock = (pv >> 16) & FLAG_SPDIFLOCK;
|
|
|
|
u8 address = (pv >> 8) & 0xff;
|
|
|
|
u8 mask = pv & 0xff;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
if (spdiflock && onyx->spdif_locked) {
|
|
|
|
/* even if alsamixer doesn't care.. */
|
|
|
|
err = -EBUSY;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
onyx_read_register(onyx, address, &v);
|
|
|
|
c = v;
|
|
|
|
c &= ~(mask);
|
|
|
|
if (!!ucontrol->value.integer.value[0] ^ polarity)
|
|
|
|
c |= mask;
|
|
|
|
err = onyx_write_register(onyx, address, c);
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return !err ? (v != c) : err;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define SINGLE_BIT(n, type, description, address, mask, flags) \
|
|
|
|
static struct snd_kcontrol_new n##_control = { \
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_##type, \
|
|
|
|
.name = description, \
|
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
|
|
|
.info = onyx_snd_single_bit_info, \
|
|
|
|
.get = onyx_snd_single_bit_get, \
|
|
|
|
.put = onyx_snd_single_bit_put, \
|
|
|
|
.private_value = (flags << 16) | (address << 8) | mask \
|
|
|
|
}
|
|
|
|
|
|
|
|
SINGLE_BIT(spdif,
|
|
|
|
MIXER,
|
|
|
|
SNDRV_CTL_NAME_IEC958("", PLAYBACK, SWITCH),
|
|
|
|
ONYX_REG_DIG_INFO4,
|
|
|
|
ONYX_SPDIF_ENABLE,
|
|
|
|
FLAG_SPDIFLOCK);
|
|
|
|
SINGLE_BIT(ovr1,
|
|
|
|
MIXER,
|
|
|
|
"Oversampling Rate",
|
|
|
|
ONYX_REG_DAC_CONTROL,
|
|
|
|
ONYX_OVR1,
|
|
|
|
0);
|
|
|
|
SINGLE_BIT(flt0,
|
|
|
|
MIXER,
|
|
|
|
"Fast Digital Filter Rolloff",
|
|
|
|
ONYX_REG_DAC_FILTER,
|
|
|
|
ONYX_ROLLOFF_FAST,
|
|
|
|
FLAG_POLARITY_INVERT);
|
|
|
|
SINGLE_BIT(hpf,
|
|
|
|
MIXER,
|
|
|
|
"Highpass Filter",
|
|
|
|
ONYX_REG_ADC_HPF_BYPASS,
|
|
|
|
ONYX_HPF_DISABLE,
|
|
|
|
FLAG_POLARITY_INVERT);
|
|
|
|
SINGLE_BIT(dm12,
|
|
|
|
MIXER,
|
|
|
|
"Digital De-Emphasis",
|
|
|
|
ONYX_REG_DAC_DEEMPH,
|
|
|
|
ONYX_DIGDEEMPH_CTRL,
|
|
|
|
0);
|
|
|
|
|
|
|
|
static int onyx_spdif_info(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_info *uinfo)
|
|
|
|
{
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_IEC958;
|
|
|
|
uinfo->count = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_spdif_mask_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
/* datasheet page 30, all others are 0 */
|
|
|
|
ucontrol->value.iec958.status[0] = 0x3e;
|
|
|
|
ucontrol->value.iec958.status[1] = 0xff;
|
|
|
|
|
|
|
|
ucontrol->value.iec958.status[3] = 0x3f;
|
|
|
|
ucontrol->value.iec958.status[4] = 0x0f;
|
2008-10-23 21:47:56 +08:00
|
|
|
|
2006-06-21 21:42:43 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new onyx_spdif_mask = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READ,
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
|
|
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,CON_MASK),
|
|
|
|
.info = onyx_spdif_info,
|
|
|
|
.get = onyx_spdif_mask_get,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int onyx_spdif_get(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 v;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
|
|
|
|
ucontrol->value.iec958.status[0] = v & 0x3e;
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO2, &v);
|
|
|
|
ucontrol->value.iec958.status[1] = v;
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
|
|
|
|
ucontrol->value.iec958.status[3] = v & 0x3f;
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
|
|
ucontrol->value.iec958.status[4] = v & 0x0f;
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_spdif_put(struct snd_kcontrol *kcontrol,
|
|
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = snd_kcontrol_chip(kcontrol);
|
|
|
|
u8 v;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO1, &v);
|
|
|
|
v = (v & ~0x3e) | (ucontrol->value.iec958.status[0] & 0x3e);
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO1, v);
|
|
|
|
|
|
|
|
v = ucontrol->value.iec958.status[1];
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO2, v);
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO3, &v);
|
|
|
|
v = (v & ~0x3f) | (ucontrol->value.iec958.status[3] & 0x3f);
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO3, v);
|
|
|
|
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
|
|
v = (v & ~0x0f) | (ucontrol->value.iec958.status[4] & 0x0f);
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2017-08-16 16:44:09 +08:00
|
|
|
static const struct snd_kcontrol_new onyx_spdif_ctrl = {
|
2006-06-21 21:42:43 +08:00
|
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
|
|
|
|
.iface = SNDRV_CTL_ELEM_IFACE_PCM,
|
|
|
|
.name = SNDRV_CTL_NAME_IEC958("",PLAYBACK,DEFAULT),
|
|
|
|
.info = onyx_spdif_info,
|
|
|
|
.get = onyx_spdif_get,
|
|
|
|
.put = onyx_spdif_put,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* our registers */
|
|
|
|
|
|
|
|
static u8 register_map[] = {
|
|
|
|
ONYX_REG_DAC_ATTEN_LEFT,
|
|
|
|
ONYX_REG_DAC_ATTEN_RIGHT,
|
|
|
|
ONYX_REG_CONTROL,
|
|
|
|
ONYX_REG_DAC_CONTROL,
|
|
|
|
ONYX_REG_DAC_DEEMPH,
|
|
|
|
ONYX_REG_DAC_FILTER,
|
|
|
|
ONYX_REG_DAC_OUTPHASE,
|
|
|
|
ONYX_REG_ADC_CONTROL,
|
|
|
|
ONYX_REG_ADC_HPF_BYPASS,
|
|
|
|
ONYX_REG_DIG_INFO1,
|
|
|
|
ONYX_REG_DIG_INFO2,
|
|
|
|
ONYX_REG_DIG_INFO3,
|
|
|
|
ONYX_REG_DIG_INFO4
|
|
|
|
};
|
|
|
|
|
|
|
|
static u8 initial_values[ARRAY_SIZE(register_map)] = {
|
|
|
|
0x80, 0x80, /* muted */
|
|
|
|
ONYX_MRST | ONYX_SRST, /* but handled specially! */
|
|
|
|
ONYX_MUTE_LEFT | ONYX_MUTE_RIGHT,
|
|
|
|
0, /* no deemphasis */
|
|
|
|
ONYX_DAC_FILTER_ALWAYS,
|
|
|
|
ONYX_OUTPHASE_INVERTED,
|
|
|
|
(-1 /*dB*/ + 8) & 0xF, /* line in selected, -1 dB gain*/
|
|
|
|
ONYX_ADC_HPF_ALWAYS,
|
|
|
|
(1<<2), /* pcm audio */
|
|
|
|
2, /* category: pcm coder */
|
|
|
|
0, /* sampling frequency 44.1 kHz, clock accuracy level II */
|
|
|
|
1 /* 24 bit depth */
|
|
|
|
};
|
|
|
|
|
|
|
|
/* reset registers of chip, either to initial or to previous values */
|
|
|
|
static int onyx_register_init(struct onyx *onyx)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u8 val;
|
|
|
|
u8 regs[sizeof(initial_values)];
|
|
|
|
|
|
|
|
if (!onyx->initialised) {
|
|
|
|
memcpy(regs, initial_values, sizeof(initial_values));
|
|
|
|
if (onyx_read_register(onyx, ONYX_REG_CONTROL, &val))
|
|
|
|
return -1;
|
|
|
|
val &= ~ONYX_SILICONVERSION;
|
|
|
|
val |= initial_values[3];
|
|
|
|
regs[3] = val;
|
|
|
|
} else {
|
|
|
|
for (i=0; i<sizeof(register_map); i++)
|
|
|
|
regs[i] = onyx->cache[register_map[i]-FIRSTREGISTER];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i=0; i<sizeof(register_map); i++) {
|
|
|
|
if (onyx_write_register(onyx, register_map[i], regs[i]))
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
onyx->initialised = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct transfer_info onyx_transfers[] = {
|
|
|
|
/* this is first so we can skip it if no input is present...
|
|
|
|
* No hardware exists with that, but it's here as an example
|
|
|
|
* of what to do :) */
|
|
|
|
{
|
|
|
|
/* analog input */
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
|
|
SNDRV_PCM_FMTBIT_S16_BE |
|
|
|
|
SNDRV_PCM_FMTBIT_S24_BE,
|
|
|
|
.rates = SNDRV_PCM_RATE_8000_96000,
|
|
|
|
.transfer_in = 1,
|
|
|
|
.must_be_clock_source = 0,
|
|
|
|
.tag = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/* if analog and digital are currently off, anything should go,
|
|
|
|
* so this entry describes everything we can do... */
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
|
|
SNDRV_PCM_FMTBIT_S16_BE |
|
|
|
|
SNDRV_PCM_FMTBIT_S24_BE
|
|
|
|
#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
|
|
|
|
| SNDRV_PCM_FMTBIT_COMPRESSED_16BE
|
|
|
|
#endif
|
|
|
|
,
|
|
|
|
.rates = SNDRV_PCM_RATE_8000_96000,
|
|
|
|
.tag = 0,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/* analog output */
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
|
|
SNDRV_PCM_FMTBIT_S16_BE |
|
|
|
|
SNDRV_PCM_FMTBIT_S24_BE,
|
|
|
|
.rates = SNDRV_PCM_RATE_8000_96000,
|
|
|
|
.transfer_in = 0,
|
|
|
|
.must_be_clock_source = 0,
|
|
|
|
.tag = 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
/* digital pcm output, also possible for analog out */
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_S8 |
|
|
|
|
SNDRV_PCM_FMTBIT_S16_BE |
|
|
|
|
SNDRV_PCM_FMTBIT_S24_BE,
|
|
|
|
.rates = SNDRV_PCM_RATE_32000 |
|
|
|
|
SNDRV_PCM_RATE_44100 |
|
|
|
|
SNDRV_PCM_RATE_48000,
|
|
|
|
.transfer_in = 0,
|
|
|
|
.must_be_clock_source = 0,
|
|
|
|
.tag = 2,
|
|
|
|
},
|
|
|
|
#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
|
2007-07-14 06:33:15 +08:00
|
|
|
/* Once alsa gets supports for this kind of thing we can add it... */
|
2006-06-21 21:42:43 +08:00
|
|
|
{
|
|
|
|
/* digital compressed output */
|
|
|
|
.formats = SNDRV_PCM_FMTBIT_COMPRESSED_16BE,
|
|
|
|
.rates = SNDRV_PCM_RATE_32000 |
|
|
|
|
SNDRV_PCM_RATE_44100 |
|
|
|
|
SNDRV_PCM_RATE_48000,
|
|
|
|
.tag = 2,
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
{}
|
|
|
|
};
|
|
|
|
|
|
|
|
static int onyx_usable(struct codec_info_item *cii,
|
|
|
|
struct transfer_info *ti,
|
|
|
|
struct transfer_info *out)
|
|
|
|
{
|
|
|
|
u8 v;
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
int spdif_enabled, analog_enabled;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
|
|
spdif_enabled = !!(v & ONYX_SPDIF_ENABLE);
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
|
2008-10-23 21:47:56 +08:00
|
|
|
analog_enabled =
|
2006-06-21 21:42:43 +08:00
|
|
|
(v & (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT))
|
|
|
|
!= (ONYX_MUTE_RIGHT|ONYX_MUTE_LEFT);
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
switch (ti->tag) {
|
|
|
|
case 0: return 1;
|
|
|
|
case 1: return analog_enabled;
|
|
|
|
case 2: return spdif_enabled;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_prepare(struct codec_info_item *cii,
|
|
|
|
struct bus_info *bi,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
u8 v;
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
int err = -EBUSY;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
|
|
|
|
#ifdef SNDRV_PCM_FMTBIT_COMPRESSED_16BE
|
|
|
|
if (substream->runtime->format == SNDRV_PCM_FMTBIT_COMPRESSED_16BE) {
|
|
|
|
/* mute and lock analog output */
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DAC_CONTROL, &v);
|
2007-07-14 06:33:15 +08:00
|
|
|
if (onyx_write_register(onyx,
|
2006-06-21 21:42:43 +08:00
|
|
|
ONYX_REG_DAC_CONTROL,
|
|
|
|
v | ONYX_MUTE_RIGHT | ONYX_MUTE_LEFT))
|
|
|
|
goto out_unlock;
|
|
|
|
onyx->analog_locked = 1;
|
|
|
|
err = 0;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
switch (substream->runtime->rate) {
|
|
|
|
case 32000:
|
|
|
|
case 44100:
|
|
|
|
case 48000:
|
|
|
|
/* these rates are ok for all outputs */
|
|
|
|
/* FIXME: program spdif channel control bits here so that
|
|
|
|
* userspace doesn't have to if it only plays pcm! */
|
|
|
|
err = 0;
|
|
|
|
goto out_unlock;
|
|
|
|
default:
|
|
|
|
/* got some rate that the digital output can't do,
|
|
|
|
* so disable and lock it */
|
|
|
|
onyx_read_register(cii->codec_data, ONYX_REG_DIG_INFO4, &v);
|
|
|
|
if (onyx_write_register(onyx,
|
|
|
|
ONYX_REG_DIG_INFO4,
|
|
|
|
v & ~ONYX_SPDIF_ENABLE))
|
|
|
|
goto out_unlock;
|
|
|
|
onyx->spdif_locked = 1;
|
|
|
|
err = 0;
|
|
|
|
goto out_unlock;
|
|
|
|
}
|
|
|
|
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_open(struct codec_info_item *cii,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx->open_count++;
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_close(struct codec_info_item *cii,
|
|
|
|
struct snd_pcm_substream *substream)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
onyx->open_count--;
|
|
|
|
if (!onyx->open_count)
|
|
|
|
onyx->spdif_locked = onyx->analog_locked = 0;
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_switch_clock(struct codec_info_item *cii,
|
|
|
|
enum clock_switch what)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
/* this *MUST* be more elaborate later... */
|
|
|
|
switch (what) {
|
|
|
|
case CLOCK_SWITCH_PREPARE_SLAVE:
|
|
|
|
onyx->codec.gpio->methods->all_amps_off(onyx->codec.gpio);
|
|
|
|
break;
|
|
|
|
case CLOCK_SWITCH_SLAVE:
|
|
|
|
onyx->codec.gpio->methods->all_amps_restore(onyx->codec.gpio);
|
|
|
|
break;
|
|
|
|
default: /* silence warning */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
|
|
|
|
static int onyx_suspend(struct codec_info_item *cii, pm_message_t state)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
u8 v;
|
|
|
|
int err = -ENXIO;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
|
|
|
if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
|
|
|
|
goto out_unlock;
|
|
|
|
onyx_write_register(onyx, ONYX_REG_CONTROL, v | ONYX_ADPSV | ONYX_DAPSV);
|
|
|
|
/* Apple does a sleep here but the datasheet says to do it on resume */
|
|
|
|
err = 0;
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int onyx_resume(struct codec_info_item *cii)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = cii->codec_data;
|
|
|
|
u8 v;
|
|
|
|
int err = -ENXIO;
|
|
|
|
|
|
|
|
mutex_lock(&onyx->mutex);
|
2006-12-18 20:20:06 +08:00
|
|
|
|
|
|
|
/* reset codec */
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
|
|
msleep(1);
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
|
|
|
|
msleep(1);
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
|
|
msleep(1);
|
|
|
|
|
|
|
|
/* take codec out of suspend (if it still is after reset) */
|
2006-06-21 21:42:43 +08:00
|
|
|
if (onyx_read_register(onyx, ONYX_REG_CONTROL, &v))
|
|
|
|
goto out_unlock;
|
|
|
|
onyx_write_register(onyx, ONYX_REG_CONTROL, v & ~(ONYX_ADPSV | ONYX_DAPSV));
|
|
|
|
/* FIXME: should divide by sample rate, but 8k is the lowest we go */
|
|
|
|
msleep(2205000/8000);
|
|
|
|
/* reset all values */
|
|
|
|
onyx_register_init(onyx);
|
|
|
|
err = 0;
|
|
|
|
out_unlock:
|
|
|
|
mutex_unlock(&onyx->mutex);
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif /* CONFIG_PM */
|
|
|
|
|
|
|
|
static struct codec_info onyx_codec_info = {
|
|
|
|
.transfers = onyx_transfers,
|
|
|
|
.sysclock_factor = 256,
|
|
|
|
.bus_factor = 64,
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
.usable = onyx_usable,
|
|
|
|
.prepare = onyx_prepare,
|
|
|
|
.open = onyx_open,
|
|
|
|
.close = onyx_close,
|
|
|
|
.switch_clock = onyx_switch_clock,
|
|
|
|
#ifdef CONFIG_PM
|
|
|
|
.suspend = onyx_suspend,
|
|
|
|
.resume = onyx_resume,
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
|
|
|
static int onyx_init_codec(struct aoa_codec *codec)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = codec_to_onyx(codec);
|
|
|
|
struct snd_kcontrol *ctl;
|
|
|
|
struct codec_info *ci = &onyx_codec_info;
|
|
|
|
u8 v;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if (!onyx->codec.gpio || !onyx->codec.gpio->methods) {
|
|
|
|
printk(KERN_ERR PFX "gpios not assigned!!\n");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
|
|
msleep(1);
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 1);
|
|
|
|
msleep(1);
|
|
|
|
onyx->codec.gpio->methods->set_hw_reset(onyx->codec.gpio, 0);
|
|
|
|
msleep(1);
|
2008-10-23 21:47:56 +08:00
|
|
|
|
2006-06-21 21:42:43 +08:00
|
|
|
if (onyx_register_init(onyx)) {
|
|
|
|
printk(KERN_ERR PFX "failed to initialise onyx registers\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2014-02-04 18:16:31 +08:00
|
|
|
if (aoa_snd_device_new(SNDRV_DEV_CODEC, onyx, &ops)) {
|
2006-06-21 21:42:43 +08:00
|
|
|
printk(KERN_ERR PFX "failed to create onyx snd device!\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* nothing connected? what a joke! */
|
|
|
|
if ((onyx->codec.connected & 0xF) == 0)
|
|
|
|
return -ENOTCONN;
|
|
|
|
|
|
|
|
/* if no inputs are present... */
|
|
|
|
if ((onyx->codec.connected & 0xC) == 0) {
|
|
|
|
if (!onyx->codec_info)
|
|
|
|
onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
|
|
|
|
if (!onyx->codec_info)
|
|
|
|
return -ENOMEM;
|
|
|
|
ci = onyx->codec_info;
|
|
|
|
*ci = onyx_codec_info;
|
|
|
|
ci->transfers++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if no outputs are present... */
|
|
|
|
if ((onyx->codec.connected & 3) == 0) {
|
|
|
|
if (!onyx->codec_info)
|
|
|
|
onyx->codec_info = kmalloc(sizeof(struct codec_info), GFP_KERNEL);
|
|
|
|
if (!onyx->codec_info)
|
|
|
|
return -ENOMEM;
|
|
|
|
ci = onyx->codec_info;
|
|
|
|
/* this is fine as there have to be inputs
|
|
|
|
* if we end up in this part of the code */
|
|
|
|
*ci = onyx_codec_info;
|
|
|
|
ci->transfers[1].formats = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (onyx->codec.soundbus_dev->attach_codec(onyx->codec.soundbus_dev,
|
|
|
|
aoa_get_card(),
|
|
|
|
ci, onyx)) {
|
|
|
|
printk(KERN_ERR PFX "error creating onyx pcm\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
#define ADDCTL(n) \
|
|
|
|
do { \
|
|
|
|
ctl = snd_ctl_new1(&n, onyx); \
|
|
|
|
if (ctl) { \
|
|
|
|
ctl->id.device = \
|
|
|
|
onyx->codec.soundbus_dev->pcm->device; \
|
|
|
|
err = aoa_snd_ctl_add(ctl); \
|
|
|
|
if (err) \
|
|
|
|
goto error; \
|
|
|
|
} \
|
|
|
|
} while (0)
|
|
|
|
|
|
|
|
if (onyx->codec.soundbus_dev->pcm) {
|
|
|
|
/* give the user appropriate controls
|
|
|
|
* depending on what inputs are connected */
|
|
|
|
if ((onyx->codec.connected & 0xC) == 0xC)
|
|
|
|
ADDCTL(capture_source_control);
|
|
|
|
else if (onyx->codec.connected & 4)
|
|
|
|
onyx_set_capture_source(onyx, 0);
|
|
|
|
else
|
|
|
|
onyx_set_capture_source(onyx, 1);
|
|
|
|
if (onyx->codec.connected & 0xC)
|
|
|
|
ADDCTL(inputgain_control);
|
|
|
|
|
|
|
|
/* depending on what output is connected,
|
|
|
|
* give the user appropriate controls */
|
|
|
|
if (onyx->codec.connected & 1) {
|
|
|
|
ADDCTL(volume_control);
|
|
|
|
ADDCTL(mute_control);
|
|
|
|
ADDCTL(ovr1_control);
|
|
|
|
ADDCTL(flt0_control);
|
|
|
|
ADDCTL(hpf_control);
|
|
|
|
ADDCTL(dm12_control);
|
|
|
|
/* spdif control defaults to off */
|
|
|
|
}
|
|
|
|
if (onyx->codec.connected & 2) {
|
|
|
|
ADDCTL(onyx_spdif_mask);
|
|
|
|
ADDCTL(onyx_spdif_ctrl);
|
|
|
|
}
|
|
|
|
if ((onyx->codec.connected & 3) == 3)
|
|
|
|
ADDCTL(spdif_control);
|
|
|
|
/* if only S/PDIF is connected, enable it unconditionally */
|
|
|
|
if ((onyx->codec.connected & 3) == 2) {
|
|
|
|
onyx_read_register(onyx, ONYX_REG_DIG_INFO4, &v);
|
|
|
|
v |= ONYX_SPDIF_ENABLE;
|
|
|
|
onyx_write_register(onyx, ONYX_REG_DIG_INFO4, v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#undef ADDCTL
|
|
|
|
printk(KERN_INFO PFX "attached to onyx codec via i2c\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
error:
|
|
|
|
onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
|
|
|
|
snd_device_free(aoa_get_card(), onyx);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void onyx_exit_codec(struct aoa_codec *codec)
|
|
|
|
{
|
|
|
|
struct onyx *onyx = codec_to_onyx(codec);
|
|
|
|
|
|
|
|
if (!onyx->codec.soundbus_dev) {
|
|
|
|
printk(KERN_ERR PFX "onyx_exit_codec called without soundbus_dev!\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
onyx->codec.soundbus_dev->detach_codec(onyx->codec.soundbus_dev, onyx);
|
|
|
|
}
|
|
|
|
|
2009-04-21 04:54:25 +08:00
|
|
|
static int onyx_i2c_probe(struct i2c_client *client,
|
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
2012-06-09 21:58:56 +08:00
|
|
|
struct device_node *node = client->dev.of_node;
|
2006-06-21 21:42:43 +08:00
|
|
|
struct onyx *onyx;
|
|
|
|
u8 dummy;
|
|
|
|
|
|
|
|
onyx = kzalloc(sizeof(struct onyx), GFP_KERNEL);
|
|
|
|
|
|
|
|
if (!onyx)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
mutex_init(&onyx->mutex);
|
2009-04-21 04:54:25 +08:00
|
|
|
onyx->i2c = client;
|
|
|
|
i2c_set_clientdata(client, onyx);
|
2006-06-21 21:42:43 +08:00
|
|
|
|
|
|
|
/* we try to read from register ONYX_REG_CONTROL
|
|
|
|
* to check if the codec is present */
|
|
|
|
if (onyx_read_register(onyx, ONYX_REG_CONTROL, &dummy) != 0) {
|
|
|
|
printk(KERN_ERR PFX "failed to read control register\n");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2007-03-27 17:50:19 +08:00
|
|
|
strlcpy(onyx->codec.name, "onyx", MAX_CODEC_NAME_LEN);
|
2006-06-21 21:42:43 +08:00
|
|
|
onyx->codec.owner = THIS_MODULE;
|
|
|
|
onyx->codec.init = onyx_init_codec;
|
|
|
|
onyx->codec.exit = onyx_exit_codec;
|
|
|
|
onyx->codec.node = of_node_get(node);
|
|
|
|
|
|
|
|
if (aoa_codec_register(&onyx->codec)) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
printk(KERN_DEBUG PFX "created and attached onyx instance\n");
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
|
|
kfree(onyx);
|
2009-04-21 04:54:25 +08:00
|
|
|
return -ENODEV;
|
2006-06-21 21:42:43 +08:00
|
|
|
}
|
|
|
|
|
2009-04-21 04:54:25 +08:00
|
|
|
static int onyx_i2c_remove(struct i2c_client *client)
|
2006-06-21 21:42:43 +08:00
|
|
|
{
|
2009-04-21 04:54:25 +08:00
|
|
|
struct onyx *onyx = i2c_get_clientdata(client);
|
2006-06-21 21:42:43 +08:00
|
|
|
|
|
|
|
aoa_codec_unregister(&onyx->codec);
|
|
|
|
of_node_put(onyx->codec.node);
|
2011-09-09 19:04:45 +08:00
|
|
|
kfree(onyx->codec_info);
|
2006-06-21 21:42:43 +08:00
|
|
|
kfree(onyx);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-04-21 04:54:25 +08:00
|
|
|
static const struct i2c_device_id onyx_i2c_id[] = {
|
2012-06-09 21:58:56 +08:00
|
|
|
{ "MAC,pcm3052", 0 },
|
2009-04-21 04:54:25 +08:00
|
|
|
{ }
|
|
|
|
};
|
2012-06-09 21:58:56 +08:00
|
|
|
MODULE_DEVICE_TABLE(i2c,onyx_i2c_id);
|
2009-04-21 04:54:25 +08:00
|
|
|
|
2006-06-21 21:42:43 +08:00
|
|
|
static struct i2c_driver onyx_driver = {
|
|
|
|
.driver = {
|
|
|
|
.name = "aoa_codec_onyx",
|
|
|
|
},
|
2009-04-21 04:54:25 +08:00
|
|
|
.probe = onyx_i2c_probe,
|
|
|
|
.remove = onyx_i2c_remove,
|
|
|
|
.id_table = onyx_i2c_id,
|
2006-06-21 21:42:43 +08:00
|
|
|
};
|
|
|
|
|
2012-01-27 15:23:51 +08:00
|
|
|
module_i2c_driver(onyx_driver);
|