/*
 * Audio support for codec Philips UDA1380
 *
 * 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.
 *
 * Copyright (c) 2005 Giorgio Padrin <giorgio@mandarinlogiq.org>
 */

#include "adriver.h"
 
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/i2c.h>

#include <sound/core.h>
#include <sound/control.h>
#include <sound/initval.h>
#include <sound/info.h>

#include <asm/byteorder.h>

#include <sound/uda1380.h>

/* begin {{ I2C }} */

static struct i2c_driver snd_uda1380_i2c_driver = {
	.driver = {
		.name = "uda1380-i2c"
	},
};

static int snd_uda1380_i2c_init(void)
{
	return i2c_add_driver(&snd_uda1380_i2c_driver);
}

static void snd_uda1380_i2c_free(void)
{
	i2c_del_driver(&snd_uda1380_i2c_driver);
}

static inline int snd_uda1380_i2c_probe(struct snd_uda1380 *uda)
{
	if (uda->i2c_client.adapter == NULL ||
	    (uda->i2c_client.addr & 0xfd) != 0x18) return -EINVAL;
	if (i2c_smbus_xfer(uda->i2c_client.adapter, uda->i2c_client.addr,
			   0, 0, 0, I2C_SMBUS_QUICK, NULL) < 0)
		return -ENODEV;
	else return 0;
}

static int snd_uda1380_i2c_attach(struct snd_uda1380 *uda)
{
	int ret = 0;
	if ((ret = snd_uda1380_i2c_probe(uda)) < 0) return ret;
	snprintf(uda->i2c_client.name, sizeof(uda->i2c_client.name),
		 "uda1380-i2c at %d-%04x",
		 i2c_adapter_id(uda->i2c_client.adapter), uda->i2c_client.addr);
	return i2c_attach_client(&uda->i2c_client);
}

static void snd_uda1380_i2c_detach(struct snd_uda1380 *uda)
{
	i2c_detach_client(&uda->i2c_client);
}

/* end {{ I2C }} */


/* begin {{ Registers Cache <--> HW }} */

static inline void _cpu_to_be16_string(u16 *to, u16 *from, unsigned int count)
{
#ifdef __BIG_ENDIAN
	memcpy(to, from, count << 1);
#else
	u16 *i_to, *i_from;
	for (i_to = to, i_from = from; i_to < to + count; i_to++, i_from++)
		*i_to = cpu_to_be16(*i_from);
#endif
}

static inline void _be16_to_cpu_string(u16 *to, u16 *from, unsigned int count)
{
#ifdef __BIG_ENDIAN
	memcpy(to, from, count << 1);
#else
	u16 *i_to, *i_from;
	for (i_to = to, i_from = from; i_to < to + count; i_to++, i_from++)
		*i_to = be16_to_cpu(*i_from);
#endif
}

/* transfer of up to 5 consecutive regs (a section)*/
static int snd_uda1380_hwsync(struct snd_uda1380 *uda, int read,
			      u8 start_reg, unsigned int count)
{
	struct i2c_msg msgs[2];
	u8 buf[11];
	int ret;

	if (snd_BUG_ON(count * 2 >= sizeof(buf)))
		return -EINVAL;
	if (snd_BUG_ON(start_reg >= ARRAY_SIZE(uda->regs)))
		return -EINVAL;
	if (snd_BUG_ON(start_reg + count > ARRAY_SIZE(uda->regs)))
		return -EINVAL;

	/* setup i2c msgs */
	msgs[0].addr = uda->i2c_client.addr;
	msgs[0].flags = 0;
	msgs[0].buf = buf;
	if (!read)
		msgs[0].len = (count << 1) + 1;
	else {
		msgs[1].flags = I2C_M_RD;
		msgs[1].addr = msgs[0].addr;
		msgs[1].buf = msgs[0].buf + 1;
		msgs[0].len = 1;
		msgs[1].len = count << 1;
	}

	buf[0] = start_reg;

	/* regs -> buffer, on write */
	if (!read && start_reg != 0x7f) /* 0x7f: software reset */
		 _cpu_to_be16_string((u16*) (buf + 1), &uda->regs[start_reg], count);

	/* i2c transfer */
	ret = i2c_transfer(uda->i2c_client.adapter, msgs, read ? 2 : 1);
	if (ret != (read ? 2 : 1)) return ret; /* transfer error */ //@@ error ret < 0, or not ?

	/* regs <- buffer, on read */
	if (read && start_reg != 0x7f) /* 0x7f: software reset */
		_be16_to_cpu_string(&uda->regs[start_reg], (u16*) (buf + 1), count);

	return 0;
}

static inline int snd_uda1380_hwsync_read(struct snd_uda1380 *uda,
				 	  u8 start_reg, unsigned int count)
{
	return snd_uda1380_hwsync(uda, 1, start_reg, count);
}

static inline int snd_uda1380_hwsync_write(struct snd_uda1380 *uda,
					   u8 start_reg, unsigned int count)
{
	return snd_uda1380_hwsync(uda, 0, start_reg, count);
}

/* end {{ Registers Cache <--> HW }} */


/* begin {{ Registers Cache Ops }} */

static inline int _cache_flush1(struct snd_uda1380 *uda, unsigned int i)
{
	snd_uda1380_hwsync_write(uda,
				 uda->cache_dirty[i].start_reg,
				 uda->cache_dirty[i].count);
	uda->cache_dirty[i].start_reg = i << 4;
	uda->cache_dirty[i].count = 0;
	return 0;
}

static inline int snd_uda1380_cache_try_flush(struct snd_uda1380 *uda)
{

	if (uda->cache_dirty[0].count)
		if (uda->powered_on) _cache_flush1(uda, 0);
	if (uda->cache_dirty[1].count)
		if (uda->playback_clock_on) _cache_flush1(uda, 1);
	if (uda->cache_dirty[2].count)
		if (uda->capture_clock_on) _cache_flush1(uda, 2);
	return 0;
}

/* Note: regs in the same section */
static int snd_uda1380_cache_dirty_zone(struct snd_uda1380 *uda,
					u8 start_reg, unsigned int count)
{
	unsigned int i = start_reg >> 4;

	if (!count) return 0;
	if (!uda->cache_dirty[i].count) {
		uda->cache_dirty[i].start_reg = start_reg;
		uda->cache_dirty[i].count = count;
	} else {
		int sr0 = uda->cache_dirty[i].start_reg;
		int er0 = sr0 + uda->cache_dirty[i].count -1;
		int sr1 = start_reg;
		int er1 = sr1 + count -1;
		int sr = (sr1 <= sr0) ? sr1 : sr0;
		int er = (er1 >= er0) ? er1 : er0;
		uda->cache_dirty[i].start_reg = (u8) sr;
		uda->cache_dirty[i].count = (u8) (er - sr + 1);
	}
	snd_uda1380_cache_try_flush(uda);
	return 0;
}

static inline int snd_uda1380_cache_dirty(struct snd_uda1380 *uda, u8 reg)
{
	return snd_uda1380_cache_dirty_zone(uda, reg, 1);
}

static inline int snd_uda1380_cache_dirty_all(struct snd_uda1380 *uda)
{
	snd_uda1380_cache_dirty_zone(uda, 0x00, 5);
	snd_uda1380_cache_dirty_zone(uda, 0x10, 5);
	snd_uda1380_cache_dirty_zone(uda, 0x20, 4);
	return 0;
}

/* end {{ Registers Cache Ops }} */


static inline void snd_uda1380_lock(struct snd_uda1380 *uda)
{
	down(&uda->sem);
}

static inline void snd_uda1380_unlock(struct snd_uda1380 *uda)
{
	up(&uda->sem);
}

#define WRITE_MASK(i, val, mask)	(((i) & ~(mask)) | ((val) & (mask)))


/* begin {{ Controls }} */

/* a control element in a register */
struct snd_uda1380_uctl_reg_elem_int {
	unsigned int
		is_stereo:1,
		inv_range:1,	/* inverted range */
		rot_range:1,	/* rotated half range (from a 2's complement) */
		reg:8,
		shift:4,	/* shift */
		mask:16;
};

#define ROT_RANGE(val, mask) \
	(((val) + (((mask) + 1) >> 1)) & (mask))
#define INV_RANGE(val, mask) \
	(~(val) & (mask))

static int snd_uda1380_actl_reg_elem_int_info(struct snd_kcontrol *kcontrol,
					      struct snd_ctl_elem_info *uinfo)
{
	struct snd_uda1380_uctl_reg_elem_int *uctl =
		(struct snd_uda1380_uctl_reg_elem_int *) kcontrol->private_value;

	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = uctl->is_stereo ? 2 : 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = uctl->is_stereo ? uctl->mask & 0x00ff : uctl->mask;
	return 0;
}

