blob: 58514540080f2657f9967b65f6c443a8289855de [file] [log] [blame]
/* linux/arch/arm/mach-msm/board-spade-audio.c
*
* Copyright (C) 2010-2011 HTC Corporation.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/android_pmem.h>
#include <linux/mfd/pmic8058.h>
#include <linux/mfd/marimba.h>
#include <linux/delay.h>
#include <linux/pmic8058-othc.h>
#include <linux/spi_aic3254.h>
#include <mach/gpio.h>
#include <mach/dal.h>
#include <linux/tpa2051d3.h>
#include <mach/qdsp6v3/snddev_icodec.h>
#include <mach/qdsp6v3/snddev_ecodec.h>
#include <mach/qdsp6v3/snddev_hdmi.h>
#include <mach/qdsp6v3/apr_audio.h>
#include <mach/qdsp6v3/q6asm.h>
#include <mach/htc_acoustic_8x60.h>
#include "board-doubleshot.h"
#include "board-doubleshot-audio-data.h"
#include <mach/qdsp6v3/audio_dev_ctl.h>
static struct mutex bt_sco_lock;
static struct mutex mic_lock;
static int curr_rx_mode;
static atomic_t aic3254_ctl = ATOMIC_INIT(0);
static int emic_micbias = OTHC_MICBIAS_2;
static int bmic_micbias = OTHC_MICBIAS_1;
#define XA_DEV 0x0
#define XB_DEV 0x1
#define XC_DEV 0x2
#define BIT_SPEAKER (1 << 0)
#define BIT_HEADSET (1 << 1)
#define BIT_RECEIVER (1 << 2)
#define BIT_FM_SPK (1 << 3)
#define BIT_FM_HS (1 << 4)
void doubleshot_snddev_bmic_pamp_on(int en);
static uint32_t msm_snddev_gpio[] = {
GPIO_CFG(108, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA),
GPIO_CFG(109, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA),
GPIO_CFG(110, 0, GPIO_CFG_OUTPUT, GPIO_CFG_PULL_DOWN, GPIO_CFG_2MA),
};
void doubleshot_snddev_poweramp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
if (en) {
/* enable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 1);
break;
case XB_DEV:
case XC_DEV:
default:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
msleep(50);
set_speaker_amp(1);
break;
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode |= BIT_SPEAKER;
} else {
/* disable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 0);
break;
case XB_DEV:
case XC_DEV:
default:
set_speaker_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
break;
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode &= ~BIT_SPEAKER;
}
}
void doubleshot_snddev_hsed_pamp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
if (en) {
/* enable rx route */
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
msleep(50);
set_headset_amp(1);
if (!atomic_read(&aic3254_ctl))
curr_rx_mode |= BIT_HEADSET;
} else {
/* disable rx route */
set_headset_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
if (!atomic_read(&aic3254_ctl))
curr_rx_mode &= ~BIT_HEADSET;
}
}
void doubleshot_snddev_hs_spk_pamp_on(int en)
{
pr_info("%s %d\n", __func__, en);
if (en) {
/* enable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 1);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
break;
case XB_DEV:
case XC_DEV:
default:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
msleep(50);
set_speaker_headset_amp(1);
break;
}
if (!atomic_read(&aic3254_ctl)) {
curr_rx_mode |= BIT_SPEAKER;
curr_rx_mode |= BIT_HEADSET;
}
} else {
/* disable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
break;
case XB_DEV:
case XC_DEV:
default:
set_speaker_headset_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
break;
}
if (!atomic_read(&aic3254_ctl)) {
curr_rx_mode &= ~BIT_SPEAKER;
curr_rx_mode &= ~BIT_HEADSET;
}
}
}
void doubleshot_snddev_receiver_pamp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
if (en) {
/* enable rx route */
if (system_rev == XA_DEV) {
set_handset_amp(1);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode |= BIT_RECEIVER;
} else {
/* disable rx route */
if (system_rev == XA_DEV) {
set_handset_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode &= ~BIT_RECEIVER;
}
}
void doubleshot_snddev_bt_sco_pamp_on(int en)
{
/* to be implemented */
}
/* power on/off externnal mic bias */
void doubleshot_mic_enable(int en, int shift)
{
pr_aud_info("%s: %d, shift %d\n", __func__, en, shift);
mutex_lock(&mic_lock);
if (en)
pm8058_micbias_enable(emic_micbias, OTHC_SIGNAL_ALWAYS_ON);
else
pm8058_micbias_enable(emic_micbias, OTHC_SIGNAL_OFF);
mutex_unlock(&mic_lock);
}
void doubleshot_snddev_imic_pamp_on(int en)
{
int ret;
pr_aud_info("%s %d\n", __func__, en);
doubleshot_snddev_bmic_pamp_on(en);
if (en) {
ret = pm8058_micbias_enable(OTHC_MICBIAS_0, OTHC_SIGNAL_ALWAYS_ON);
if (ret)
pr_aud_err("%s: Enabling int mic power failed\n", __func__);
} else {
ret = pm8058_micbias_enable(OTHC_MICBIAS_0, OTHC_SIGNAL_OFF);
if (ret)
pr_aud_err("%s: Enabling int mic power failed\n", __func__);
}
}
void doubleshot_snddev_bmic_pamp_on(int en)
{
int ret;
pr_aud_info("%s %d\n", __func__, en);
if (en) {
ret = pm8058_micbias_enable(bmic_micbias, OTHC_SIGNAL_ALWAYS_ON);
if (ret)
pr_aud_err("%s: Enabling int mic power failed\n", __func__);
/* select internal mic path */
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_MIC_SEL), 0);
} else {
ret = pm8058_micbias_enable(bmic_micbias, OTHC_SIGNAL_OFF);
if (ret)
pr_aud_err("%s: Enabling int mic power failed\n", __func__);
}
}
void doubleshot_snddev_emic_pamp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
/*
external micbias should be controlled by headset driver with doubleshot_mic_enable
turn on with headset plugged in and turn off when headset unplugged.
*/
if (en) {
/* select external mic path */
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_MIC_SEL), 1);
/* TVOUT HP select low, it will be set to high when TVOUT turned on. */
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_TVOUT_HP_SEL), 0);
}
}
void doubleshot_snddev_stereo_mic_pamp_on(int en)
{
int ret;
pr_aud_info("%s %d\n", __func__, en);
if (en) {
ret = pm8058_micbias_enable(OTHC_MICBIAS_0, OTHC_SIGNAL_ALWAYS_ON);
if (ret)
pr_aud_err("%s: Enabling int mic power failed\n", __func__);
ret = pm8058_micbias_enable(OTHC_MICBIAS_1, OTHC_SIGNAL_ALWAYS_ON);
if (ret)
pr_aud_err("%s: Enabling back mic power failed\n", __func__);
/* select external mic path */
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_MIC_SEL), 0);
} else {
ret = pm8058_micbias_enable(OTHC_MICBIAS_0, OTHC_SIGNAL_OFF);
if (ret)
pr_aud_err("%s: Disabling int mic power failed\n", __func__);
ret = pm8058_micbias_enable(OTHC_MICBIAS_1, OTHC_SIGNAL_OFF);
if (ret)
pr_aud_err("%s: Disabling back mic power failed\n", __func__);
}
}
void doubleshot_snddev_fmspk_pamp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
if (en) {
/* enable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 1);
break;
case XB_DEV:
case XC_DEV:
default:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
set_speaker_amp(1);
break;
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode |= BIT_FM_SPK;
} else {
/* disable rx route */
switch (system_rev) {
case XA_DEV:
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_SPK_ENO), 0);
break;
case XB_DEV:
case XC_DEV:
default:
set_speaker_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
break;
}
if (!atomic_read(&aic3254_ctl))
curr_rx_mode &= ~BIT_FM_SPK;
}}
void doubleshot_snddev_fmhs_pamp_on(int en)
{
pr_aud_info("%s %d\n", __func__, en);
if (en) {
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 1);
set_headset_amp(1);
if (!atomic_read(&aic3254_ctl))
curr_rx_mode |= BIT_FM_HS;
} else {
set_headset_amp(0);
gpio_set_value(PM8058_GPIO_PM_TO_SYS(DOUBLESHOT_AUD_HANDSET_ENO), 0);
if (!atomic_read(&aic3254_ctl))
curr_rx_mode &= ~BIT_FM_HS;
}
}
void doubleshot_voltage_on (int en)
{
/* to be implemented */
}
int doubleshot_get_rx_vol(uint8_t hw, int network, int level)
{
int vol = 0;
/* to be implemented */
pr_aud_info("%s(%d, %d, %d) => %d\n", __func__, hw, network, level, vol);
return vol;
}
void doubleshot_rx_amp_enable(int en)
{
if (curr_rx_mode != 0) {
atomic_set(&aic3254_ctl, 1);
pr_aud_info("%s: curr_rx_mode 0x%x, en %d\n",
__func__, curr_rx_mode, en);
if (curr_rx_mode & BIT_SPEAKER)
doubleshot_snddev_poweramp_on(en);
if (curr_rx_mode & BIT_HEADSET)
doubleshot_snddev_hsed_pamp_on(en);
if (curr_rx_mode & BIT_RECEIVER)
doubleshot_snddev_receiver_pamp_on(en);
if (curr_rx_mode & BIT_FM_SPK)
doubleshot_snddev_fmspk_pamp_on(en);
if (curr_rx_mode & BIT_FM_HS)
doubleshot_snddev_fmhs_pamp_on(en);
atomic_set(&aic3254_ctl, 0);;
}
}
int doubleshot_support_aic3254(void)
{
return 1;
}
int doubleshot_support_back_mic(void)
{
return 1;
}
void doubleshot_get_acoustic_tables(struct acoustic_tables *tb)
{
switch (system_rev) {
case XA_DEV:
strcpy(tb->tpa2051, "TPA2051_CFG.csv");
strcpy(tb->aic3254, "AIC3254_REG_DualMic.csv");
break;
case XB_DEV:
strcpy(tb->tpa2051, "TPA2051_CFG_XB.csv");
strcpy(tb->aic3254, "AIC3254_REG_DualMic_XB.csv");
break;
case XC_DEV:
default:
strcpy(tb->tpa2051, "TPA2051_CFG_XC.csv");
strcpy(tb->aic3254, "AIC3254_REG_DualMic_XC.csv");
break;
}
}
int doubleshot_is_msm_i2s_slave(void)
{
/* 1 - CPU slave, 0 - CPU master */
return 1;
}
void doubleshot_spibus_enable(int en)
{
uint32_t msm_spi_gpio_on[] = {
GPIO_CFG(DOUBLESHOT_SPI_DO, 1, GPIO_CFG_OUTPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_DI, 1, GPIO_CFG_INPUT,
GPIO_CFG_PULL_DOWN, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_CS, 1, GPIO_CFG_OUTPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_CLK, 1, GPIO_CFG_OUTPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_8MA),
};
uint32_t msm_spi_gpio_off[] = {
GPIO_CFG(DOUBLESHOT_SPI_DO, 0, GPIO_CFG_OUTPUT,
GPIO_CFG_PULL_DOWN, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_DI, 1, GPIO_CFG_INPUT,
GPIO_CFG_NO_PULL, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_CS, 0, GPIO_CFG_OUTPUT,
GPIO_CFG_PULL_DOWN, GPIO_CFG_8MA),
GPIO_CFG(DOUBLESHOT_SPI_CLK, 0, GPIO_CFG_OUTPUT,
GPIO_CFG_PULL_DOWN, GPIO_CFG_8MA),
};
pr_debug("%s %d\n", __func__, en);
if (en) {
gpio_tlmm_config(msm_spi_gpio_on[0], GPIO_CFG_ENABLE);
gpio_tlmm_config(msm_spi_gpio_on[1], GPIO_CFG_ENABLE);
gpio_tlmm_config(msm_spi_gpio_on[2], GPIO_CFG_ENABLE);
gpio_tlmm_config(msm_spi_gpio_on[3], GPIO_CFG_ENABLE);
} else {
gpio_tlmm_config(msm_spi_gpio_off[0], GPIO_CFG_DISABLE);
gpio_tlmm_config(msm_spi_gpio_off[1], GPIO_CFG_DISABLE);
gpio_tlmm_config(msm_spi_gpio_off[2], GPIO_CFG_DISABLE);
gpio_tlmm_config(msm_spi_gpio_off[3], GPIO_CFG_DISABLE);
}
mdelay(1);
}
void doubleshot_reset_3254(void)
{
gpio_set_value(DOUBLESHOT_AUD_CODEC_RST, 0);
mdelay(1);
gpio_set_value(DOUBLESHOT_AUD_CODEC_RST, 1);
}
static struct q6v2audio_analog_ops ops = {
.speaker_enable = doubleshot_snddev_poweramp_on,
.headset_enable = doubleshot_snddev_hsed_pamp_on,
.handset_enable = doubleshot_snddev_receiver_pamp_on,
.headset_speaker_enable = doubleshot_snddev_hs_spk_pamp_on,
.bt_sco_enable = doubleshot_snddev_bt_sco_pamp_on,
.int_mic_enable = doubleshot_snddev_imic_pamp_on,
.back_mic_enable = doubleshot_snddev_bmic_pamp_on,
.ext_mic_enable = doubleshot_snddev_emic_pamp_on,
.stereo_mic_enable = doubleshot_snddev_stereo_mic_pamp_on,
.fm_headset_enable = doubleshot_snddev_fmhs_pamp_on,
.fm_speaker_enable = doubleshot_snddev_fmspk_pamp_on,
.voltage_on = doubleshot_voltage_on,
};
static struct q6v2audio_icodec_ops iops = {
.support_aic3254 = doubleshot_support_aic3254,
.is_msm_i2s_slave = doubleshot_is_msm_i2s_slave,
};
static struct q6v2audio_ecodec_ops eops = {
.bt_sco_enable = doubleshot_snddev_bt_sco_pamp_on,
};
static struct aic3254_ctl_ops cops = {
.rx_amp_enable = doubleshot_rx_amp_enable,
.reset_3254 = doubleshot_reset_3254,
.spibus_enable = doubleshot_spibus_enable,
.lb_dsp_init = &LOOPBACK_DSP_INIT_PARAM,
.lb_receiver_imic = &LOOPBACK_Receiver_IMIC_PARAM,
.lb_speaker_imic = &LOOPBACK_Speaker_IMIC_PARAM,
.lb_headset_emic = &LOOPBACK_Headset_EMIC_PARAM,
.lb_receiver_bmic = &LOOPBACK_Receiver_BMIC_PARAM,
.lb_speaker_bmic = &LOOPBACK_Speaker_BMIC_PARAM,
.lb_headset_bmic = &LOOPBACK_Headset_BMIC_PARAM,
};
static struct acoustic_ops acoustic = {
.enable_mic_bias = doubleshot_mic_enable,
.support_aic3254 = doubleshot_support_aic3254,
.support_back_mic = doubleshot_support_back_mic,
.get_acoustic_tables = doubleshot_get_acoustic_tables,
};
void doubleshot_aic3254_set_mode(int config, int mode)
{
aic3254_set_mode(config, mode);
}
static struct q6v2audio_aic3254_ops aops = {
.aic3254_set_mode = doubleshot_aic3254_set_mode,
};
void __init doubleshot_audio_init(void)
{
int i = 0;
mutex_init(&bt_sco_lock);
mutex_init(&mic_lock);
#ifdef CONFIG_MSM8X60_AUDIO
pr_aud_info("%s\n", __func__);
htc_8x60_register_analog_ops(&ops);
htc_8x60_register_icodec_ops(&iops);
htc_8x60_register_ecodec_ops(&eops);
acoustic_register_ops(&acoustic);
htc_8x60_register_aic3254_ops(&aops);
/*fix voice sample rate as 8KHz for 3254 dual mic.*/
msm_set_voc_freq(8000, 8000);
#endif
aic3254_register_ctl_ops(&cops);
/* PMIC GPIO Init (See board-doubleshot.c) */
/*
EVT XA : 0x0000FFFF
DVT XB : 0x0101FFFF
DVT XC : 0x0202FFFF
Use XB as critiria because there are some XA devices did
not follow this rule.
*/
pr_info("%s: system_rev = 0x%08x", __func__, system_rev);
switch (system_rev)
{
case XA_DEV:
emic_micbias = OTHC_MICBIAS_1;
bmic_micbias = OTHC_MICBIAS_2;
break;
case XB_DEV:
case XC_DEV:
default:
emic_micbias = OTHC_MICBIAS_2;
bmic_micbias = OTHC_MICBIAS_1;
break;
}
/* Reset AIC3254 */
doubleshot_reset_3254();
for (i=0 ; i<sizeof(msm_snddev_gpio); i++)
gpio_tlmm_config(msm_snddev_gpio[i], GPIO_CFG_DISABLE);
}