/*
* 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 <linux/slab.h>
MODULE_AUTHOR("Johannes Berg <johannes@sipsolutions.net>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("pcm3052 (onyx) codec driver for snd-aoa");
#include "onyx.h"
#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];
struct i2c_client *i2c;
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;
}
v = i2c_smbus_read_byte_data(onyx->i2c, reg);
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;
result = i2c_smbus_write_byte_data(onyx->i2c, reg, value);
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;
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->