📄 omap2-audio-twl4030.c
字号:
/* * sound/arm/omap/omap-audio-twl4030.c * * Codec driver for TWL4030 for OMAP processors * * Copyright (C) 2007 Texas Instruments, Inc. * * This package 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. * * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * History: * ------- * 2006-01-18 Nishanth Menon - Created * 2006-09-15 Jian Zhang - Ported to ALSA * 2007-04-16 Leonides Martinez - Added ALSA controls *//***************************** INCLUDES ************************************/#include <linux/module.h>#include <linux/init.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/errno.h>#include <linux/sound.h>#include <linux/soundcard.h>#include <linux/bitops.h>#include <linux/interrupt.h>#include <linux/device.h>#include <asm/semaphore.h>#include <asm/uaccess.h>#include <asm/hardware.h>#include <asm/arch/dma.h>#include <asm/io.h>#include <asm/arch/mux.h>#include <asm/arch/io.h>#include <asm/mach-types.h>#include <sound/driver.h>#include <sound/core.h>#include <sound/initval.h>#include <sound/pcm.h>#include <sound/control.h>#include "omap2-audio_if.h"#include <asm/arch/omap2_mcbsp.h>#include <asm/arch/clock.h>#include <asm/arch/twl4030.h>#include "omap2-audio-twl4030.h"/*********** Debug Macros ********//* Change to define if required *//* To Generate a rather shrill tone -test the entire path */#undef TONE_GEN/* To dump the twl registers for debug */#undef TWL_DUMP_REGISTERS#undef TWL_DUMP_REGISTERS_MCBSP#undef DEBUG#undef DPRINTK#define DPRINTK( x... )#define FN_IN#define FN_OUT(n)static int codec_configured = 0;static int mcbsp_interface_acquired = 0;/**************** ************** * ***************************** COMM APIS ******************************** * *//** * @brief audio_twl4030_write * * @param address * @param data * * @return 0 if successful */static inline int audio_twl4030_write(u8 address, u8 data){ int ret = 0; /* DEBUG */ DPRINTK("add[0x%02x]<=0x%02x\n", address, data); ret = twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, data, address); if (ret >= 0) { ret = 0; } else { DPRINTK("TWL4030:Audio:Write[0x%x]" " Error %d\n", address, ret); } FN_OUT(ret); return ret;}/** * @brief audio_twl4030_read * * @param address * * @return data if successful else return negative error value */static inline int audio_twl4030_read(u8 address){ u8 data; int ret = 0; ret = twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &data, address); if (ret >= 0) { ret = data; } else { DPRINTK("TWL4030:Audio:Read[0x%x] Error %d\n", address, ret); } return ret;}/**************** ************** * ***************************** CODEC UTIL APIS ************************** * *//** * @brief twl4030_ext_mut_conf - configure GPIO for data out * * @return error in case of failure else 0 */static inline int twl4030_ext_mut_conf(void){ int ret; u8 data; ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, &data , GPIO_DATA_DIR); if (ret) return ret; data |= 0x1<< T2_AUD_EXT_MUT_GPIO; //ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, data , GPIO_DATA_DIR); return ret;}/** * @brief twl4030_ext_mut_off - disable mute also handle time of wait * * @return error in case of failure else 0 */static inline int twl4030_ext_mut_off(void){ int ret; u8 data; ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, &data , GPIO_CLR); if (ret) return ret; /* Wait for ramp duration.. settling time for signal */ udelay(1); data |= 0x1<< T2_AUD_EXT_MUT_GPIO; //ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, data, GPIO_CLR); //Clear mute return ret;}/** * @brief twl4030_ext_mut_on - enable mute * * @return error in case of failure else 0 */static inline int twl4030_ext_mut_on(void){ int ret; u8 data; ret = twl4030_i2c_read_u8(TWL4030_MODULE_GPIO, &data , GPIO_SET); if (ret) return ret; data |= 0x1<< T2_AUD_EXT_MUT_GPIO; //ret = twl4030_i2c_write_u8(TWL4030_MODULE_GPIO, data, GPIO_SET); //Set mute return ret;}/** * @brief twl4030_codec_on * * @return 0 if success, else error value */static inline int twl4030_codec_on(void){ int data = audio_twl4030_read(REG_CODEC_MODE); FN_IN; if (unlikely(data < 0)) { printk(KERN_ERR "Reg read failed\n"); return data; } if (unlikely(data & BIT_CODEC_MODE_CODECPDZ_M)) { DPRINTK("Codec already powered on??\n"); FN_OUT(0); return 0; } data |= BIT_CODEC_MODE_CODECPDZ_M; FN_OUT(0); return audio_twl4030_write(REG_CODEC_MODE, (u8) data);}/** * @brief twl4030_codec_off - Switch off the codec * * @return 0 if success, else error value */static inline int twl4030_codec_off(void){ int data = audio_twl4030_read(REG_CODEC_MODE); FN_IN; if (unlikely(data < 0)) { printk(KERN_ERR "Reg read failed\n"); return data; } /* Expected to be already off at bootup - but * we do not know the status of the device * hence would like to force a shut down */ if (unlikely(!(data & BIT_CODEC_MODE_CODECPDZ_M))) { DPRINTK("Codec already powered off??\n"); FN_OUT(0); return 0; } data &= ~BIT_CODEC_MODE_CODECPDZ_M; FN_OUT(0); return audio_twl4030_write(REG_CODEC_MODE, (u8) data);}/** * @brief twl4030_codec_tog_on - set the power to on after toggle to off * and then on * * @return 0 if success, else error value */static int twl4030_codec_tog_on(void){ int ret = 0; int data = audio_twl4030_read(REG_CODEC_MODE); FN_IN; if (unlikely(data < 0)) { printk(KERN_ERR "Reg read failed\n"); return data; } data &= ~BIT_CODEC_MODE_CODECPDZ_M; ret = audio_twl4030_write(REG_CODEC_MODE, (u8) data); if (ret) { printk(KERN_ERR "CODEC WRITE failed ! %d\n",ret); FN_OUT(ret); return ret; } udelay(10); /* 10 ms delay for power settling */ data |= BIT_CODEC_MODE_CODECPDZ_M; ret = audio_twl4030_write(REG_CODEC_MODE, (u8) data); udelay(10); /* 10 ms delay for power settling */ FN_OUT(ret); return ret;}/** * @brief twl_mcbsp_irq - the Interrupt handler for mcbsp events * * @param arg - ignored * @param irq_status -mcbsp interrupt status * @param regs -ignored */static void twl_mcbsp_irq(void *arg, u32 irq_status, struct pt_regs *regs){ /* In case of errors - we need to reset the reciever to ensure * recovery */ if ((irq_status & (OMAP2_MCBSP_IRQSTAT_XSYNCERR | OMAP2_MCBSP_IRQSTAT_XUNDFL | OMAP2_MCBSP_IRQSTAT_XOVFL))) { /* We will handle this when we attempt a transfer */ tx_err = 1; } if ((irq_status & (OMAP2_MCBSP_IRQSTAT_RSYNCERR | OMAP2_MCBSP_IRQSTAT_RUNDFL | OMAP2_MCBSP_IRQSTAT_ROVFL))) { /* We will handle this when we attempt a transfer */ rx_err = 1; }}/** * @brief twl4030_enable_output - enable the output path * * NOTE * Codec power must be shut down during before this call * * NOTE * This does not take care of gain settings * for the specified output * @return 0 if successful */static int twl4030_enable_output(void){ u8 ear_ctl = 0; u8 hs_ctl = 0; u8 hs_pop = 0; u8 hf_ctll = 0; u8 hf_ctlr = 0; u8 dac_ctl = 0; u8 ck_ctll = 0; u8 ck_ctlr = 0; u8 opt = 0; u32 line = 0; int ret = 0; FN_IN; opt = audio_twl4030_read(REG_OPTION) & ~(BIT_OPTION_ARXR2_EN_M | BIT_OPTION_ARXL2_EN_M); /* AR2 and AL2 are active for I2S */ if ((current_output & OUTPUT_STEREO_HEADSET) == OUTPUT_STEREO_HEADSET) { hs_ctl |= BIT_HS_SEL_HSOR_AR2_EN_M | BIT_HS_SEL_HSOL_AL2_EN_M; /* POP control - VMID? */ hs_pop = BIT_HS_POPN_SET_VMID_EN_M; dac_ctl = BIT_AVDAC_CTL_ADACL2_EN_M | BIT_AVDAC_CTL_ADACR2_EN_M; opt |= BIT_OPTION_ARXR2_EN_M | BIT_OPTION_ARXL2_EN_M; } if ((current_output & OUTPUT_HANDS_FREE_CLASSD) == OUTPUT_HANDS_FREE_CLASSD) { if (handsfree_en) { hf_ctll |= HANDS_FREEL_AL2 << BIT_HFL_CTL_HFL_INPUT_SEL | BIT_HFL_CTL_HFL_REF_EN_M; hf_ctlr |= HANDS_FREER_AR2 << BIT_HFR_CTL_HFR_INPUT_SEL | BIT_HFR_CTL_HFR_REF_EN_M; } dac_ctl = BIT_AVDAC_CTL_ADACL2_EN_M | BIT_AVDAC_CTL_ADACR2_EN_M; opt |= BIT_OPTION_ARXR2_EN_M | BIT_OPTION_ARXL2_EN_M; } if ((current_output & OUTPUT_MONO_EARPIECE) == OUTPUT_MONO_EARPIECE) { /* only AL2 comes in case of i2s */ ear_ctl |= BIT_EAR_CTL_EAR_AL2_EN_M; dac_ctl = BIT_AVDAC_CTL_ADACL2_EN_M; opt |= BIT_OPTION_ARXL2_EN_M; } if ((current_output & OUTPUT_CARKIT) == OUTPUT_CARKIT) { ck_ctll |= BIT_PRECKL_CTL_PRECKL_AL2_EN_M | BIT_PRECKL_CTL_PRECKL_EN_M; ck_ctlr |= BIT_PRECKR_CTL_PRECKR_AR2_EN_M | BIT_PRECKR_CTL_PRECKR_EN_M; dac_ctl = BIT_AVDAC_CTL_ADACL2_EN_M | BIT_AVDAC_CTL_ADACR2_EN_M; opt |= BIT_OPTION_ARXL2_EN_M | BIT_OPTION_ARXR2_EN_M; } if (opt) { ret = audio_twl4030_write(REG_OPTION, opt); if (ret) { line = __LINE__; goto enable_op_exit; } } if (ear_ctl) { u8 temp; temp = audio_twl4030_read(REG_EAR_CTL); ear_ctl |= temp; ret = audio_twl4030_write(REG_EAR_CTL, ear_ctl); if (ret) { line = __LINE__; goto enable_op_exit; } } if (hs_ctl) { ret = audio_twl4030_write(REG_HS_SEL, hs_ctl); if (ret) { line = __LINE__; goto enable_op_exit; } } if (hs_pop) { /* IMPORTANT: The following sequence is *required* * for starting the headset- esp for * ensuring the existance of the negative phase of * analog signal */ ret = audio_twl4030_write(REG_HS_POPN_SET, hs_pop); if (ret) { line = __LINE__; goto enable_op_exit; } hs_pop |= BIT_HS_POPN_SET_RAMP_EN_M; udelay(1); /* require a short delay before enabling ramp */ ret = audio_twl4030_write(REG_HS_POPN_SET, hs_pop); if (ret) { line = __LINE__; goto enable_op_exit; } } /* IMPORTANT: The following sequence is *required* * for starting the speakers! */ if (hf_ctll) { ret = audio_twl4030_write(REG_HFL_CTL, hf_ctll); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctll |= BIT_HFL_CTL_HFL_RAMP_EN_M; ret = audio_twl4030_write(REG_HFL_CTL, hf_ctll); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctll |= BIT_HFL_CTL_HFL_LOOP_EN_M; ret = audio_twl4030_write(REG_HFL_CTL, hf_ctll); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctll |= BIT_HFL_CTL_HFL_HB_EN_M; ret = audio_twl4030_write(REG_HFL_CTL, hf_ctll); if (ret) { line = __LINE__; goto enable_op_exit; } } if (hf_ctlr) { ret = audio_twl4030_write(REG_HFR_CTL, hf_ctlr); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctlr |= BIT_HFR_CTL_HFR_RAMP_EN_M; ret = audio_twl4030_write(REG_HFR_CTL, hf_ctlr); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctlr |= BIT_HFR_CTL_HFR_LOOP_EN_M; ret = audio_twl4030_write(REG_HFR_CTL, hf_ctlr); if (ret) { line = __LINE__; goto enable_op_exit; } hf_ctlr |= BIT_HFR_CTL_HFR_HB_EN_M; ret = audio_twl4030_write(REG_HFR_CTL, hf_ctlr); if (ret) { line = __LINE__; goto enable_op_exit; } } if (dac_ctl) { /* I2S should go thru DACR2, DACL2- unless we are on mono */ if (current_stereomode == MONO_MODE) { dac_ctl &= ~BIT_AVDAC_CTL_ADACR2_EN_M; } ret = audio_twl4030_write(REG_AVDAC_CTL, dac_ctl); if (ret) { line = __LINE__; goto enable_op_exit; } } if (ck_ctll) { ret = audio_twl4030_write(REG_PRECKL_CTL, ck_ctll); if (ret) { line = __LINE__; goto enable_op_exit; } } if (ck_ctlr) { ret = audio_twl4030_write(REG_PRECKR_CTL, ck_ctlr); } enable_op_exit: if (ret) printk(KERN_ERR "Error in Enable output[%d] in Line %d\n", ret, line); FN_OUT(ret); return ret;}/** * @brief twl4030_disable_output - remove the output path * *NOTE* shut down the codec before attempting this * * @return 0 if successful */static int twl4030_disable_output(void){ int ret = 0; int line = 0; u8 RdReg; FN_IN; RdReg = audio_twl4030_read(REG_PRECKR_CTL); RdReg &= BIT_PRECKR_CTL_PRECKR_GAIN_M; /* To preserve gain settings */ ret = audio_twl4030_write(REG_PRECKR_CTL, 0x0); if (ret) { line = __LINE__; goto disable_op_exit; } RdReg = audio_twl4030_read(REG_PRECKL_CTL); RdReg &= BIT_PRECKL_CTL_PRECKL_GAIN_M; /* To preserve gain settings */ ret = audio_twl4030_write(REG_PRECKL_CTL, 0x0); if (ret) { line = __LINE__; goto disable_op_exit; } ret = audio_twl4030_write(REG_PREDR_CTL, 0x0); if (ret) { line = __LINE__; goto disable_op_exit; } ret = audio_twl4030_write(REG_PREDL_CTL, 0x0); if (ret) { line = __LINE__; goto disable_op_exit; } RdReg = audio_twl4030_read(REG_EAR_CTL); RdReg &= BIT_EAR_CTL_EAR_GAIN_M; /* To preserve gain settings */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -