Merge "ASoc : use hwdep node to get calibration"

This commit is contained in:
Linux Build Service Account 2015-02-08 23:53:14 -08:00 committed by Gerrit - the friendly Code Review server
commit 16942c11bf
9 changed files with 621 additions and 96 deletions

View File

@ -11,3 +11,4 @@ header-y += compress_offload.h
header-y += tlv.h
header-y += compress_params.h
header-y += compress_offload.h
header-y += msmcal-hwdep.h

View File

@ -95,9 +95,10 @@ enum {
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 {

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2015, 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_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*/

View File

@ -49,7 +49,7 @@ snd-soc-twl6040-objs := twl6040.o
snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
snd-soc-wcd9304-objs := wcd9304.o wcd9304-tables.o
snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o
snd-soc-wcd9310-objs := wcd9310.o wcd9310-tables.o wcdcal-hwdep.o
snd-soc-cs8427-objs := cs8427.o
snd-soc-wcd9320-objs := wcd9320.o wcd9320-tables.o
snd-soc-wl1273-objs := wl1273.o

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2012-2013, The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2013,2015 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
@ -38,6 +38,7 @@
#include <linux/wakelock.h>
#include <linux/suspend.h>
#include "wcd9304.h"
#include "wcdcal-hwdep.h"
#define WCD9304_RATES (SNDRV_PCM_RATE_8000|SNDRV_PCM_RATE_16000|\
SNDRV_PCM_RATE_32000|SNDRV_PCM_RATE_48000)
@ -341,6 +342,10 @@ struct sitar_priv {
bool gpio_irq_resend;
struct wake_lock irq_resend_wlock;
/* cal info for codec */
struct fw_info *fw_data;
struct firmware_cal *mbhc_cal;
};
#ifdef CONFIG_DEBUG_FS
@ -1567,7 +1572,7 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
const char *filename;
const struct firmware *fw;
int i;
int ret;
int ret = 0;
int num_anc_slots;
struct anc_header *anc_head;
struct sitar_priv *sitar = snd_soc_codec_get_drvdata(codec);
@ -1576,6 +1581,9 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
u32 *anc_ptr;
u16 reg;
u8 mask, val, old_val;
size_t cal_size;
const void *data;
struct firmware_cal *hwdep_cal = NULL;
pr_debug("%s %d\n", __func__, event);
switch (event) {
@ -1586,38 +1594,54 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
* WCD9310 and WCD9304
*/
filename = "wcd9310/wcd9310_anc.bin";
hwdep_cal = wcdcal_get_fw_cal(sitar ->fw_data, WCD9XXX_ANC_CAL);
if (hwdep_cal) {
data = hwdep_cal->data;
cal_size = hwdep_cal->size;
dev_dbg(codec->dev, "%s: using hwdep calibration\n",
__func__);
} else {
ret = request_firmware(&fw, filename, codec->dev);
if (ret != 0) {
dev_err(codec->dev, "Failed to acquire ANC data: %d\n",
ret);
return -ENODEV;
}
if (!fw) {
dev_err(codec->dev, "failed to get anc fw");
return -ENODEV;
}
data = fw->data;
cal_size = fw->size;
dev_dbg(codec->dev, "%s: using request_firmware calibration\n",
__func__);
ret = request_firmware(&fw, filename, codec->dev);
if (ret != 0) {
dev_err(codec->dev, "Failed to acquire ANC data: %d\n",
ret);
return -ENODEV;
}
if (fw->size < sizeof(struct anc_header)) {
if (cal_size < sizeof(struct anc_header)) {
dev_err(codec->dev, "Not enough data\n");
release_firmware(fw);
return -ENOMEM;
ret = -ENOMEM;
goto err;
}
/* First number is the number of register writes */
anc_head = (struct anc_header *)(fw->data);
anc_ptr = (u32 *)((u32)fw->data + sizeof(struct anc_header));
anc_size_remaining = fw->size - sizeof(struct anc_header);
anc_head = (struct anc_header *)(data);
anc_ptr = (u32 *)((u32)data + sizeof(struct anc_header));
anc_size_remaining = cal_size - sizeof(struct anc_header);
num_anc_slots = anc_head->num_anc_slots;
if (sitar->anc_slot >= num_anc_slots) {
dev_err(codec->dev, "Invalid ANC slot selected\n");
release_firmware(fw);
return -EINVAL;
ret = -EINVAL;
goto err;
}
for (i = 0; i < num_anc_slots; i++) {
if (anc_size_remaining < SITAR_PACKED_REG_SIZE) {
dev_err(codec->dev, "Invalid register format\n");
release_firmware(fw);
return -EINVAL;
ret = -EINVAL;
goto err;
}
anc_writes_size = (u32)(*anc_ptr);
anc_size_remaining -= sizeof(u32);
@ -1626,8 +1650,8 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
if (anc_writes_size * SITAR_PACKED_REG_SIZE
> anc_size_remaining) {
dev_err(codec->dev, "Invalid register format\n");
release_firmware(fw);
return -ENOMEM;
ret = -EINVAL;
goto err;
}
if (sitar->anc_slot == i)
@ -1639,8 +1663,8 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
}
if (i == num_anc_slots) {
dev_err(codec->dev, "Selected ANC slot not present\n");
release_firmware(fw);
return -ENOMEM;
ret = -EINVAL;
goto err;
}
for (i = 0; i < anc_writes_size; i++) {
@ -1651,7 +1675,8 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
(val & mask));
}
release_firmware(fw);
if (!hwdep_cal)
release_firmware(fw);
/* For Sitar, it is required to enable both Feed-forward
* and Feed back clocks to enable ANC
@ -1666,6 +1691,11 @@ static int sitar_codec_enable_anc(struct snd_soc_dapm_widget *w,
break;
}
return 0;
err:
if (!hwdep_cal)
release_firmware(fw);
return ret;
}
@ -4363,39 +4393,63 @@ void sitar_mbhc_init(struct snd_soc_codec *codec)
}
static bool sitar_mbhc_fw_validate(const struct firmware *fw)
static bool sitar_mbhc_fw_validate(const void *data, size_t size)
{
u32 cfg_offset;
struct sitar_mbhc_imped_detect_cfg *imped_cfg;
struct sitar_mbhc_btn_detect_cfg *btn_cfg;
struct firmware_cal fw;
if (fw->size < SITAR_MBHC_CAL_MIN_SIZE)
fw.data = (void *)data;
fw.size = size;
if (fw.size < SITAR_MBHC_CAL_MIN_SIZE)
return false;
/* previous check guarantees that there is enough fw data up
* to num_btn
*/
btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(fw->data);
cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data);
if (fw->size < (cfg_offset + SITAR_MBHC_CAL_BTN_SZ(btn_cfg)))
btn_cfg = SITAR_MBHC_CAL_BTN_DET_PTR(fw.data);
cfg_offset = (u32) ((void *) btn_cfg - (void *) fw.data);
if (fw.size < (cfg_offset + SITAR_MBHC_CAL_BTN_SZ(btn_cfg)))
return false;
/* previous check guarantees that there is enough fw data up
* to start of impedance detection configuration
*/
imped_cfg = SITAR_MBHC_CAL_IMPED_DET_PTR(fw->data);
cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data);
imped_cfg = SITAR_MBHC_CAL_IMPED_DET_PTR(fw.data);
cfg_offset = (u32) ((void *) imped_cfg - (void *) fw.data);
if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_MIN_SZ))
if (fw.size < (cfg_offset + SITAR_MBHC_CAL_IMPED_MIN_SZ))
return false;
if (fw->size < (cfg_offset + SITAR_MBHC_CAL_IMPED_SZ(imped_cfg)))
if (fw.size < (cfg_offset + SITAR_MBHC_CAL_IMPED_SZ(imped_cfg)))
return false;
return true;
}
static
struct firmware_cal *get_hwdep_fw_cal(struct snd_soc_codec *codec,
enum wcd_cal_type type)
{
struct sitar_priv *sitar;
struct firmware_cal *hwdep_cal;
if (!codec) {
pr_err("%s: NULL codec pointer\n", __func__);
return NULL;
}
sitar = snd_soc_codec_get_drvdata(codec);
hwdep_cal = wcdcal_get_fw_cal(sitar->fw_data, type);
if (!hwdep_cal) {
dev_err(codec->dev, "%s: cal not sent by %d\n",
__func__, type);
return NULL;
}
return hwdep_cal;
}
static void sitar_turn_onoff_override(struct snd_soc_codec *codec, bool on)
{
snd_soc_update_bits(codec, SITAR_A_CDC_MBHC_B1_CTL, 0x04, on << 2);
@ -5053,6 +5107,8 @@ static void mbhc_fw_read(struct work_struct *work)
struct snd_soc_codec *codec;
const struct firmware *fw;
int ret = -1, retry = 0;
struct firmware_cal *fw_data = NULL;
bool use_default_cal = false;
dwork = to_delayed_work(work);
sitar = container_of(dwork, struct sitar_priv,
@ -5061,12 +5117,19 @@ static void mbhc_fw_read(struct work_struct *work)
while (retry < MBHC_FW_READ_ATTEMPTS) {
retry++;
pr_info("%s:Attempt %d to request MBHC firmware\n",
__func__, retry);
ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin",
pr_err("%s:Attempt %d to request MBHC firmware\n",
__func__, retry);
fw_data = get_hwdep_fw_cal(codec,
WCD9XXX_MBHC_CAL);
if (!fw_data)
ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin",
codec->dev);
if (ret != 0) {
/*
* if request_firmware and hwdep cal both fail then
* retry for few times before bailing out
*/
if ((ret != 0) && !fw_data) {
usleep_range(MBHC_FW_READ_TIMEOUT,
MBHC_FW_READ_TIMEOUT);
} else {
@ -5075,18 +5138,43 @@ static void mbhc_fw_read(struct work_struct *work)
}
}
if (ret != 0) {
if (!fw_data)
pr_debug("%s: using request_firmware\n", __func__);
else
pr_debug("%s: using hwdep cal\n", __func__);
if (ret != 0 && !fw_data) {
pr_err("%s: Cannot load MBHC firmware use default cal\n",
__func__);
} else if (sitar_mbhc_fw_validate(fw) == false) {
pr_err("%s: Invalid MBHC cal data size use default cal\n",
__func__);
release_firmware(fw);
} else {
sitar->calibration = (void *)fw->data;
sitar->mbhc_fw = fw;
use_default_cal = true;
}
if (!use_default_cal) {
const void *data;
size_t size;
if (fw_data) {
data = fw_data->data;
size = fw_data->size;
} else {
data = fw->data;
size = fw->size;
}
if (sitar_mbhc_fw_validate(data, size) == false) {
pr_err("%s: Invalid MBHC cal data size use default cal\n",
__func__);
if (!fw_data)
release_firmware(fw);
} else {
if (fw_data) {
sitar->mbhc_cfg.calibration =
(void *)fw_data->data;
sitar->mbhc_cal = fw_data;
} else {
sitar->mbhc_cfg.calibration =
(void *)fw->data;
sitar->mbhc_fw = fw;
}
}
}
sitar_mbhc_init_and_calibrate(codec);
}
@ -5129,13 +5217,21 @@ int sitar_hs_detect(struct snd_soc_codec *codec,
INIT_WORK(&sitar->hs_correct_plug_work,
sitar_hs_correct_gpio_plug);
if (!sitar->mbhc_cfg.read_fw_bin) {
if ((!sitar->mbhc_cfg.read_fw_bin) ||
(sitar->mbhc_cfg.read_fw_bin && sitar->mbhc_fw) ||
(sitar->mbhc_cfg.read_fw_bin && sitar->mbhc_cal)) {
rc = sitar_mbhc_init_and_calibrate(codec);
} else {
schedule_delayed_work(&sitar->mbhc_firmware_dwork,
usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
if (!sitar->mbhc_fw || sitar->mbhc_cal)
schedule_delayed_work(&sitar ->mbhc_firmware_dwork,
usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
else
pr_err("%s: Skipping to read mbhc fw, 0x%p 0x%p\n",
__func__, sitar ->mbhc_fw, sitar->mbhc_cal);
}
return rc;
}
EXPORT_SYMBOL_GPL(sitar_hs_detect);
@ -5936,7 +6032,8 @@ static int sitar_codec_probe(struct snd_soc_codec *codec)
sitar = kzalloc(sizeof(struct sitar_priv), GFP_KERNEL);
if (!sitar) {
dev_err(codec->dev, "Failed to allocate private data\n");
return -ENOMEM;
goto err_nomem_slimch;
}
for (i = 0; i < NUM_DECIMATORS; i++) {
@ -5989,6 +6086,20 @@ static int sitar_codec_probe(struct snd_soc_codec *codec)
goto err_pdata;
}
sitar->fw_data = kzalloc(sizeof(*(sitar ->fw_data)), GFP_KERNEL);
if (!sitar ->fw_data) {
dev_err(codec->dev, "Failed to allocate fw_data\n");
goto err_nomem_slimch;
}
set_bit(WCD9XXX_ANC_CAL, sitar->fw_data->cal_bit);
set_bit(WCD9XXX_MBHC_CAL, sitar->fw_data->cal_bit);
ret = wcd_cal_create_hwdep(sitar->fw_data,
WCD9XXX_CODEC_HWDEP_NODE, codec);
if (ret < 0) {
dev_err(codec->dev, "%s hwdep failed %d\n", __func__, ret);
goto err_hwdep;
}
snd_soc_add_codec_controls(codec, sitar_snd_controls,
ARRAY_SIZE(sitar_snd_controls));
snd_soc_dapm_new_controls(dapm, sitar_dapm_widgets,
@ -6125,7 +6236,10 @@ err_potential_irq:
err_remove_irq:
wcd9xxx_free_irq(codec->control_data,
SITAR_IRQ_MBHC_INSERTION, sitar);
err_hwdep:
err_insert_irq:
kfree(sitar->fw_data);
err_nomem_slimch:
err_pdata:
mutex_destroy(&sitar->codec_resource_lock);
kfree(sitar);
@ -6147,12 +6261,13 @@ static int sitar_codec_remove(struct snd_soc_codec *codec)
sitar_codec_disable_clock_block(codec);
SITAR_RELEASE_LOCK(sitar->codec_resource_lock);
sitar_codec_enable_bandgap(codec, SITAR_BANDGAP_OFF);
if (sitar->mbhc_fw)
if (sitar->mbhc_fw||sitar->mbhc_cal)
release_firmware(sitar->mbhc_fw);
for (i = 0; i < ARRAY_SIZE(sitar_dai); i++)
kfree(sitar->dai[i].ch_num);
mutex_destroy(&sitar->codec_resource_lock);
kfree(sitar);
kfree(sitar->fw_data);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_sitar = {

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2011-2013, The Linux Foundation. All rights reserved.
/* Copyright (c) 2011-2013,2015 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
@ -37,6 +37,7 @@
#include <linux/wakelock.h>
#include <linux/suspend.h>
#include "wcd9310.h"
#include "wcdcal-hwdep.h"
static int cfilt_adjust_ms = 10;
module_param(cfilt_adjust_ms, int, 0644);
@ -345,6 +346,7 @@ struct tabla_priv {
/* Work to perform MBHC Firmware Read */
struct delayed_work mbhc_firmware_dwork;
const struct firmware *mbhc_fw;
struct firmware_cal *mbhc_cal;
/* num of slim ports required */
struct tabla_codec_dai_data dai[NUM_CODEC_DAIS];
@ -388,6 +390,8 @@ struct tabla_priv {
struct dentry *debugfs_poke;
struct dentry *debugfs_mbhc;
#endif
/* cal info for codec */
struct fw_info *fw_data;
};
static const u32 comp_shift[] = {
@ -3087,7 +3091,7 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
const char *filename;
const struct firmware *fw;
int i;
int ret;
int ret =0;
int num_anc_slots;
struct anc_header *anc_head;
struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
@ -3096,6 +3100,9 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
u32 *anc_ptr;
u16 reg;
u8 mask, val, old_val;
size_t cal_size;
const void *data;
struct firmware_cal *hwdep_cal = NULL;
pr_debug("%s: DAPM Event %d ANC func is %d\n",
__func__, event, tabla->anc_func);
@ -3107,24 +3114,40 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
case SND_SOC_DAPM_PRE_PMU:
filename = "wcd9310/wcd9310_anc.bin";
hwdep_cal = wcdcal_get_fw_cal(tabla->fw_data, WCD9XXX_ANC_CAL);
if (hwdep_cal) {
data = hwdep_cal->data;
cal_size = hwdep_cal->size;
dev_dbg(codec->dev, "%s: using hwdep calibration\n",
__func__);
} else {
ret = request_firmware(&fw, filename, codec->dev);
if (ret != 0) {
dev_err(codec->dev, "Failed to acquire ANC data: %d\n",
ret);
return -ENODEV;
}
if (!fw) {
dev_err(codec->dev, "failed to get anc fw");
return -ENODEV;
}
data = fw->data;
cal_size = fw->size;
dev_dbg(codec->dev, "%s: using request_firmware calibration\n",
__func__);
ret = request_firmware(&fw, filename, codec->dev);
if (ret != 0) {
dev_err(codec->dev, "Failed to acquire ANC data: %d\n",
ret);
return -ENODEV;
}
if (fw->size < sizeof(struct anc_header)) {
if (cal_size < sizeof(struct anc_header)) {
dev_err(codec->dev, "Not enough data\n");
release_firmware(fw);
return -ENOMEM;
ret = -ENOMEM;
goto err;
}
/* First number is the number of register writes */
anc_head = (struct anc_header *)(fw->data);
anc_ptr = (u32 *)((u32)fw->data + sizeof(struct anc_header));
anc_size_remaining = fw->size - sizeof(struct anc_header);
anc_head = (struct anc_header *)(data);
anc_ptr = (u32 *)((u32)data + sizeof(struct anc_header));
anc_size_remaining = cal_size - sizeof(struct anc_header);
num_anc_slots = anc_head->num_anc_slots;
if (tabla->anc_slot >= num_anc_slots) {
@ -3137,8 +3160,8 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
if (anc_size_remaining < TABLA_PACKED_REG_SIZE) {
dev_err(codec->dev, "Invalid register format\n");
release_firmware(fw);
return -EINVAL;
ret = -EINVAL;
goto err;
}
anc_writes_size = (u32)(*anc_ptr);
anc_size_remaining -= sizeof(u32);
@ -3147,8 +3170,8 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
if (anc_writes_size * TABLA_PACKED_REG_SIZE
> anc_size_remaining) {
dev_err(codec->dev, "Invalid register format\n");
release_firmware(fw);
return -ENOMEM;
ret = -EINVAL;
goto err;
}
if (tabla->anc_slot == i)
@ -3160,8 +3183,9 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
}
if (i == num_anc_slots) {
dev_err(codec->dev, "Selected ANC slot not present\n");
release_firmware(fw);
return -ENOMEM;
ret = -EINVAL;
goto err;
}
for (i = 0; i < anc_writes_size; i++) {
@ -3171,7 +3195,9 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
snd_soc_write(codec, reg, (old_val & ~mask) |
(val & mask));
}
release_firmware(fw);
if (!hwdep_cal)
release_firmware(fw);
break;
case SND_SOC_DAPM_PRE_PMD:
snd_soc_update_bits(codec, TABLA_A_CDC_ANC1_CTL, 0x01, 0x00);
@ -3183,6 +3209,11 @@ static int tabla_codec_enable_anc(struct snd_soc_dapm_widget *w,
break;
}
return 0;
err:
if (!hwdep_cal)
release_firmware(fw);
return ret;
}
static int tabla_hph_pa_event(struct snd_soc_dapm_widget *w,
@ -6330,33 +6361,37 @@ void tabla_mbhc_init(struct snd_soc_codec *codec)
tabla->mbhc_cfg.micbias);
}
static bool tabla_mbhc_fw_validate(const struct firmware *fw)
static bool tabla_mbhc_fw_validate(const void *data, size_t size)
{
u32 cfg_offset;
struct tabla_mbhc_imped_detect_cfg *imped_cfg;
struct tabla_mbhc_btn_detect_cfg *btn_cfg;
struct firmware_cal fw;
if (fw->size < TABLA_MBHC_CAL_MIN_SIZE)
fw.data = (void *)data;
fw.size = size;
if (fw.size < TABLA_MBHC_CAL_MIN_SIZE)
return false;
/* previous check guarantees that there is enough fw data up
* to num_btn
*/
btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(fw->data);
cfg_offset = (u32) ((void *) btn_cfg - (void *) fw->data);
if (fw->size < (cfg_offset + TABLA_MBHC_CAL_BTN_SZ(btn_cfg)))
btn_cfg = TABLA_MBHC_CAL_BTN_DET_PTR(fw.data);
cfg_offset = (u32) ((void *) btn_cfg - (void *) fw.data);
if (fw.size < (cfg_offset + TABLA_MBHC_CAL_BTN_SZ(btn_cfg)))
return false;
/* previous check guarantees that there is enough fw data up
* to start of impedance detection configuration
*/
imped_cfg = TABLA_MBHC_CAL_IMPED_DET_PTR(fw->data);
cfg_offset = (u32) ((void *) imped_cfg - (void *) fw->data);
imped_cfg = TABLA_MBHC_CAL_IMPED_DET_PTR(fw.data);
cfg_offset = (u32) ((void *) imped_cfg - (void *) fw.data);
if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_MIN_SZ))
if (fw.size < (cfg_offset + TABLA_MBHC_CAL_IMPED_MIN_SZ))
return false;
if (fw->size < (cfg_offset + TABLA_MBHC_CAL_IMPED_SZ(imped_cfg)))
if (fw.size < (cfg_offset + TABLA_MBHC_CAL_IMPED_SZ(imped_cfg)))
return false;
return true;
@ -7884,6 +7919,26 @@ static int tabla_mbhc_init_and_calibrate(struct tabla_priv *tabla)
return ret;
}
static
struct firmware_cal *get_hwdep_fw_cal(struct snd_soc_codec *codec,
enum wcd_cal_type type)
{
struct tabla_priv *tabla;
struct firmware_cal *hwdep_cal;
if (!codec) {
pr_err("%s: NULL codec pointer\n", __func__);
return NULL;
}
tabla = snd_soc_codec_get_drvdata(codec);
hwdep_cal = wcdcal_get_fw_cal(tabla->fw_data, type);
if (!hwdep_cal) {
dev_err(codec->dev, "%s: cal not sent by %d\n",
__func__, type);
return NULL;
}
return hwdep_cal;
}
static void mbhc_fw_read(struct work_struct *work)
{
struct delayed_work *dwork;
@ -7891,6 +7946,8 @@ static void mbhc_fw_read(struct work_struct *work)
struct snd_soc_codec *codec;
const struct firmware *fw;
int ret = -1, retry = 0;
struct firmware_cal *fw_data = NULL;
bool use_default_cal = false;
dwork = to_delayed_work(work);
tabla = container_of(dwork, struct tabla_priv, mbhc_firmware_dwork);
@ -7898,12 +7955,18 @@ static void mbhc_fw_read(struct work_struct *work)
while (retry < MBHC_FW_READ_ATTEMPTS) {
retry++;
pr_info("%s:Attempt %d to request MBHC firmware\n",
__func__, retry);
ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin",
pr_err("%s:Attempt %d to request MBHC firmware\n",
__func__, retry);
fw_data = get_hwdep_fw_cal(codec,
WCD9XXX_MBHC_CAL);
if (!fw_data)
ret = request_firmware(&fw, "wcd9310/wcd9310_mbhc.bin",
codec->dev);
if (ret != 0) {
/*
* if request_firmware and hwdep cal both fail then
* retry for few times before bailing out
*/
if ((ret != 0) && !fw_data) {
usleep_range(MBHC_FW_READ_TIMEOUT,
MBHC_FW_READ_TIMEOUT);
} else {
@ -7912,16 +7975,43 @@ static void mbhc_fw_read(struct work_struct *work)
}
}
if (ret != 0) {
if (!fw_data)
pr_debug("%s: using request_firmware\n", __func__);
else
pr_debug("%s: using hwdep cal\n", __func__);
if (ret != 0 && !fw_data) {
pr_err("%s: Cannot load MBHC firmware use default cal\n",
__func__);
} else if (tabla_mbhc_fw_validate(fw) == false) {
pr_err("%s: Invalid MBHC cal data size use default cal\n",
__func__);
release_firmware(fw);
use_default_cal = true;
}
if (!use_default_cal) {
const void *data;
size_t size;
if (fw_data) {
data = fw_data->data;
size = fw_data->size;
} else {
tabla->mbhc_cfg.calibration = (void *)fw->data;
tabla->mbhc_fw = fw;
data = fw->data;
size = fw->size;
}
if (tabla_mbhc_fw_validate(data, size) == false) {
pr_err("%s: Invalid MBHC cal data size use default cal\n",
__func__);
if (!fw_data)
release_firmware(fw);
} else {
if (fw_data) {
tabla->mbhc_cfg.calibration =
(void *)fw_data->data;
tabla->mbhc_cal = fw_data;
} else {
tabla->mbhc_cfg.calibration =
(void *)fw->data;
tabla->mbhc_fw = fw;
}
}
}
(void) tabla_mbhc_init_and_calibrate(tabla);
@ -7964,12 +8054,20 @@ int tabla_hs_detect(struct snd_soc_codec *codec,
INIT_WORK(&tabla->hphrocp_work, hphrocp_off_report);
INIT_DELAYED_WORK(&tabla->mbhc_insert_dwork, mbhc_insert_work);
if (!tabla->mbhc_cfg.read_fw_bin)
if ((!tabla->mbhc_cfg.read_fw_bin)||
(tabla->mbhc_cfg.read_fw_bin && tabla->mbhc_fw) ||
(tabla->mbhc_cfg.read_fw_bin && tabla->mbhc_cal)) {
rc = tabla_mbhc_init_and_calibrate(tabla);
else
schedule_delayed_work(&tabla->mbhc_firmware_dwork,
}
else {
if (!tabla->mbhc_fw || !tabla->mbhc_cal)
schedule_delayed_work(&tabla->mbhc_firmware_dwork,
usecs_to_jiffies(MBHC_FW_READ_TIMEOUT));
else
pr_err("%s: Skipping to read mbhc fw, 0x%p 0x%p\n",
__func__, tabla->mbhc_fw, tabla->mbhc_cal);
}
return rc;
}
EXPORT_SYMBOL_GPL(tabla_hs_detect);
@ -8496,7 +8594,8 @@ static int tabla_codec_probe(struct snd_soc_codec *codec)
tabla = kzalloc(sizeof(struct tabla_priv), GFP_KERNEL);
if (!tabla) {
dev_err(codec->dev, "Failed to allocate private data\n");
return -ENOMEM;
goto err_nomem_slimch;
}
for (i = 0 ; i < NUM_DECIMATORS; i++) {
tx_hpf_work[i].tabla = tabla;
@ -8547,6 +8646,19 @@ static int tabla_codec_probe(struct snd_soc_codec *codec)
pr_err("%s: bad pdata\n", __func__);
goto err_pdata;
}
tabla->fw_data = kzalloc(sizeof(*(tabla->fw_data)), GFP_KERNEL);
if (!tabla->fw_data) {
dev_err(codec->dev, "Failed to allocate fw_data\n");
goto err_nomem_slimch;
}
set_bit(WCD9XXX_ANC_CAL, tabla->fw_data->cal_bit);
set_bit(WCD9XXX_MBHC_CAL, tabla->fw_data->cal_bit);
ret = wcd_cal_create_hwdep(tabla->fw_data,
WCD9XXX_CODEC_HWDEP_NODE, codec);
if (ret < 0) {
dev_err(codec->dev, "%s hwdep failed %d\n", __func__, ret);
goto err_hwdep;
}
// snd_soc_add_codec_controls(codec, tabla_snd_controls,
// ARRAY_SIZE(tabla_snd_controls));
@ -8728,7 +8840,10 @@ err_potential_irq:
wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_REMOVAL, tabla);
err_remove_irq:
wcd9xxx_free_irq(codec->control_data, TABLA_IRQ_MBHC_INSERTION, tabla);
err_hwdep:
err_insert_irq:
kfree(tabla->fw_data);
err_nomem_slimch:
err_pdata:
mutex_destroy(&tabla->codec_resource_lock);
kfree(tabla);
@ -8750,7 +8865,7 @@ static int tabla_codec_remove(struct snd_soc_codec *codec)
tabla_codec_disable_clock_block(codec);
TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
tabla_codec_enable_bandgap(codec, TABLA_BANDGAP_OFF);
if (tabla->mbhc_fw)
if (tabla->mbhc_fw||tabla->mbhc_cal)
release_firmware(tabla->mbhc_fw);
for (i = 0; i < ARRAY_SIZE(tabla_dai); i++)
kfree(tabla->dai[i].ch_num);
@ -8760,6 +8875,7 @@ static int tabla_codec_remove(struct snd_soc_codec *codec)
debugfs_remove(tabla->debugfs_mbhc);
#endif
kfree(tabla);
kfree(tabla->fw_data);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_tabla = {

View File

@ -0,0 +1,218 @@
/*
* Copyright (c) 2015, 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,
};
const char *cal_name_info[WCD9XXX_MAX_CAL] = {
[WCD9XXX_ANC_CAL] = "anc",
[WCD9XXX_MBHC_CAL] = "mbhc",
};
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);

View File

@ -0,0 +1,40 @@
/*
* Copyright (c) 2015, 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__ */

View File

@ -151,6 +151,7 @@ config SND_SOC_MSM8960
select SND_SOC_CS8427 if !MACH_APQ8064_MAKO
select SND_SOC_DUAL_AMIC if MACH_APQ8064_MAKO
select SND_SOC_TPA2028D if MACH_APQ8064_MAKO
select SND_HWDEP
default n
help
To add support for SoC audio on MSM8960 and APQ8064 boards