static int snd_uda1380_actl_reg_elem_int_get(struct snd_kcontrol *kcontrol,
					     struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;
	struct snd_uda1380_uctl_reg_elem_int *uctl =
		(struct snd_uda1380_uctl_reg_elem_int *) kcontrol->private_value;
	unsigned int val, val_l, val_r, mask1;

	val = uda->regs[uctl->reg] >> uctl->shift & uctl->mask;
	if (uctl->is_stereo) {
		val_l = val >> 8; val_r = val & 0x00ff; mask1 = uctl->mask & 0x00ff;
		if (uctl->rot_range) { val_l = ROT_RANGE(val_l, mask1);
				       val_r = ROT_RANGE(val_r, mask1); }
		if (uctl->inv_range) { val_l = INV_RANGE(val_l, mask1);
				       val_r = INV_RANGE(val_r, mask1); }
		ucontrol->value.integer.value[0] = val_l;
		ucontrol->value.integer.value[1] = val_r;
	} else {
		if (uctl->rot_range) val = ROT_RANGE(val, uctl->mask);
		if (uctl->inv_range) val = INV_RANGE(val, uctl->mask);
		ucontrol->value.integer.value[0] = val;
	}
	return 0;
}

static int snd_uda1380_actl_reg_elem_int_put(struct snd_kcontrol *kcontrol,
					     struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;
	struct snd_uda1380_uctl_reg_elem_int *uctl =
		(struct snd_uda1380_uctl_reg_elem_int *) kcontrol->private_value;
	unsigned int val, val_l, val_r, mask1;

	if (uctl->is_stereo) {
		val_l = ucontrol->value.integer.value[0];
		val_r = ucontrol->value.integer.value[1];
		mask1 = uctl->mask & 0x00ff;
		if (uctl->rot_range) { val_l = ROT_RANGE(val_l, mask1);
				       val_r = ROT_RANGE(val_r, mask1); }
		if (uctl->inv_range) { val_l = INV_RANGE(val_l, mask1);
				       val_r = INV_RANGE(val_r, mask1); }
		val = val_l << 8 | val_r;
	} else {
		val = ucontrol->value.integer.value[0];
		if (uctl->rot_range) val = ROT_RANGE(val, uctl->mask);
		if (uctl->inv_range) val = INV_RANGE(val, uctl->mask);
	}

	snd_uda1380_lock(uda);
	uda->regs[uctl->reg] = WRITE_MASK(uda->regs[uctl->reg],
					  val << uctl->shift, uctl->mask << uctl->shift);

	snd_uda1380_cache_dirty(uda, uctl->reg);
	snd_uda1380_unlock(uda);
	return 0;
}

/* declarations of ALSA reg_elem_int controls */

#define ACTL_REG_ELEM_INT(ctl_name, _name, _reg, _shift, _mask, _inv_range, _rot_range) \
static struct snd_uda1380_uctl_reg_elem_int snd_uda1380_actl_ ## ctl_name ## _pvalue = \
{ .reg = _reg, .shift = _shift, .mask = _mask, \
  .inv_range = _inv_range, .rot_range = _rot_range }; \
static struct snd_kcontrol_new snd_uda1380_actl_ ## ctl_name = \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = _name, .info = snd_uda1380_actl_reg_elem_int_info, \
  .get = snd_uda1380_actl_reg_elem_int_get, .put = snd_uda1380_actl_reg_elem_int_put, \
  .private_value = (unsigned long)&snd_uda1380_actl_ ## ctl_name ## _pvalue };

#define ACTL_REG_ELEM_INT_STEREO(ctl_name, _name, _reg, _shift, _mask, _inv_range, _rot_range) \
static struct snd_uda1380_uctl_reg_elem_int snd_uda1380_actl_ ## ctl_name ## _pvalue = \
{ .is_stereo = 1, .reg = _reg, .shift = _shift, .mask = _mask, \
  .inv_range = _inv_range, .rot_range = _rot_range }; \
static struct snd_kcontrol_new snd_uda1380_actl_ ## ctl_name = \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = _name, .info = snd_uda1380_actl_reg_elem_int_info, \
  .get = snd_uda1380_actl_reg_elem_int_get, .put = snd_uda1380_actl_reg_elem_int_put, \
  .private_value = (unsigned long)&snd_uda1380_actl_ ## ctl_name ## _pvalue };\

ACTL_REG_ELEM_INT_STEREO(playback_volume, "Master Playback Volume", 0x10, 0, 0xffff, 1, 0)
ACTL_REG_ELEM_INT(deemphasis, "De-Emphasis", 0x13, 0, 0x0007, 0, 0)
ACTL_REG_ELEM_INT(tone_ctl_strength, "Tone Control - Strength", 0x12, 14, 0x0003, 0, 0)
ACTL_REG_ELEM_INT_STEREO(tone_ctl_treble, "Tone Control - Treble", 0x12, 4, 0x0303, 0, 0)
ACTL_REG_ELEM_INT_STEREO(tone_ctl_bass, "Tone Control - Bass", 0x12, 0, 0x0f0f, 0, 0)
ACTL_REG_ELEM_INT(mic_gain, "Mic Capture Volume", 0x22, 8, 0x000f, 0, 0)
ACTL_REG_ELEM_INT_STEREO(line_in_gain, "Line Capture Volume", 0x21, 0, 0x0f0f, 0, 0)
ACTL_REG_ELEM_INT_STEREO(capture_volume, "Capture Volume", 0x20, 0, 0xffff, 0, 1)

struct snd_uda1380_uctl_bool {
	int (*get) (struct snd_uda1380 *uda);
	int (*set) (struct snd_uda1380 *uda, int on);
};

static int snd_uda1380_actl_bool_info(struct snd_kcontrol *kcontrol,
				      struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
	uinfo->count = 1;
	return 0;
}

static int snd_uda1380_actl_bool_get(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;
	struct snd_uda1380_uctl_bool *uctl =
		(struct snd_uda1380_uctl_bool *) kcontrol->private_value;

	ucontrol->value.integer.value[0] = uctl->get(uda);
	return 0;
}

static int snd_uda1380_actl_bool_put(struct snd_kcontrol *kcontrol,
				     struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;
	struct snd_uda1380_uctl_bool *uctl =
		(struct snd_uda1380_uctl_bool *) kcontrol->private_value;

	return uctl->set(uda, ucontrol->value.integer.value[0]);
}

/* Register flags */
#define R00_EN_ADC	0x0800
#define R00_EN_DEC	0x0400
#define R00_EN_DAC	0x0200
#define R00_EN_INT	0x0100
#define R02_PON_HP	0x2000
#define R02_PON_DAC	0x0400
#define R02_PON_BIAS	0x0100
#define R02_PON_LNA	0x0010
#define R02_PON_PGAL	0x0008
#define R02_PON_ADCL	0x0004
#define R02_PON_PGAR	0x0002
#define R02_PON_ADCR	0x0001
#define R13_MTM		0x4000
#define R21_MT_ADC	0x8000
#define R22_SEL_LNA	0x0008
#define R22_SEL_MIC	0x0004
#define R22_SKIP_DCFIL	0x0002
#define R23_AGC_EN	0x0001

static int snd_uda1380_uctl_playback_switch_get(struct snd_uda1380 *uda)
{
	return uda->playback_switch_ureq;
}

static int snd_uda1380_uctl_playback_switch_set(struct snd_uda1380 *uda, int on)
{
	snd_uda1380_lock(uda);
	uda->playback_switch_ureq = on;
	if (uda->playback_on) {
		uda->regs[0x13] = WRITE_MASK(uda->regs[0x13],
					     on ? 0x0000 : R13_MTM, R13_MTM);
		snd_uda1380_hwsync_write(uda, 0x13, 1);
	}
	snd_uda1380_unlock(uda);
	return 0;
}

static int snd_uda1380_uctl_capture_switch_get(struct snd_uda1380 *uda)
{
	return uda->capture_switch_ureq;
}

static int snd_uda1380_uctl_capture_switch_set(struct snd_uda1380 *uda, int on)
{
	snd_uda1380_lock(uda);
	uda->capture_switch_ureq = on;
	if (uda->capture_on) {
		uda->regs[0x21] = WRITE_MASK(uda->regs[0x21],
					     on ? 0x0000 : R21_MT_ADC, R21_MT_ADC);
		snd_uda1380_hwsync_write(uda, 0x21, 1);
	}
	snd_uda1380_unlock(uda);
	return 0;
}

static int snd_uda1380_uctl_agc_get(struct snd_uda1380 *uda)
{
	return uda->regs[0x23] & R23_AGC_EN;
}

static int snd_uda1380_uctl_agc_set (struct snd_uda1380 *uda, int on)
{
	snd_uda1380_lock(uda);
	uda->regs[0x23] = WRITE_MASK(uda->regs[0x23],
				     on ? R23_AGC_EN : 0x0000, R23_AGC_EN);
	snd_uda1380_cache_dirty_zone(uda, 0x23, 1);
	snd_uda1380_unlock(uda);
	return 0;
}

