Merge "ASoC: wcd9xxx: use hwdep node for codec calibration"
diff --git a/include/sound/Kbuild b/include/sound/Kbuild
index aeccfed..bb8b17b 100644
--- a/include/sound/Kbuild
+++ b/include/sound/Kbuild
@@ -14,3 +14,4 @@
 header-y += lsm_params.h
 header-y += voice_params.h
 header-y += voice_svc.h
+header-y += msmcal-hwdep.h
diff --git a/include/sound/asound.h b/include/sound/asound.h
index 7bf01b6..244bb30 100644
--- a/include/sound/asound.h
+++ b/include/sound/asound.h
@@ -95,9 +95,10 @@
 	SNDRV_HWDEP_IFACE_SB_RC,	/* SB Extigy/Audigy2NX remote control */
 	SNDRV_HWDEP_IFACE_HDA,		/* HD-audio */
 	SNDRV_HWDEP_IFACE_USB_STREAM,	/* direct access to usb stream */
+        SNDRV_HWDEP_IFACE_AUDIO_CODEC,  /* codec Audio Control */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_USB_STREAM
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_AUDIO_CODEC
 };
 
 struct snd_hwdep_info {
diff --git a/include/sound/msmcal-hwdep.h b/include/sound/msmcal-hwdep.h
new file mode 100644
index 0000000..324b497
--- /dev/null
+++ b/include/sound/msmcal-hwdep.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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.
+ */
+#ifndef _CALIB_HWDEP_H
+#define _CALIB_HWDEP_H
+
+#define WCD9XXX_CODEC_HWDEP_NODE    1000
+enum wcd_cal_type {
+	WCD9XXX_MIN_CAL,
+	WCD9XXX_ANC_CAL = WCD9XXX_MIN_CAL,
+	WCD9XXX_MAD_CAL,
+	WCD9XXX_MBHC_CAL,
+	WCD9XXX_MAX_CAL,
+};
+
+struct wcdcal_ioctl_buffer {
+	__u32 size;
+	__u8 __user *buffer;
+	enum wcd_cal_type cal_type;
+};
+
+#define SNDRV_CTL_IOCTL_HWDEP_CAL_TYPE \
+	_IOW('U', 0x1, struct wcdcal_ioctl_buffer)
+
+#endif /*_CALIB_HWDEP_H*/
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index a3ed23d..b42599e 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -51,8 +51,8 @@
 snd-soc-wcd9304-objs := wcd9304.o wcd9304-tables.o
 snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o
 snd-soc-cs8427-objs := cs8427.o
-snd-soc-wcd9320-objs := wcd9xxx-resmgr.o wcd9320.o wcd9320-tables.o wcd9xxx-mbhc.o wcd9xxx-common.o
-snd-soc-wcd9306-objs := wcd9306.o wcd9306-tables.o wcd9xxx-common.o
+snd-soc-wcd9320-objs := wcd9xxx-resmgr.o wcd9320.o wcd9320-tables.o wcd9xxx-mbhc.o wcd9xxx-common.o wcdcal-hwdep.o
+snd-soc-wcd9306-objs := wcd9306.o wcd9306-tables.o wcd9xxx-common.o wcdcal-hwdep.o
 snd-soc-msm8x10-wcd-objs := msm8x10-wcd.o msm8x10-wcd-tables.o wcd9xxx-common.o
 snd-soc-wl1273-objs := wl1273.o
 snd-soc-wm1250-ev1-objs := wm1250-ev1.o
diff --git a/sound/soc/codecs/wcdcal-hwdep.c b/sound/soc/codecs/wcdcal-hwdep.c
new file mode 100644
index 0000000..1132a3c
--- /dev/null
+++ b/sound/soc/codecs/wcdcal-hwdep.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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.
+ *
+ */
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/ioctl.h>
+#include <linux/bitops.h>
+#include <sound/hwdep.h>
+#include <sound/msmcal-hwdep.h>
+#include <sound/soc.h>
+#include "wcdcal-hwdep.h"
+
+const int cal_size_info[WCD9XXX_MAX_CAL] = {
+	[WCD9XXX_ANC_CAL] = 4096,
+	[WCD9XXX_MBHC_CAL] = 4096,
+	[WCD9XXX_MAD_CAL] = 4096,
+};
+
+const char *cal_name_info[WCD9XXX_MAX_CAL] = {
+	[WCD9XXX_ANC_CAL] = "anc",
+	[WCD9XXX_MBHC_CAL] = "mbhc",
+	[WCD9XXX_MAD_CAL] = "mad",
+};
+
+struct firmware_cal *wcdcal_get_fw_cal(struct fw_info *fw_data,
+					enum wcd_cal_type type)
+{
+	if (!fw_data) {
+		pr_err("%s: fw_data is NULL\n", __func__);
+		return NULL;
+	}
+	if (type >= WCD9XXX_MAX_CAL ||
+		type < WCD9XXX_MIN_CAL) {
+		pr_err("%s: wrong cal type sent %d\n", __func__, type);
+		return NULL;
+	}
+	mutex_lock(&fw_data->lock);
+	if (!test_bit(WCDCAL_RECIEVED,
+		&fw_data->wcdcal_state[type])) {
+		pr_err("%s: cal not sent by userspace %d\n",
+			__func__, type);
+		mutex_unlock(&fw_data->lock);
+		return NULL;
+	}
+	mutex_unlock(&fw_data->lock);
+	return fw_data->fw[type];
+}
+EXPORT_SYMBOL(wcdcal_get_fw_cal);
+
+static int wcdcal_hwdep_ioctl_shared(struct snd_hwdep *hw,
+			struct wcdcal_ioctl_buffer fw_user)
+{
+	struct fw_info *fw_data = hw->private_data;
+	struct firmware_cal **fw = fw_data->fw;
+	void *data;
+
+	if (!test_bit(fw_user.cal_type, fw_data->cal_bit)) {
+		pr_err("%s: codec didn't set this %d!!\n",
+				__func__, fw_user.cal_type);
+		return -EFAULT;
+	}
+	if (fw_user.cal_type >= WCD9XXX_MAX_CAL ||
+		fw_user.cal_type < WCD9XXX_MIN_CAL) {
+		pr_err("%s: wrong cal type sent %d\n",
+				__func__, fw_user.cal_type);
+		return -EFAULT;
+	}
+	if (fw_user.size > cal_size_info[fw_user.cal_type] ||
+		fw_user.size <= 0) {
+		pr_err("%s: incorrect firmware size %d for %s\n",
+			__func__, fw_user.size,
+			cal_name_info[fw_user.cal_type]);
+		return -EFAULT;
+	}
+	data = fw[fw_user.cal_type]->data;
+	memcpy(data, fw_user.buffer, fw_user.size);
+	fw[fw_user.cal_type]->size = fw_user.size;
+	mutex_lock(&fw_data->lock);
+	set_bit(WCDCAL_RECIEVED, &fw_data->wcdcal_state[fw_user.cal_type]);
+	mutex_unlock(&fw_data->lock);
+	return 0;
+}
+
+#ifdef CONFIG_COMPAT
+struct wcdcal_ioctl_buffer32 {
+	u32 size;
+	compat_uptr_t buffer;
+	enum wcd_cal_type cal_type;
+};
+
+enum {
+	SNDRV_CTL_IOCTL_HWDEP_CAL_TYPE32 =
+		_IOW('U', 0x1, struct wcdcal_ioctl_buffer32),
+};
+
+static int wcdcal_hwdep_ioctl_compat(struct snd_hwdep *hw, struct file *file,
+		unsigned int cmd, unsigned long arg)
+{
+	struct wcdcal_ioctl_buffer __user *argp = (void __user *)arg;
+	struct wcdcal_ioctl_buffer32 fw_user32;
+	struct wcdcal_ioctl_buffer fw_user_compat;
+
+	if (cmd != SNDRV_CTL_IOCTL_HWDEP_CAL_TYPE32) {
+		pr_err("%s: wrong ioctl command sent %u!\n", __func__, cmd);
+		return -ENOIOCTLCMD;
+	}
+	if (copy_from_user(&fw_user32, argp, sizeof(fw_user32))) {
+		pr_err("%s: failed to copy\n", __func__);
+		return -EFAULT;
+	}
+	fw_user_compat.size = fw_user32.size;
+	fw_user_compat.buffer = compat_ptr(fw_user32.buffer);
+	fw_user_compat.cal_type = fw_user32.cal_type;
+	return wcdcal_hwdep_ioctl_shared(hw, fw_user_compat);
+}
+#else
+#define wcdcal_hwdep_ioctl_compat NULL
+#endif
+
+static int wcdcal_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
+		unsigned int cmd, unsigned long arg)
+{
+	struct wcdcal_ioctl_buffer __user *argp = (void __user *)arg;
+	struct wcdcal_ioctl_buffer fw_user;
+
+	if (cmd != SNDRV_CTL_IOCTL_HWDEP_CAL_TYPE) {
+		pr_err("%s: wrong ioctl command sent %d!\n", __func__, cmd);
+		return -ENOIOCTLCMD;
+	}
+	if (copy_from_user(&fw_user, argp, sizeof(fw_user))) {
+		pr_err("%s: failed to copy\n", __func__);
+		return -EFAULT;
+	}
+	return wcdcal_hwdep_ioctl_shared(hw, fw_user);
+}
+
+static int wcdcal_hwdep_release(struct snd_hwdep *hw, struct file *file)
+{
+	struct fw_info *fw_data = hw->private_data;
+	mutex_lock(&fw_data->lock);
+	/* clear all the calibrations */
+	memset(fw_data->wcdcal_state, 0,
+		sizeof(fw_data->wcdcal_state));
+	mutex_unlock(&fw_data->lock);
+	return 0;
+}
+
+int wcd_cal_create_hwdep(void *data, int node, struct snd_soc_codec *codec)
+{
+	char hwname[40];
+	struct snd_hwdep *hwdep;
+	struct firmware_cal **fw;
+	struct fw_info *fw_data = data;
+	int err, cal_bit;
+
+	if (!fw_data || !codec) {
+		pr_err("%s: wrong arguments passed\n", __func__);
+		return -EINVAL;
+	}
+
+	fw = fw_data->fw;
+	snprintf(hwname, strlen("Codec %s"), "Codec %s", codec->name);
+	err = snd_hwdep_new(codec->card->snd_card, hwname, node, &hwdep);
+	if (err < 0) {
+		dev_err(codec->dev, "%s: new hwdep failed %d\n",
+				__func__, err);
+		return err;
+	}
+	snprintf(hwdep->name, strlen("Codec %s"), "Codec %s", codec->name);
+	hwdep->iface = SNDRV_HWDEP_IFACE_AUDIO_CODEC;
+	hwdep->private_data = fw_data;
+	hwdep->ops.ioctl_compat = wcdcal_hwdep_ioctl_compat;
+	hwdep->ops.ioctl = wcdcal_hwdep_ioctl;
+	hwdep->ops.release = wcdcal_hwdep_release;
+	mutex_init(&fw_data->lock);
+
+	for_each_set_bit(cal_bit, fw_data->cal_bit, WCD9XXX_MAX_CAL) {
+		set_bit(WCDCAL_UNINITIALISED,
+				&fw_data->wcdcal_state[cal_bit]);
+		fw[cal_bit] = kzalloc(sizeof *(fw[cal_bit]), GFP_KERNEL);
+		if (!fw[cal_bit]) {
+			dev_err(codec->dev, "%s: no memory for %s cal\n",
+				__func__, cal_name_info[cal_bit]);
+			goto end;
+		}
+	}
+	for_each_set_bit(cal_bit, fw_data->cal_bit, WCD9XXX_MAX_CAL) {
+		fw[cal_bit]->data = kzalloc(cal_size_info[cal_bit],
+						GFP_KERNEL);
+		if (!fw[cal_bit]->data) {
+			dev_err(codec->dev, "%s: no memory for %s cal data\n",
+				__func__, cal_name_info[cal_bit]);
+			goto exit;
+		}
+		set_bit(WCDCAL_INITIALISED,
+			&fw_data->wcdcal_state[cal_bit]);
+	}
+	return 0;
+exit:
+	for_each_set_bit(cal_bit, fw_data->cal_bit, WCD9XXX_MAX_CAL) {
+		kfree(fw[cal_bit]->data);
+		fw[cal_bit]->data = NULL;
+	}
+end:
+	for_each_set_bit(cal_bit, fw_data->cal_bit, WCD9XXX_MAX_CAL) {
+		kfree(fw[cal_bit]);
+		fw[cal_bit] = NULL;
+	}
+	return -ENOMEM;
+}
+EXPORT_SYMBOL(wcd_cal_create_hwdep);
diff --git a/sound/soc/codecs/wcdcal-hwdep.h b/sound/soc/codecs/wcdcal-hwdep.h
new file mode 100644
index 0000000..632e2f1
--- /dev/null
+++ b/sound/soc/codecs/wcdcal-hwdep.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program 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.
+ */
+#ifndef __WCD9XXX_HWDEP_H__
+#define __WCD9XXX_HWDEP_H__
+#include <sound/msmcal-hwdep.h>
+
+enum wcd_cal_states {
+	WCDCAL_UNINITIALISED,
+	WCDCAL_INITIALISED,
+	WCDCAL_RECIEVED
+};
+
+struct fw_info {
+	struct firmware_cal *fw[WCD9XXX_MAX_CAL];
+	DECLARE_BITMAP(cal_bit, WCD9XXX_MAX_CAL);
+	/* for calibration tracking */
+	unsigned long wcdcal_state[WCD9XXX_MAX_CAL];
+	struct mutex lock;
+};
+
+struct firmware_cal {
+	u8 *data;
+	size_t size;
+};
+
+struct snd_soc_codec;
+int wcd_cal_create_hwdep(void *fw, int node, struct snd_soc_codec *codec);
+struct firmware_cal *wcdcal_get_fw_cal(struct fw_info *fw_data,
+					enum wcd_cal_type type);
+#endif /* __WCD9XXX_HWDEP_H__ */
diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig
index 86dd840..fbcd953 100644
--- a/sound/soc/msm/Kconfig
+++ b/sound/soc/msm/Kconfig
@@ -183,6 +183,7 @@
 	select SND_DYNAMIC_MINORS
 	select AUDIO_OCMEM
 	select DOLBY_DAP
+	select SND_HWDEP
 	help
 	 To add support for SoC audio on MSM8974.
 	 This will enable sound soc drivers which