| /* linux/arch/arm/mach-msm/board-lexikon-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/mfd/marimba-codec.h> |
| #include <linux/delay.h> |
| #include <mach/tpa2051d3.h> |
| #include <mach/gpio.h> |
| #include <mach/dal.h> |
| #include "board-lexikon.h" |
| #include <mach/qdsp5v2_1x/snddev_icodec.h> |
| #include <mach/qdsp5v2_1x/snddev_ecodec.h> |
| #include <mach/qdsp5v2_1x/audio_def.h> |
| #include <mach/qdsp5v2_1x/voice.h> |
| #include <mach/htc_acoustic_7x30.h> |
| #include <mach/htc_acdb_7x30.h> |
| |
| static struct mutex bt_sco_lock; |
| static struct mutex pm_lock; |
| static struct mutex vreg_lock; |
| static int usb_headset_connected; |
| #define LEXIKON_ACDB_SMEM_SIZE (0xE000) |
| |
| static struct q5v2_hw_info_percentage q5v2_audio_hw[Q5V2_HW_COUNT] = { |
| [Q5V2_HW_HANDSET] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-1250, -950, -650, -300, 100, 500, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-1250, -950, -650, -300, 100, 500, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_HEADSET] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-1100, -700, -300, 100, 500, 900, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-1100, -700, -300, 100, 500, 900, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_SPEAKER] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-800, -450, -100, 300, 750, 1300, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-800, -450, -100, 300, 750, 1300, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_BT_SCO] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-1500, -1200, -900, -600, -300, 0, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-1500, -1200, -900, -600, -300, 0, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_TTY] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_HS_SPKR] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-2000, -1700, -1400, -1100, -800, -500, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-2000, -1700, -1400, -1100, -800, -500, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_USB_HS] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-500, -200, 100, 400, 700, 1000, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-500, -200, 100, 400, 700, 1000, 0, 0, 0, 0}, |
| }, |
| [Q5V2_HW_HAC] = { |
| .max_step = 6, |
| .gain[VOC_NB_INDEX] = |
| {-500, -200, 100, 400, 700, 1000, 0, 0, 0, 0}, |
| .gain[VOC_WB_INDEX] = |
| {-500, -200, 100, 400, 700, 1000, 0, 0, 0, 0}, |
| }, |
| }; |
| |
| static unsigned aux_pcm_gpio_off[] = { |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_OUT, 0, GPIO_INPUT, |
| GPIO_PULL_DOWN, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_IN, 0, GPIO_INPUT, |
| GPIO_PULL_DOWN, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_SYNC, 0, GPIO_INPUT, |
| GPIO_PULL_DOWN, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_CLK, 0, GPIO_INPUT, |
| GPIO_PULL_DOWN, GPIO_2MA), |
| }; |
| |
| static unsigned aux_pcm_gpio_on[] = { |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_OUT, 1, GPIO_OUTPUT, |
| GPIO_NO_PULL, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_IN, 1, GPIO_INPUT, |
| GPIO_NO_PULL, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_SYNC, 1, GPIO_OUTPUT, |
| GPIO_NO_PULL, GPIO_2MA), |
| PCOM_GPIO_CFG(LEXIKON_GPIO_BT_PCM_CLK, 1, GPIO_OUTPUT, |
| GPIO_NO_PULL, GPIO_2MA), |
| }; |
| |
| void pm8058_spkr_gpio_cfg(int en) { |
| static int pm8058_spkr_refcount; |
| |
| mutex_lock(&pm_lock); |
| if (en) { |
| if (++pm8058_spkr_refcount == 1) |
| gpio_set_value( |
| PM8058_GPIO_PM_TO_SYS(LEXIKON_AUD_SPK_ENO), 1); |
| } else { |
| if (--pm8058_spkr_refcount == 0) |
| gpio_set_value( |
| PM8058_GPIO_PM_TO_SYS(LEXIKON_AUD_SPK_ENO), 0); |
| } |
| mutex_unlock(&pm_lock); |
| } |
| |
| int vreg_ncp_enable(int en) { |
| static int vreg_ncp_refcount; |
| struct vreg *vreg_ncp; |
| int ret = 0; |
| |
| mutex_lock(&vreg_lock); |
| vreg_ncp = vreg_get(NULL, "ncp"); |
| if (IS_ERR(vreg_ncp)) { |
| pr_aud_err("%s: vreg_get(%s) failed (%ld)\n", |
| __func__, "ncp", PTR_ERR(vreg_ncp)); |
| mutex_unlock(&vreg_lock); |
| return -1; |
| } |
| |
| if (en) { |
| if (++vreg_ncp_refcount == 1) |
| ret = vreg_enable(vreg_ncp); |
| } else { |
| if (--vreg_ncp_refcount == 0) |
| ret = vreg_disable(vreg_ncp); |
| } |
| mutex_unlock(&vreg_lock); |
| pr_aud_info("%s(%d), ret %d\n", __func__, en, ret); |
| return ret; |
| } |
| |
| void lexikon_snddev_poweramp_on(int en) |
| { |
| pr_aud_info("%s %d\n", __func__, en); |
| if (en) { |
| pm8058_spkr_gpio_cfg(1); |
| if (usb_headset_connected) { |
| gpio_set_value(95, 1); |
| gpio_set_value(96, 0); |
| } |
| mdelay(30); |
| set_speaker_amp(1); |
| } else { |
| set_speaker_amp(0); |
| if (usb_headset_connected) { |
| gpio_set_value(95, 0); |
| gpio_set_value(96, 1); |
| } |
| pm8058_spkr_gpio_cfg(0); |
| } |
| } |
| |
| void lexikon_snddev_hsed_pamp_on(int en) |
| { |
| pr_aud_info("%s %d\n", __func__, en); |
| if (en) { |
| vreg_ncp_enable(1); |
| pm8058_spkr_gpio_cfg(1); |
| mdelay(30); |
| set_headset_amp(1); |
| } else { |
| set_headset_amp(0); |
| pm8058_spkr_gpio_cfg(0); |
| vreg_ncp_enable(0); |
| } |
| } |
| |
| void lexikon_snddev_hs_spk_pamp_on(int en) |
| { |
| pr_aud_info("%s %d\n", __func__, en); |
| |
| if (en) { |
| vreg_ncp_enable(1); |
| pm8058_spkr_gpio_cfg(1); |
| mdelay(30); |
| set_speaker_headset_amp(1); |
| } else { |
| set_speaker_headset_amp(0); |
| pm8058_spkr_gpio_cfg(0); |
| vreg_ncp_enable(0); |
| } |
| } |
| |
| void lexikon_snddev_bt_sco_pamp_on(int en) |
| { |
| static int bt_sco_refcount; |
| pr_aud_info("%s %d\n", __func__, en); |
| mutex_lock(&bt_sco_lock); |
| if (en) { |
| if (++bt_sco_refcount == 1) |
| config_gpio_table(aux_pcm_gpio_on, |
| ARRAY_SIZE(aux_pcm_gpio_on)); |
| } else { |
| if (--bt_sco_refcount == 0) { |
| config_gpio_table(aux_pcm_gpio_off, |
| ARRAY_SIZE(aux_pcm_gpio_off)); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_OUT, 0); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_SYNC, 0); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_CLK, 0); |
| } |
| } |
| mutex_unlock(&bt_sco_lock); |
| } |
| |
| void lexikon_snddev_receiver_pamp_on(int en) |
| { |
| gpio_set_value(PM8058_GPIO_PM_TO_SYS(LEXIKON_AUD_HANDSET_ENO), en); |
| } |
| |
| void lexikon_usb_headset_on(int en) |
| { |
| pr_aud_err("%s %d\n",__func__, en); |
| if (en) { |
| usb_headset_adie_enable(1); |
| gpio_set_value(95, 0); |
| gpio_set_value(96, 1); |
| usb_headset_connected = 1; |
| } else { |
| gpio_set_value(95, 1); |
| gpio_set_value(96, 0); |
| usb_headset_adie_enable(0); |
| usb_headset_connected = 0; |
| } |
| } |
| |
| void lexikon_snddev_imic_pamp_on(int en) |
| { |
| pr_aud_info("%s: %d\n", __func__, en); |
| |
| if (en) |
| pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_ALWAYS); |
| else |
| pmic_hsed_enable(PM_HSED_CONTROLLER_0, PM_HSED_ENABLE_OFF); |
| } |
| |
| int lexikon_get_rx_vol(uint8_t hw, int network, int level) |
| { |
| struct q5v2_hw_info_percentage *info; |
| int vol; |
| |
| info = &q5v2_audio_hw[hw]; |
| |
| level = (level > 100)? 100 : ((level < 0) ? 0 : level); |
| vol = info->gain[network][(uint32_t)((info->max_step - 1) * level / 100)]; |
| |
| pr_aud_info("%s(%d, %d, %d) => %d\n", __func__, hw, network, level, vol); |
| return vol; |
| } |
| |
| void lexikon_mic_bias_enable(int en, int shift) |
| { |
| pr_aud_info("%s: %d\n", __func__, en); |
| |
| if (en) |
| pmic_hsed_enable(PM_HSED_CONTROLLER_1, PM_HSED_ENABLE_ALWAYS); |
| else |
| pmic_hsed_enable(PM_HSED_CONTROLLER_1, PM_HSED_ENABLE_OFF); |
| } |
| |
| uint32_t lexikon_get_smem_size(void) |
| { |
| return LEXIKON_ACDB_SMEM_SIZE; |
| } |
| |
| static struct q5v2audio_analog_ops ops = { |
| .speaker_enable = lexikon_snddev_poweramp_on, |
| .headset_enable = lexikon_snddev_hsed_pamp_on, |
| .handset_enable = lexikon_snddev_receiver_pamp_on, |
| .bt_sco_enable = lexikon_snddev_bt_sco_pamp_on, |
| .headset_speaker_enable = lexikon_snddev_hs_spk_pamp_on, |
| .int_mic_enable = lexikon_snddev_imic_pamp_on, |
| .fm_headset_enable = lexikon_snddev_hsed_pamp_on, |
| .fm_speaker_enable = lexikon_snddev_poweramp_on, |
| }; |
| |
| static struct q5v2audio_ecodec_ops eops = { |
| .bt_sco_enable = lexikon_snddev_bt_sco_pamp_on, |
| }; |
| |
| static struct q5v2voice_ops vops = { |
| .get_rx_vol = lexikon_get_rx_vol, |
| }; |
| |
| static struct acoustic_ops acoustic = { |
| .enable_mic_bias = lexikon_mic_bias_enable, |
| .enable_usb_headset = lexikon_usb_headset_on, |
| }; |
| |
| static struct acdb_ops acdb = { |
| .get_smem_size = lexikon_get_smem_size, |
| }; |
| |
| void __init lexikon_audio_init(void) |
| { |
| struct pm8058_gpio audio_pwr = { |
| .direction = PM_GPIO_DIR_OUT, |
| .output_buffer = PM_GPIO_OUT_BUF_CMOS, |
| .output_value = 0, |
| .pull = PM_GPIO_PULL_NO, |
| .vin_sel = 6, /* LDO5 2.85 V */ |
| .out_strength = PM_GPIO_STRENGTH_HIGH, |
| .function = PM_GPIO_FUNC_NORMAL, |
| }; |
| |
| struct pm8058_gpio tpa2051_pwr = { |
| .direction = PM_GPIO_DIR_OUT, |
| .output_buffer = PM_GPIO_OUT_BUF_CMOS, |
| .output_value = 0, |
| .pull = PM_GPIO_PULL_NO, |
| .vin_sel = 6, /* S3 1.8 V */ |
| .out_strength = PM_GPIO_STRENGTH_HIGH, |
| .function = PM_GPIO_FUNC_NORMAL, |
| }; |
| |
| mutex_init(&bt_sco_lock); |
| mutex_init(&pm_lock); |
| mutex_init(&vreg_lock); |
| #ifdef CONFIG_MSM7KV2_1X_AUDIO |
| htc_7x30_register_analog_ops(&ops); |
| htc_7x30_register_ecodec_ops(&eops); |
| htc_7x30_register_voice_ops(&vops); |
| acoustic_register_ops(&acoustic); |
| acdb_register_ops(&acdb); |
| #endif |
| pm8058_gpio_config(LEXIKON_AUD_HANDSET_ENO, &audio_pwr); |
| pm8058_gpio_config(LEXIKON_AUD_SPK_ENO, &tpa2051_pwr); |
| |
| mutex_lock(&bt_sco_lock); |
| config_gpio_table(aux_pcm_gpio_off, ARRAY_SIZE(aux_pcm_gpio_off)); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_OUT, 0); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_SYNC, 0); |
| gpio_set_value(LEXIKON_GPIO_BT_PCM_CLK, 0); |
| mutex_unlock(&bt_sco_lock); |
| } |
| |
| |