#define ACTL_BOOL(ctl_name, _name) \
static struct snd_uda1380_uctl_bool snd_uda1380_actl_ ## ctl_name ## _pvalue = \
{ .get = snd_uda1380_uctl_ ## ctl_name ## _get, \
  .set = snd_uda1380_uctl_ ## ctl_name ## _set }; \
static struct snd_kcontrol_new snd_uda1380_actl_ ## ctl_name = \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = _name, .info = snd_uda1380_actl_bool_info, \
  .get = snd_uda1380_actl_bool_get, .put = snd_uda1380_actl_bool_put, \
  .private_value = (unsigned long) &snd_uda1380_actl_ ## ctl_name ## _pvalue };

ACTL_BOOL(playback_switch, "Master Playback Switch")
ACTL_BOOL(agc, "AGC")
ACTL_BOOL(capture_switch, "Capture Switch")

static inline void snd_uda1380_line_out_on(struct snd_uda1380 *uda, int on);

void snd_uda1380_hp_connected(struct snd_uda1380 *uda, int connected)
{
	snd_uda1380_lock(uda);
	if (connected != uda->hp_connected) {
		uda->hp_connected = connected;
		if (uda->playback_on) {
			uda->regs[0x02] =
				WRITE_MASK(uda->regs[0x02],
					   connected ? R02_PON_HP : 0x0000, R02_PON_HP);
			snd_uda1380_hwsync_write(uda, 0x02, 1);
			if (uda->hp_or_line_out)
				snd_uda1380_line_out_on(uda, !connected);
		}
	}
	snd_uda1380_unlock(uda);
}

static int snd_uda1380_uctl_select_capture_source(struct snd_uda1380 *uda,
						  enum snd_uda1380_capture_source capture_source)
{
	snd_uda1380_lock(uda);
	uda->capture_source = capture_source;

	if ((uda->capture_source == SND_UDA1380_CAP_SOURCE_MIC) ==
	    ((uda->regs[0x22] & R22_SEL_MIC) == R22_SEL_MIC))
		 { snd_uda1380_unlock(uda); return 0; }

	if (uda->capture_on) {
		if (uda->capture_source == SND_UDA1380_CAP_SOURCE_MIC) {
			uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
						     R02_PON_LNA, R02_PON_LNA);
			snd_uda1380_hwsync_write(uda, 0x02, 1);
			uda->regs[0x22] = WRITE_MASK(uda->regs[0x22],
						     R22_SEL_LNA | R22_SEL_MIC,
						     R22_SEL_LNA | R22_SEL_MIC);
			snd_uda1380_hwsync_write(uda, 0x22, 1);
			uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
						     0x0000, R02_PON_PGAL | R02_PON_PGAR);
			snd_uda1380_hwsync_write(uda, 0x02, 1);
		} else {
			uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
						     R02_PON_PGAL | R02_PON_PGAR,
						     R02_PON_PGAL | R02_PON_PGAR);
			snd_uda1380_hwsync_write(uda, 0x02, 1);
			uda->regs[0x22] = WRITE_MASK(uda->regs[0x22],
						     0x0000,
						     R22_SEL_LNA | R22_SEL_MIC);
			snd_uda1380_hwsync_write(uda, 0x22, 1);
			uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
						     0x0000, R02_PON_LNA );
			snd_uda1380_hwsync_write(uda, 0x02, 1);
		}
	}
	snd_uda1380_unlock(uda);
	return 0;
}

static int snd_uda1380_actl_capture_source_info(struct snd_kcontrol *kcontrol,
						struct snd_ctl_elem_info *uinfo)
{
	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
	uinfo->count = 1;
	uinfo->value.enumerated.items = 2;
	if (uinfo->value.enumerated.item == SND_UDA1380_CAP_SOURCE_LINE_IN)
		strcpy(uinfo->value.enumerated.name, "Line");
	else strcpy(uinfo->value.enumerated.name, "Mic");
	return 0;
}

static int snd_uda1380_actl_capture_source_get(struct snd_kcontrol *kcontrol,
					       struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;

	ucontrol->value.enumerated.item[0] = uda->capture_source;
	return 0;
}

static int snd_uda1380_actl_capture_source_put(struct snd_kcontrol *kcontrol,
					       struct snd_ctl_elem_value *ucontrol)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *) kcontrol->private_data;

	return snd_uda1380_uctl_select_capture_source(uda, ucontrol->value.enumerated.item[0]);
}

static struct snd_kcontrol_new snd_uda1380_actl_capture_source = {
	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture Source",
	.info = snd_uda1380_actl_capture_source_info,
	.get = snd_uda1380_actl_capture_source_get,
	.put = snd_uda1380_actl_capture_source_put
};

/* end {{ Controls }} */


/* begin {{ Headphone Detected Notification }} */

static void snd_uda1380_hp_detected_w_fn(void *p)
{
	struct snd_uda1380 *uda = (struct snd_uda1380 *)p;

	snd_uda1380_hp_connected(uda, uda->hp_detected.detected);
}

void snd_uda1380_hp_detected(struct snd_uda1380 *uda, int detected)
{
	if (detected != uda->hp_detected.detected) {
		uda->hp_detected.detected = detected;
		queue_work(uda->hp_detected.wq, &uda->hp_detected.w);
	}
}

static int snd_uda1380_hp_detected_init(struct snd_uda1380 *uda)
{
	INIT_WORK(&uda->hp_detected.w, snd_uda1380_hp_detected_w_fn, uda);
	uda->hp_detected.detected = uda->hp_connected;
	uda->hp_detected.wq = create_singlethread_workqueue("uda1380");
	if (uda->hp_detected.wq) return 0;
	else return -1;
}

static void snd_uda1380_hp_detected_free(struct snd_uda1380 *uda)
{
	destroy_workqueue(uda->hp_detected.wq);
}

/* end {{ Headphone Detected Notification }} */


/* begin {{ Codec Control }} */

static inline int _wait_mute(struct snd_uda1380 *uda, u8 reg, int on)
{
#define MUTE_STATE 0x0004
	unsigned int timeout_count = 50;

	snd_uda1380_hwsync_read(uda, reg, 1);
	while (((uda->regs[reg] & MUTE_STATE) == MUTE_STATE) != on) {
		if (--timeout_count == 0) break;
		msleep(1);
		snd_uda1380_hwsync_read(uda, reg, 1);
	}
	return (timeout_count) ? 0 : -1;
}

static inline int snd_uda1380_power_on(struct snd_uda1380 *uda)
{
	uda->power_on_chip(1);
	uda->reset_pin(1);
	mdelay(1);
	uda->reset_pin(0);
	uda->powered_on = 1;
	return 0;
}

static inline int snd_uda1380_power_off(struct snd_uda1380 *uda)
{
	uda->powered_on = 0;
	uda->power_on_chip(0);
	return 0;
}

static inline void snd_uda1380_line_out_on(struct snd_uda1380 *uda, int on)
{
	if (uda->line_out_on) uda->line_out_on(on);
}

static inline void snd_uda1380_mic_on(struct snd_uda1380 *uda, int on)
{
	if (uda->mic_on) uda->mic_on(on);
}

static inline void snd_uda1380_line_in_on(struct snd_uda1380 *uda, int on)
{
	if (uda->line_in_on) uda->line_in_on(on);
}

static int snd_uda1380_playback_on(struct snd_uda1380 *uda)
{
	unsigned int val;

	if (uda->playback_on) return 0;

	/* power-up */
	uda->regs[0x00] = WRITE_MASK(uda->regs[0x00],
				     R00_EN_DAC | R00_EN_INT, R00_EN_DAC | R00_EN_INT);
	val = R02_PON_DAC;
	val |= uda->hp_connected ? R02_PON_HP : 0x0000;
	uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
				     val, R02_PON_HP | R02_PON_DAC);
	snd_uda1380_hwsync_write(uda, 0x00, 3);
	uda->playback_clock_on = 1;

	snd_uda1380_cache_try_flush(uda);

	/* notify line out is on */
	if (!(uda->hp_or_line_out && uda->hp_connected))
		snd_uda1380_line_out_on(uda, 1);

	/* unmute, in case */
	if (uda->playback_switch_ureq) {
		uda->regs[0x13] = WRITE_MASK(uda->regs[0x13], 0x0000, R13_MTM);
		snd_uda1380_hwsync_write(uda, 0x13, 1);
		_wait_mute(uda, 0x18, 0);
	}

	uda->playback_on = 1;

	return 0;
}

static int snd_uda1380_playback_off(struct snd_uda1380 *uda)
{
	if (!uda->playback_on) return 0;

	uda->playback_on = 0;

	/* mute */
	if (!(uda->regs[0x13] & R13_MTM)) {
		uda->regs[0x13] = WRITE_MASK(uda->regs[0x13], R13_MTM, R13_MTM);
		snd_uda1380_hwsync_write(uda, 0x13, 1);
	}
	_wait_mute(uda, 0x18, 1);

	/* notify line out going off */
	if (!(uda->hp_or_line_out && uda->hp_connected))
		snd_uda1380_line_out_on(uda, 0);

	/* power-down */
	uda->playback_clock_on = 0;
	uda->regs[0x00] = WRITE_MASK(uda->regs[0x00], 0x0000, R00_EN_DAC | R00_EN_INT);
	uda->regs[0x02] = WRITE_MASK(uda->regs[0x02], 0x0000, R02_PON_HP | R02_PON_DAC);
	snd_uda1380_hwsync_write(uda, 0x00, 3);

	return 0;
}

static int snd_uda1380_capture_on(struct snd_uda1380 *uda)
{
	unsigned int val;

	if (uda->capture_on) return 0;

	/* power-up */
	uda->regs[0x00] = WRITE_MASK(uda->regs[0x00],
				     R00_EN_ADC | R00_EN_DEC, R00_EN_ADC | R00_EN_DEC);
	snd_uda1380_hwsync_write(uda, 0x00, 1);
	uda->capture_clock_on = 1;

	snd_uda1380_cache_try_flush(uda);

	val = R02_PON_ADCL | R02_PON_ADCR;
	val |= (uda->regs[0x22] & R22_SEL_MIC) ?
	       R02_PON_LNA :
	       R02_PON_PGAL | R02_PON_PGAR;
	uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
				     val, R02_PON_ADCL | R02_PON_ADCR |
					  R02_PON_LNA | R02_PON_PGAL | R02_PON_PGAR);
	snd_uda1380_hwsync_write(uda, 0x02, 1);

	/* notify input sources */
	if (uda->regs[0x22] & R22_SEL_MIC) snd_uda1380_mic_on(uda, 1);
	else snd_uda1380_line_in_on(uda, 1);

	/* unmute, in case */
	if (uda->playback_switch_ureq) {
		uda->regs[0x21] = WRITE_MASK(uda->regs[0x21], 0x0000, R21_MT_ADC);
		snd_uda1380_hwsync_write(uda, 0x21, 1);
		_wait_mute(uda, 0x28, 0);
	}

	uda->capture_on = 1;

	return 0;
}

static int snd_uda1380_capture_off(struct snd_uda1380 *uda)
{
	if (!uda->capture_on) return 0;

	uda->capture_on = 0;

	/* mute */
	if (!(uda->regs[0x21] & R21_MT_ADC)) {
		uda->regs[0x21] = WRITE_MASK(uda->regs[0x21], R21_MT_ADC, R21_MT_ADC);
		snd_uda1380_hwsync_write(uda, 0x21, 1);
	}
	_wait_mute(uda, 0x28, 1);

	/* notify input sources going off */
	if (uda->regs[0x22] & R22_SEL_MIC) snd_uda1380_mic_on(uda, 0);
	else snd_uda1380_line_in_on(uda, 0);

	/* power-down */
	uda->capture_clock_on = 0;
	uda->regs[0x00] = WRITE_MASK(uda->regs[0x00], 0x0000, R00_EN_ADC | R00_EN_DEC);
	uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
				     0x0000, R02_PON_ADCL | R02_PON_ADCR |
				     R02_PON_LNA | R02_PON_PGAL | R02_PON_PGAR);
	snd_uda1380_hwsync_write(uda, 0x00, 3);

	return 0;
}

int snd_uda1380_open_stream(struct snd_uda1380 *uda, int stream)
{
	snd_uda1380_lock(uda);
	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
		uda->playback_stream_opened = 1;
		snd_uda1380_playback_on(uda);
	} else {
		uda->capture_stream_opened = 1;
		snd_uda1380_capture_on(uda);
	}
	snd_uda1380_unlock(uda);
	return 0;
}

int snd_uda1380_close_stream(struct snd_uda1380 *uda, int stream)
{
	snd_uda1380_lock(uda);
	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
		uda->playback_stream_opened = 0;
		snd_uda1380_playback_off(uda);
	} else {
		uda->capture_stream_opened = 0;
		snd_uda1380_capture_off(uda);
	}
	snd_uda1380_unlock(uda);
	return 0;
}

static int snd_uda1380_init_regs(struct snd_uda1380 *uda)
{
	snd_uda1380_hwsync_read(uda, 0x00, 5);
	snd_uda1380_hwsync_read(uda, 0x10, 5);
	snd_uda1380_hwsync_read(uda, 0x20, 4);

	//@@ MEMO: add some configs

	if (uda->capture_source == SND_UDA1380_CAP_SOURCE_MIC) {
		uda->regs[0x22] = WRITE_MASK(uda->regs[0x22],
					     R22_SEL_LNA | R22_SEL_MIC,
					     R22_SEL_LNA | R22_SEL_MIC);
	}
	uda->regs[0x22] = WRITE_MASK(uda->regs[0x22], 0x0000, R22_SKIP_DCFIL);
	snd_uda1380_hwsync_write(uda, 0x22, 1);

	uda->regs[0x00] = WRITE_MASK(uda->regs[0x00],
				     0x0000, R00_EN_DEC | R00_EN_INT);
	uda->regs[0x02] = WRITE_MASK(uda->regs[0x02],
				     R02_PON_BIAS, R02_PON_BIAS);
	snd_uda1380_hwsync_write(uda, 0x00, 3);

	return 0;
}

int snd_uda1380_suspend(struct snd_uda1380 *uda, pm_message_t state)
{
	snd_uda1380_lock(uda);
	if (uda->playback_on) snd_uda1380_playback_off(uda);
	if (uda->capture_on) snd_uda1380_capture_off(uda);
	snd_uda1380_power_off(uda);
	snd_uda1380_cache_dirty_all(uda);
	snd_uda1380_unlock(uda);
	return 0;
}

int snd_uda1380_resume(struct snd_uda1380 *uda)
{
	snd_uda1380_lock(uda);
	snd_uda1380_power_on(uda);
	snd_uda1380_cache_try_flush(uda);
	if (uda->playback_stream_opened) snd_uda1380_playback_on(uda);
	if (uda->capture_stream_opened) snd_uda1380_capture_on(uda);
	snd_uda1380_unlock(uda);
	return 0;
}

static void snd_uda1380_init_uda(struct snd_uda1380 *uda)
{
	init_MUTEX(&uda->sem);
	uda->i2c_client.driver = &snd_uda1380_i2c_driver;
}

int snd_uda1380_activate(struct snd_uda1380 *uda)
{
	int ret = 0;

	snd_uda1380_init_uda(uda);
	snd_uda1380_lock(uda);
	snd_uda1380_power_on(uda);
	if ((ret = snd_uda1380_i2c_attach(uda)) < 0)
		goto failed_i2c_attach;
	snd_uda1380_init_regs(uda);
	if ((ret = snd_uda1380_hp_detected_init(uda)) < 0)
		goto failed_hp_detected_init;
	snd_uda1380_unlock(uda);
	return 0;

 failed_hp_detected_init:
	snd_uda1380_i2c_detach(uda);
 failed_i2c_attach:
	snd_uda1380_power_off(uda);
	snd_uda1380_unlock(uda);
	return ret;
}

void snd_uda1380_deactivate(struct snd_uda1380 *uda)
{
	snd_uda1380_lock(uda);
	snd_uda1380_hp_detected_free(uda);
	snd_uda1380_i2c_detach(uda);
	snd_uda1380_power_off(uda);
	snd_uda1380_unlock(uda);
}

int snd_uda1380_add_mixer_controls(struct snd_uda1380 *uda, struct snd_card *card)
{
	snd_uda1380_lock(uda);
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_playback_volume, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_playback_switch, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_deemphasis, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_tone_ctl_strength, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_tone_ctl_treble, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_tone_ctl_bass, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_capture_volume, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_capture_switch, uda));
	if (uda->mic_connected)
		snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_mic_gain, uda));
	if (uda->line_in_connected)
		snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_line_in_gain, uda));
	if (uda->mic_connected && uda->line_in_connected)
		snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_capture_source, uda));
	snd_ctl_add(card, snd_ctl_new1(&snd_uda1380_actl_agc, uda));
	snd_uda1380_unlock(uda);
	return 0;
}

/* end {{ Codec Control }} */


/* begin {{ Module }} */

static int __init snd_uda1380_module_on_load(void)
{
	snd_uda1380_i2c_init();
	return 0;
}

static void __exit snd_uda1380_module_on_unload(void)
{
	snd_uda1380_i2c_free();
}

module_init(snd_uda1380_module_on_load);
module_exit(snd_uda1380_module_on_unload);

EXPORT_SYMBOL(snd_uda1380_activate);
EXPORT_SYMBOL(snd_uda1380_deactivate);
EXPORT_SYMBOL(snd_uda1380_add_mixer_controls);
EXPORT_SYMBOL(snd_uda1380_open_stream);
EXPORT_SYMBOL(snd_uda1380_close_stream);
EXPORT_SYMBOL(snd_uda1380_suspend);
EXPORT_SYMBOL(snd_uda1380_resume);
EXPORT_SYMBOL(snd_uda1380_hp_connected);
EXPORT_SYMBOL(snd_uda1380_hp_detected);

MODULE_AUTHOR("Giorgio Padrin");
MODULE_DESCRIPTION("Audio support for codec Philips UDA1380");
MODULE_LICENSE("GPL");

/* end {{ Module }} */
