[alsa-devel] [ASoC] Problem with codec driver for tlv320aic32x4 and bias level.
javier Martin
javier.martin at vista-silicon.com
Mon Feb 21 10:00:38 CET 2011
Hi,
I am currently developing a codec driver for tlv320aic32x4 codec for
kernel 2.6.37, based on a TI driver made for 2.6.27. The last version
of the code is posted below as plain text, not as a patch because it
is only meant for showing purposes.
As you can see, my "aic32x4_probe" function sets bias level to
SND_SOC_BIAS_OFF. As far as I know, when I enable playback (with aplay
command), ASoC should call "aic32x4_set_bias_level" and change level
to SND_SOC_BIAS_ON and thus triggering some dapm events. However, this
won't happen with the current version of the code.
Maybe someone could point out some tips.
--
/*
* linux/sound/soc/codecs/tlv320aic32x4.c
*
* Copyright 2011 Vista Silicon S.L.
*
* Author: Javier Martin <javier.martin at vista-silicon.com>
*
* Based on sound/soc/codecs/wm8974 and TI driver for kernel 2.6.27.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
#include "tlv320aic32x4.h"
#define SOC_DOUBLE_R_AIC32X4(xname, reg_left, reg_right, shift, mask, invert) \
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
.info = snd_soc_info_volsw_2r_aic32x4, \
.get = snd_soc_get_volsw_2r_aic32x4, .put = snd_soc_put_volsw_2r_aic32x4, \
.private_value = (reg_left) | ((shift) << 8) | \
((mask) << 12) | ((invert) << 20) | ((reg_right) << 24) }
static int aic32x4_set_bias_level(struct snd_soc_codec *codec, enum
snd_soc_bias_level level);
static int snd_soc_info_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
static int snd_soc_get_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
static int snd_soc_put_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
/*
*****************************************************************************
* Structure Initialization
*****************************************************************************
*/
static const struct snd_kcontrol_new aic32x4_snd_controls[] = {
/* Output */
/* sound new kcontrol for PCM Playback volume control */
SOC_DOUBLE_R_AIC32X4("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0xAf,
0),
/* sound new kcontrol for HP driver gain */
SOC_DOUBLE_R_AIC32X4("HP Driver Gain", HPL_GAIN, HPR_GAIN, 0, 0x23, 0),
/* sound new kcontrol for LO driver gain */
SOC_DOUBLE_R_AIC32X4("LO Driver Gain", LOL_GAIN, LOR_GAIN, 0, 0x23, 0),
/* sound new kcontrol for HP mute */
SOC_DOUBLE_R("HP DAC Playback Switch", HPL_GAIN, HPR_GAIN, 6,
0x01, 1),
/* sound new kcontrol for LO mute */
SOC_DOUBLE_R("LO DAC Playback Switch", LOL_GAIN, LOR_GAIN, 6,
0x01, 1),
/* Input */
/* sound new kcontrol for PGA capture volume */
SOC_DOUBLE_R("ADC Volume Level", LADC_VOL, RADC_VOL, 0, 0x28,
0),
SOC_DOUBLE_R("PGA Gain Level", LMICPGA_VOL_CTRL, RMICPGA_VOL_CTRL, 0, 0x5f, 0),
/* DAC Automute control */
SOC_SINGLE("Auto-mute", DAC_MUTE_CTRL_REG, 4, 7, 0),
/* AGC controls */
SOC_SINGLE("AGC Left Enable Switch", LEFT_CHN_AGC_1, 7, 1, 0),
SOC_SINGLE("AGC Right Enable Switch", RIGHT_CHN_AGC_1, 7, 1, 0),
SOC_DOUBLE_R("AGC Target Level", LEFT_CHN_AGC_1, RIGHT_CHN_AGC_1,
4, 0x07, 0),
SOC_DOUBLE_R("AGC Gain Hysteresis", LEFT_CHN_AGC_1, RIGHT_CHN_AGC_1,
0, 0x03, 0),
SOC_DOUBLE_R("AGC Hysteresis", LEFT_CHN_AGC_2, RIGHT_CHN_AGC_2,
6, 0x03, 0),
SOC_DOUBLE_R("AGC Noise Threshold", LEFT_CHN_AGC_2, RIGHT_CHN_AGC_2,
1, 0x1F, 0),
SOC_DOUBLE_R("AGC Max PGA", LEFT_CHN_AGC_3, RIGHT_CHN_AGC_3,
0, 0x7F, 0),
SOC_DOUBLE_R("AGC Attack Time", LEFT_CHN_AGC_4, RIGHT_CHN_AGC_4,
3, 0x1F, 0),
SOC_DOUBLE_R("AGC Decay Time", LEFT_CHN_AGC_5, RIGHT_CHN_AGC_5,
3, 0x1F, 0),
SOC_DOUBLE_R("AGC Noise Debounce", LEFT_CHN_AGC_6, RIGHT_CHN_AGC_6,
0, 0x1F, 0),
SOC_DOUBLE_R("AGC Signal Debounce", LEFT_CHN_AGC_7, RIGHT_CHN_AGC_7,
0, 0x0F, 0),
/* MIC BIAS control */
SOC_SINGLE("MIC BIAS", MICBIAS_CTRL, 6, 1, 0),
};
/* the sturcture contains the different values for mclk */
static const struct aic32x4_rate_divs aic32x4_divs[] = {
/*
* mclk, rate, p_val, pll_j, pll_d, dosr, ndac, mdac, aosr, nadc, madc, blck_N,
* codec_speficic_initializations
*/
/* 8k rate */
{12000000, 8000, 1, 7, 6800, 768, 5, 3, 128, 5, 18, 24,
{{60, 1}, {61, 1}}},
{24000000, 8000, 2, 7, 6800, 768, 15, 1, 64, 45, 4, 24,
{{60, 1}, {61, 1}}},
{25000000, 8000, 2, 7, 3728, 768, 15, 1, 64, 45, 4, 24,
{{60, 1}, {61, 1}}},
/* 11.025k rate */
{12000000, 11025, 1, 7, 5264, 512, 8, 2, 128, 8, 8, 16,
{{60, 1}, {61, 1}}},
{24000000, 11025, 2, 7, 5264, 512, 16, 1, 64, 32, 4, 16,
{{60, 1}, {61, 1}}},
/* 16k rate */
{12000000, 16000, 1, 7, 6800, 384, 5, 3, 128, 5, 9, 12,
{{60, 1}, {61, 1}}},
{24000000, 16000, 2, 7, 6800, 384, 15, 1, 64, 18, 5, 12,
{{60, 1}, {61, 1}}},
{25000000, 16000, 2, 7, 3728, 384, 15, 1, 64, 18, 5, 12,
{{60, 1}, {61, 1}}},
/* 22.05k rate */
{12000000, 22050, 1, 7, 5264, 256, 4, 4, 128, 4, 8, 8,
{{60, 1}, {61, 1}}},
{24000000, 22050, 2, 7, 5264, 256, 16, 1, 64, 16, 4, 8,
{{60, 1}, {61, 1}}},
{25000000, 22050, 2, 7, 2253, 256, 16, 1, 64, 16, 4, 8,
{{60, 1}, {61, 1}}},
/* 32k rate */
{12000000, 32000, 1, 7, 1680, 192, 2, 7, 64, 2, 21, 6,
{{60, 1}, {61, 1}}},
{24000000, 32000, 2, 7, 1680, 192, 7, 2, 64, 7, 6, 6,
{{60, 1}, {61, 1}}},
/* 44.1k rate */
{12000000, 44100, 1, 7, 5264, 128, 2, 8, 128, 2, 8, 4,
{{60, 1}, {61, 1}}},
{24000000, 44100, 2, 7, 5264, 128, 8, 2, 64, 8, 4, 4,
{{60, 1}, {61, 1}}},
{25000000, 44100, 2, 7, 2253, 128, 8, 2, 64, 8, 4, 4,
{{60, 1}, {61, 1}}},
/* 48k rate */
{12000000, 48000, 1, 8, 1920, 128, 2, 8, 128, 2, 8, 4,
{{60, 1}, {61, 1}}},
{24000000, 48000, 2, 8, 1920, 128, 8, 2, 64, 8, 4, 4,
{{60, 1}, {61, 1}}},
{25000000, 48000, 2, 7, 8643, 128, 8, 2, 64, 8, 4, 4,
{{60, 1}, {61, 1}}},
/*96k rate */
{12000000, 96000, 1, 8, 1920, 64, 2, 8, 64, 2, 8, 2,
{{60, 7}, {61, 7}}},
{24000000, 96000, 2, 8, 1920, 64, 4, 4, 64, 8, 2, 2,
{{60, 7}, {61, 7}}},
/*192k */
{12000000, 192000, 1, 8, 1920, 32, 2, 8, 32, 2, 8, 1,
{{60, 17}, {61, 13}}},
{24000000, 192000, 2, 8, 1920, 32, 4, 4, 32, 4, 4, 1,
{{60, 17}, {61, 13}}},
};
/*
*----------------------------------------------------------------------------
* @struct aic32x4_priv |
* AIC32X4 priviate data structure to set the system clock, mode and
* page number.
* @field u32 | sysclk |
* system clock
* @field s32 | master |
* master/slave mode setting for AIC32X4
* @field u8 | page_no |
* page number. Here, page 0 and page 1 are used.
*----------------------------------------------------------------------------
*/
struct aic32x4_priv {
u32 sysclk;
s32 master;
u8 page_no;
};
#define aic32x4_reset(c) aic32x4_write(c, AIC32X4_RESET, 0x01);
/*
* aic32x4 initialization data
* This structure initialization contains the initialization required for
* AIC32X4.
* These registers values (reg_val) are written into the respective AIC32X4
* register offset (reg_offset) to initialize AIC32X4.
* These values are used in aic32x4_init() function only.
*/
static const struct aic32x4_configs aic32x4_reg_init[] = {
/* Carry out the software reset */
{AIC32X4_RESET, 0x01},
/* Disable crude LDO */
{POW_CFG, 0x08},
/* Switch on the analog blocks */
{LDO_CTL, 0x01},
/* Do not connect IN1_L and IN1_R to CM (differential mode) */
{INPUT_CFG_REG, 0x00},
/* USE LDOIN range 1.8 - 3.6 V */
{CM_MODE_CTL, 0x03},
/* PLL is CODEC_CLKIN */
{CLK_REG_1, PLLCLK_2_CODEC_CLKIN},
/* DAC_MOD_CLK is BCLK source */
{AIS_REG_3, DAC_MOD_CLK_2_BDIV_CLKIN},
/* Setting up DAC Channel */
{DAC_CHN_REG,
LDAC_2_LCHN | RDAC_2_RCHN | SOFT_STEP_2WCLK},
/* Headphone powerup */
{HPHONE_STARTUP_CTRL, 0x35},
/* Left Channel DAC recons filter's positive terminal is routed to HPL */
{HPL_ROUTE_CTL, LDAC_CHNL_2_HPL},
/* Right Channel DAC recons filter's positive terminal is routed to HPR */
{HPR_ROUTE_CTL, RDAC_CHNL_2_HPR},
/* Left Channel DAC recons filter's positive terminal is routed to LOL */
{LOL_ROUTE_CTL, LDAC_CHNL_2_HPL},
/* Right Channel DAC recons filter's positive terminal is routed to LOR */
{LOR_ROUTE_CTL, RDAC_CHNL_2_HPR},
/* HPL unmute and gain 0db */
{HPL_GAIN, 0x0},
/* HPR unmute and gain 0db */
{HPR_GAIN, 0x0},
/* LOL unmute and gain 0db */
{LOL_GAIN, 0x0},
/* LOR unmute and gain 0db */
{LOR_GAIN, 0x0},
/* Unmute DAC Left and Right channels */
{DAC_MUTE_CTRL_REG, 0x00},
/* IN2_L is selected for left P */
{LMICPGA_PIN_CFG, 0x10},
/* IN2_R is selected for left M */
{LMICPGA_NIN_CFG, 0x10},
/* IN1_R is selected for right P */
{RMICPGA_PIN_CFG, 0x40},
/* IN1_L is selected for right M */
{RMICPGA_NIN_CFG, 0x10},
/* Left mic PGA unmuted */
{LMICPGA_VOL_CTRL, 0x00},
/* Right mic PGA unmuted */
{RMICPGA_VOL_CTRL, 0x00},
/* ADC volume control change by 2 gain step per ADC Word Clock */
{ADC_REG_1, 0x02},
/* Unmute ADC left and right channels */
{ADC_FGA, 0x00},
/* MICBIAS = 2.075V(CM=0.75V) generated from LDOIN */
{MICBIAS_CTRL, 0x28},
/* FIXME: these registers should be configured from user space using alsactl */
{146, 0x0a},
{147, 0x0a},
{83, 0x28},
{84, 0x28},
{187, 0x20},
{188, 0x20},
{64, 0x10},
{86, 0x80},
{94, 0x80},
{87, 0x3e},
{95, 0x3e},
{88, 0x32},
{96, 0x32},
{179, 0x68},
{88, 0x14}
};
/* Left DAC_L Mixer */
static const struct snd_kcontrol_new hpl_output_mixer_controls[] = {
SOC_DAPM_SINGLE("L_DAC switch", HPL_ROUTE_CTL, 3, 1, 0),
SOC_DAPM_SINGLE("IN1_L switch", HPL_ROUTE_CTL, 2, 1, 0),
// SOC_DAPM_SINGLE("Left_Bypass switch", HPL_ROUTE_CTL, 1, 1, 0),
};
/* Right DAC_R Mixer */
static const struct snd_kcontrol_new hpr_output_mixer_controls[] = {
SOC_DAPM_SINGLE("R_DAC switch", HPR_ROUTE_CTL, 3, 1, 0),
SOC_DAPM_SINGLE("IN1_R switch", HPR_ROUTE_CTL, 2, 1, 0),
// SOC_DAPM_SINGLE("Right_Bypass switch", HPR_ROUTE_CTL, 1, 1, 0),
};
static const struct snd_kcontrol_new lol_output_mixer_controls[] = {
SOC_DAPM_SINGLE("L_DAC switch", LOL_ROUTE_CTL, 3, 1, 0),
// SOC_DAPM_SINGLE("Left_Bypass switch", HPL_ROUTE_CTL, 1, 1, 0),
};
static const struct snd_kcontrol_new lor_output_mixer_controls[] = {
SOC_DAPM_SINGLE("R_DAC switch", LOR_ROUTE_CTL, 3, 1, 0),
// SOC_DAPM_SINGLE("Right_Bypass switch", LOR_ROUTE_CTL, 1, 1, 0),
};
/* Right DAC_R Mixer */
static const struct snd_kcontrol_new left_input_mixer_controls[] = {
SOC_DAPM_SINGLE("IN1_L switch", LMICPGA_PIN_CFG, 6, 1, 0),
SOC_DAPM_SINGLE("IN2_L switch", LMICPGA_PIN_CFG, 4, 1, 0),
SOC_DAPM_SINGLE("IN3_L switch", LMICPGA_PIN_CFG, 2, 1, 0),
};
static const struct snd_kcontrol_new right_input_mixer_controls[] = {
SOC_DAPM_SINGLE("IN1_R switch", RMICPGA_PIN_CFG, 6, 1, 0),
SOC_DAPM_SINGLE("IN2_R switch", RMICPGA_PIN_CFG, 4, 1, 0),
SOC_DAPM_SINGLE("IN3_R switch", RMICPGA_PIN_CFG, 2, 1, 0),
};
static const struct snd_soc_dapm_widget aic32x4_dapm_widgets[] = {
/* Left DAC to Left Outputs */
/* dapm widget (stream domain) for left DAC */
SND_SOC_DAPM_DAC("Left DAC", "Left Playback", DAC_CHN_REG, 7, 0),
/* dapm widget (path domain) for left DAC_L Mixer */
SND_SOC_DAPM_MIXER("HPL Output Mixer", SND_SOC_NOPM, 0, 0,
&hpl_output_mixer_controls[0],
ARRAY_SIZE(hpl_output_mixer_controls)),
SND_SOC_DAPM_PGA("HPL Power", OUT_PWR_CTL, 5, 0, NULL, 0),
SND_SOC_DAPM_MIXER("LOL Output Mixer", SND_SOC_NOPM, 0, 0,
&lol_output_mixer_controls[0],
ARRAY_SIZE(lol_output_mixer_controls)),
SND_SOC_DAPM_PGA("LOL Power", OUT_PWR_CTL, 3, 0, NULL, 0),
/* Right DAC to Right Outputs */
/* dapm widget (stream domain) for right DAC */
SND_SOC_DAPM_DAC("Right DAC", "Right Playback", DAC_CHN_REG, 6, 0),
/* dapm widget (path domain) for right DAC_R mixer */
SND_SOC_DAPM_MIXER("HPR Output Mixer", SND_SOC_NOPM, 0, 0,
&hpr_output_mixer_controls[0],
ARRAY_SIZE(hpr_output_mixer_controls)),
SND_SOC_DAPM_PGA("HPR Power", OUT_PWR_CTL, 4, 0, NULL, 0),
SND_SOC_DAPM_MIXER("LOR Output Mixer", SND_SOC_NOPM, 0, 0,
&lor_output_mixer_controls[0],
ARRAY_SIZE(lor_output_mixer_controls)),
SND_SOC_DAPM_PGA("LOR Power", OUT_PWR_CTL, 2, 0, NULL, 0),
SND_SOC_DAPM_MIXER("Left Input Mixer", SND_SOC_NOPM, 0, 0,
&left_input_mixer_controls[0],
ARRAY_SIZE(left_input_mixer_controls)),
SND_SOC_DAPM_MIXER("Right Input Mixer", SND_SOC_NOPM, 0, 0,
&right_input_mixer_controls[0],
ARRAY_SIZE(right_input_mixer_controls)),
/* Left Inputs to Left ADC */
SND_SOC_DAPM_ADC("Left ADC", "Left Capture", ADC_REG_1, 7, 0),
/* Right Inputs to Right ADC */
SND_SOC_DAPM_ADC("Right ADC", "Right Capture", ADC_REG_1, 6, 0),
/* dapm widget (platform domain) name for HPLOUT */
SND_SOC_DAPM_OUTPUT("HPL"),
/* dapm widget (platform domain) name for HPROUT */
SND_SOC_DAPM_OUTPUT("HPR"),
/* dapm widget (platform domain) name for LOLOUT */
SND_SOC_DAPM_OUTPUT("LOL"),
/* dapm widget (platform domain) name for LOROUT */
SND_SOC_DAPM_OUTPUT("LOR"),
/* dapm widget (platform domain) name for LINE1L */
SND_SOC_DAPM_INPUT("IN1_L"),
/* dapm widget (platform domain) name for LINE1R */
SND_SOC_DAPM_INPUT("IN1_R"),
/* dapm widget (platform domain) name for LINE2L */
SND_SOC_DAPM_INPUT("IN2_L"),
/* dapm widget (platform domain) name for LINE2R */
SND_SOC_DAPM_INPUT("IN2_R"),
/* dapm widget (platform domain) name for LINE3L */
SND_SOC_DAPM_INPUT("IN3_L"),
/* dapm widget (platform domain) name for LINE3R */
SND_SOC_DAPM_INPUT("IN3_R"),
};
static const struct snd_soc_dapm_route aic32x4_dapm_routes[] = {
/* ******** Left Output ******** */
{"HPL Output Mixer", "L_DAC switch", "Left DAC"},
{"HPL Output Mixer", "IN1_L switch", "IN1_L"},
//{"HPL Output Mixer", "Left_Bypass switch", "Left_Bypass"},
{"HPL Power", NULL, "HPL Output Mixer"},
{"HPL", NULL, "HPL Power"},
{"LOL Output Mixer", "L_DAC switch", "Left DAC"},
// {"LOL Output Mixer", "Left_Bypass switch", "Left_Bypass"},
{"LOL Power", NULL, "LOL Output Mixer"},
{"LOL", NULL, "LOL Power"},
/* ******** Right Output ******** */
{"HPR Output Mixer", "R_DAC switch", "Right DAC"},
{"HPR Output Mixer", "IN1_R switch", "IN1_R"},
//{"HPR Output Mixer", "Right_Bypass switch", "Right_Bypass"},
{"HPR Power", NULL, "HPR Output Mixer"},
{"HPR", NULL, "HPR Power"},
{"LOR Output Mixer", "R_DAC switch", "Right DAC"},
// {"LOR Output Mixer", "Right_Bypass switch", "Right_Bypass"},
{"LOR Power", NULL, "LOR Output Mixer"},
{"LOR", NULL, "LOR Power"},
/* ******** Left input ******** */
{"Left Input Mixer", "IN1_L switch", "IN1_L"},
{"Left Input Mixer", "IN2_L switch", "IN2_L"},
{"Left Input Mixer", "IN3_L switch", "IN3_L"},
//{"Left_Bypass", NULL, "Left Input Mixer"},
{"Left ADC", NULL, "Left Input Mixer"},
/* ******** Right Input ******** */
{"Right Input Mixer", "IN1_R switch", "IN1_R"},
{"Right Input Mixer", "IN2_R switch", "IN2_R"},
{"Right Input Mixer", "IN3_R switch", "IN3_R"},
// {"Right_Bypass", NULL, "Right Input Mixer"},
{"Right ADC", NULL, "Right Input Mixer"},
};
/*
*****************************************************************************
* Function Definitions
*****************************************************************************
*/
static void aic32x4_change_page(struct snd_soc_codec *codec, u8 new_page)
{
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
snd_soc_write(codec, AIC32X4_PSEL, new_page);
aic32x4->page_no = new_page;
}
static void aic32x4_write(struct snd_soc_codec *codec, u16 reg, u8 val)
{
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
u8 page = reg / 128;
u8 fixed_reg = reg % 128;
printk("%s: reg=%d, val=0x%02x\n", __func__, reg, val);
/* A write to AIC32X4_PSEL is really a non-explicit page change */
if (reg == AIC32X4_PSEL) {
aic32x4_change_page(codec, val);
return;
}
if (aic32x4->page_no != page) {
aic32x4_change_page(codec, page);
}
snd_soc_write(codec, fixed_reg, val);
}
static u8 aic32x4_read(struct snd_soc_codec *codec, u16 reg)
{
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
u8 page = reg / 128;
u8 fixed_reg = reg % 128;
if (aic32x4->page_no != page) {
aic32x4_change_page(codec, page);
}
return snd_soc_read(codec, fixed_reg);
}
static int snd_soc_info_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
int mask = (kcontrol->private_value >> 12) & 0xff;
uinfo->type =
mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 2;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = mask;
return 0;
}
/*
*----------------------------------------------------------------------------
* Function : snd_soc_get_volsw_2r_aic32x4
* Purpose : Callback to get the value of a double mixer control that spans
* two registers.
*
*----------------------------------------------------------------------------
*/
int snd_soc_get_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & AIC32X4_8BITS_MASK;
int reg2 = (kcontrol->private_value >> 24) & AIC32X4_8BITS_MASK;
int mask;
int shift;
unsigned short val, val2;
if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) {
mask = AIC32X4_8BITS_MASK;
shift = 0;
} else if ((!strcmp(kcontrol->id.name, "HP Driver Gain")) ||
(!strcmp(kcontrol->id.name, "LO Driver Gain"))) {
mask = 0x3F;
shift = 0;
} else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) {
mask = 0x7F;
shift = 0;
} else {
printk("Invalid kcontrol name\n");
return -1;
}
val = (snd_soc_read(codec, reg) >> shift) & mask;
val2 = (snd_soc_read(codec, reg2) >> shift) & mask;
if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) {
ucontrol->value.integer.value[0] =
(val <= 48) ? (val + 127) : (val - 129);
ucontrol->value.integer.value[1] =
(val2 <= 48) ? (val2 + 127) : (val2 - 129);
} else if ((!strcmp(kcontrol->id.name, "HP Driver Gain"))
|| (!strcmp(kcontrol->id.name, "LO Driver Gain"))) {
ucontrol->value.integer.value[0] =
(val <= 29) ? (val + 6) : (val - 58);
ucontrol->value.integer.value[1] =
(val2 <= 29) ? (val2 + 6) : (val2 - 58);
} else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) {
ucontrol->value.integer.value[0] =
(val <= 38) ? (val + 25) : (val - 103);
ucontrol->value.integer.value[1] =
(val2 <= 38) ? (val2 + 25) : (val2 - 103);
}
return 0;
}
/*
*----------------------------------------------------------------------------
* Function : snd_soc_put_volsw_2r_aic32x4
* Purpose : Callback to set the value of a double mixer control that spans
* two registers.
*
*----------------------------------------------------------------------------
*/
int snd_soc_put_volsw_2r_aic32x4(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
int reg = kcontrol->private_value & AIC32X4_8BITS_MASK;
int reg2 = (kcontrol->private_value >> 24) & AIC32X4_8BITS_MASK;
int err;
unsigned short val, val2, val_mask;
val = ucontrol->value.integer.value[0];
val2 = ucontrol->value.integer.value[1];
if (!strcmp(kcontrol->id.name, "PCM Playback Volume")) {
val = (val >= 127) ? (val - 127) : (val + 129);
val2 = (val2 >= 127) ? (val2 - 127) : (val2 + 129);
val_mask = AIC32X4_8BITS_MASK; /* 8 bits */
} else if ((!strcmp(kcontrol->id.name, "HP Driver Gain")) ||
(!strcmp(kcontrol->id.name, "LO Driver Gain"))) {
val = (val >= 6) ? (val - 6) : (val + 58);
val2 = (val2 >= 6) ? (val2 - 6) : (val2 + 58);
val_mask = 0x3F; /* 6 bits */
} else if (!strcmp(kcontrol->id.name, "PGA Capture Volume")) {
val = (val >= 25) ? (val - 25) : (val + 103);
val2 = (val2 >= 25) ? (val2 - 25) : (val2 + 103);
val_mask = 0x7F; /* 7 bits */
} else {
printk("Invalid control name\n");
return -1;
}
if ((err = snd_soc_update_bits(codec, reg, val_mask, val)) < 0) {
printk("Error while updating bits\n");
return err;
}
err = snd_soc_update_bits(codec, reg2, val_mask, val2);
return err;
}
/*
*----------------------------------------------------------------------------
* Function : aic32x4_get_divs
* Purpose : This function is to get required divisor from the "aic32x4_divs"
* table.
*
*----------------------------------------------------------------------------
*/
static inline int aic32x4_get_divs(int mclk, int rate)
{
int i;
for (i = 0; i < ARRAY_SIZE(aic32x4_divs); i++) {
if ((aic32x4_divs[i].rate == rate)
&& (aic32x4_divs[i].mclk == mclk)) {
return i;
}
}
printk("Master clock and sample rate is not supported\n");
return -EINVAL;
}
static int aic32x4_add_widgets(struct snd_soc_codec *codec)
{
snd_soc_dapm_new_controls(codec, aic32x4_dapm_widgets,
ARRAY_SIZE(aic32x4_dapm_widgets));
snd_soc_dapm_add_routes(codec, aic32x4_dapm_routes,
ARRAY_SIZE(aic32x4_dapm_routes));
snd_soc_dapm_new_widgets(codec);
return 0;
}
static int aic32x4_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
printk("%s\n", __func__);
switch (freq) {
case AIC32X4_FREQ_12000000:
case AIC32X4_FREQ_24000000:
case AIC32X4_FREQ_25000000:
aic32x4->sysclk = freq;
return 0;
}
printk("Invalid frequency to set DAI system clock\n");
return -EINVAL;
}
static int aic32x4_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
u8 iface_reg;
printk("%s\n", __func__);
iface_reg = aic32x4_read(codec, INTERFACE_SET_REG_1);
iface_reg = iface_reg & ~(3 << 6 | 3 << 2);
/* set master/slave audio interface */
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
aic32x4->master = 1;
iface_reg |= BIT_CLK_MASTER | WORD_CLK_MASTER;
break;
case SND_SOC_DAIFMT_CBS_CFS:
aic32x4->master = 0;
break;
default:
printk("Invalid DAI master/slave interface\n");
return -EINVAL;
}
/* interface format */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
break;
case SND_SOC_DAIFMT_DSP_A:
iface_reg |= (AIC32X4_DSP_MODE << CLK_REG_3_SHIFT);
break;
case SND_SOC_DAIFMT_RIGHT_J:
iface_reg |= (AIC32X4_RIGHT_JUSTIFIED_MODE << CLK_REG_3_SHIFT);
break;
case SND_SOC_DAIFMT_LEFT_J:
iface_reg |= (AIC32X4_LEFT_JUSTIFIED_MODE << CLK_REG_3_SHIFT);
break;
default:
printk("Invalid DAI interface format\n");
return -EINVAL;
}
aic32x4_write(codec, INTERFACE_SET_REG_1, iface_reg);
return 0;
}
static int aic32x4_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params,
struct snd_soc_dai *dai)
{
struct snd_soc_codec *codec = dai->codec;
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
int i, j;
u8 data;
printk("%s\n", __func__);
i = aic32x4_get_divs(aic32x4->sysclk, params_rate(params));
if (i < 0) {
printk("sampling rate not supported\n");
return i;
}
/* We will fix R value to 1 and will make P & J=K.D as varialble */
/* Setting P & R values */
aic32x4_write(codec, CLK_REG_2,
((aic32x4_divs[i].p_val << 4) | 0x01));
/* J value */
aic32x4_write(codec, CLK_REG_3, aic32x4_divs[i].pll_j);
/* MSB & LSB for D value */
aic32x4_write(codec, CLK_REG_4, (aic32x4_divs[i].pll_d >> 8));
aic32x4_write(codec, CLK_REG_5,
(aic32x4_divs[i].pll_d & AIC32X4_8BITS_MASK));
/* NDAC divider value */
aic32x4_write(codec, NDAC_CLK_REG_6, aic32x4_divs[i].ndac);
/* MDAC divider value */
aic32x4_write(codec, MDAC_CLK_REG_7, aic32x4_divs[i].mdac);
/* DOSR MSB & LSB values */
aic32x4_write(codec, DAC_OSR_MSB, aic32x4_divs[i].dosr >> 8);
aic32x4_write(codec, DAC_OSR_LSB,
(aic32x4_divs[i].dosr & AIC32X4_8BITS_MASK));
/* NADC divider value */
aic32x4_write(codec, NADC_CLK_REG_8, aic32x4_divs[i].nadc);
/* MADC divider value */
aic32x4_write(codec, MADC_CLK_REG_9, aic32x4_divs[i].madc);
/* AOSR value */
aic32x4_write(codec, ADC_OSR_REG, aic32x4_divs[i].aosr);
/* BCLK N divider */
aic32x4_write(codec, CLK_REG_11, aic32x4_divs[i].blck_N);
data = aic32x4_read(codec, INTERFACE_SET_REG_1);
data = data & ~(3 << 4);
switch (params_format(params)) {
case SNDRV_PCM_FORMAT_S16_LE:
break;
case SNDRV_PCM_FORMAT_S20_3LE:
data |= (AIC32X4_WORD_LEN_20BITS << DAC_OSR_MSB_SHIFT);
break;
case SNDRV_PCM_FORMAT_S24_LE:
data |= (AIC32X4_WORD_LEN_24BITS << DAC_OSR_MSB_SHIFT);
break;
case SNDRV_PCM_FORMAT_S32_LE:
data |= (AIC32X4_WORD_LEN_32BITS << DAC_OSR_MSB_SHIFT);
break;
}
aic32x4_write(codec, INTERFACE_SET_REG_1, data);
for (j = 0; j < NO_FEATURE_REGS; j++) {
aic32x4_write(codec,
aic32x4_divs[i].codec_specific_regs[j].reg_offset,
aic32x4_divs[i].codec_specific_regs[j].reg_val);
}
return 0;
}
static int aic32x4_mute(struct snd_soc_dai *dai, int mute)
{
struct snd_soc_codec *codec = dai->codec;
u8 dac_reg;
printk("%s\n", __func__);
dac_reg = aic32x4_read(codec, DAC_MUTE_CTRL_REG) & ~MUTE_ON;
if (mute) {
aic32x4_write(codec, DAC_MUTE_CTRL_REG, dac_reg | MUTE_ON);
} else {
aic32x4_write(codec, DAC_MUTE_CTRL_REG, dac_reg);
}
return 0;
}
static int aic32x4_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
struct aic32x4_priv *aic32x4 = snd_soc_codec_get_drvdata(codec);
u8 value;
char *dbgmap[] = {"SND_SOC_BIAS_OFF", "SND_SOC_BIAS_STANDBY",
"SND_SOC_BIAS_PREPARE", "SND_SOC_BIAS_ON"};
printk("%s: %s\n", __func__, dbgmap[level]);
switch (level) {
/* full On */
case SND_SOC_BIAS_ON:
/* all power is driven by DAPM system */
if (aic32x4->master) {
/* Switch on PLL */
value = aic32x4_read(codec, CLK_REG_2);
aic32x4_write(codec, CLK_REG_2, (value | ENABLE_PLL));
/* Switch on NDAC Divider */
value = aic32x4_read(codec, NDAC_CLK_REG_6);
aic32x4_write(codec, NDAC_CLK_REG_6,
value | ENABLE_NDAC);
/* Switch on MDAC Divider */
value = aic32x4_read(codec, MDAC_CLK_REG_7);
aic32x4_write(codec, MDAC_CLK_REG_7,
value | ENABLE_MDAC);
/* Switch on NADC Divider */
value = aic32x4_read(codec, NADC_CLK_REG_8);
aic32x4_write(codec, NADC_CLK_REG_8,
value | ENABLE_MDAC);
/* Switch on MADC Divider */
value = aic32x4_read(codec, MADC_CLK_REG_9);
aic32x4_write(codec, MADC_CLK_REG_9,
value | ENABLE_MDAC);
/* Switch on BCLK_N Divider */
value = aic32x4_read(codec, CLK_REG_11);
aic32x4_write(codec, CLK_REG_11, value | ENABLE_BCLK);
}
break;
/* partial On */
case SND_SOC_BIAS_PREPARE:
break;
/* Off, with power */
case SND_SOC_BIAS_STANDBY:
/*
* all power is driven by DAPM system,
* so output power is safe if bypass was set
*/;
if (aic32x4->master) {
/* Switch off PLL */
value = aic32x4_read(codec, CLK_REG_2);
aic32x4_write(codec, CLK_REG_2, (value & ~ENABLE_PLL));
/* Switch off NDAC Divider */
value = aic32x4_read(codec, NDAC_CLK_REG_6);
aic32x4_write(codec, NDAC_CLK_REG_6,
value & ~ENABLE_NDAC);
/* Switch off MDAC Divider */
value = aic32x4_read(codec, MDAC_CLK_REG_7);
aic32x4_write(codec, MDAC_CLK_REG_7,
value & ~ENABLE_MDAC);
/* Switch off NADC Divider */
value = aic32x4_read(codec, NADC_CLK_REG_8);
aic32x4_write(codec, NADC_CLK_REG_8,
value & ~ENABLE_NDAC);
/* Switch off MADC Divider */
value = aic32x4_read(codec, MADC_CLK_REG_9);
aic32x4_write(codec, MADC_CLK_REG_9,
value & ~ENABLE_MDAC);
value = aic32x4_read(codec, CLK_REG_11);
/* Switch off BCLK_N Divider */
aic32x4_write(codec, CLK_REG_11, value & ~ENABLE_BCLK);
}
break;
/* Off, without power */
case SND_SOC_BIAS_OFF:
/* TODO: implement a real shutdown and use a cache for registers */
break;
}
codec->bias_level = level;
return 0;
}
#define AIC32X4_RATES SNDRV_PCM_RATE_8000_192000
#define AIC32X4_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE \
| SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
static struct snd_soc_dai_ops aic32x4_ops = {
.hw_params = aic32x4_hw_params,
.digital_mute = aic32x4_mute,
.set_fmt = aic32x4_set_dai_fmt,
.set_sysclk = aic32x4_set_dai_sysclk,
};
static struct snd_soc_dai_driver aic32x4_dai = {
.name = "tlv320aic32x4-hifi",
.playback = {
.stream_name = "Playback",
.channels_min = 1,
.channels_max = 2,
.rates = AIC32X4_RATES,
.formats = AIC32X4_FORMATS,},
.capture = {
.stream_name = "Capture",
.channels_min = 1,
.channels_max = 2,
.rates = AIC32X4_RATES,
.formats = AIC32X4_FORMATS,},
.ops = &aic32x4_ops,
.symmetric_rates = 1,
};
static int aic32x4_suspend(struct snd_soc_codec *codec, pm_message_t state)
{
printk("%s\n", __func__);
aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
static int aic32x4_resume(struct snd_soc_codec *codec)
{
printk("%s\n", __func__);
/*
* TODO: Since SND_SOC_BIAS_OFF is not effective there's no need to
* sync with a cache here.
*/
aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
}
static int aic32x4_probe(struct snd_soc_codec *codec)
{
int ret, i;
printk("%s\n", __func__);
ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C);
if (ret < 0) {
dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret);
return ret;
}
// aic32x4_reset(codec);
/* TODO: initial configuration of registers -> remove since this is
machine specific */
for (i=0; i<ARRAY_SIZE(aic32x4_reg_init); i++)
aic32x4_write(codec, aic32x4_reg_init[i].reg_offset,
aic32x4_reg_init[i].reg_val);
aic32x4_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
snd_soc_add_controls(codec, aic32x4_snd_controls,
ARRAY_SIZE(aic32x4_snd_controls));
aic32x4_add_widgets(codec);
return 0;
}
static int aic32x4_remove(struct snd_soc_codec *codec)
{
aic32x4_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
}
static struct snd_soc_codec_driver soc_codec_dev_aic32x4 = {
.probe = aic32x4_probe,
.remove = aic32x4_remove,
.suspend = aic32x4_suspend,
.resume = aic32x4_resume,
.set_bias_level = aic32x4_set_bias_level,
};
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
static __devinit int aic32x4_i2c_probe(struct i2c_client *i2c,
const struct i2c_device_id *id)
{
struct aic32x4_priv *aic32x4;
int ret;
aic32x4 = kzalloc(sizeof(struct aic32x4_priv), GFP_KERNEL);
if (aic32x4 == NULL)
return -ENOMEM;
i2c_set_clientdata(i2c, aic32x4);
ret = snd_soc_register_codec(&i2c->dev,
&soc_codec_dev_aic32x4, &aic32x4_dai, 1);
if (ret < 0)
kfree(aic32x4);
return ret;
}
static __devexit int aic32x4_i2c_remove(struct i2c_client *client)
{
snd_soc_unregister_codec(&client->dev);
kfree(i2c_get_clientdata(client));
return 0;
}
/* TODO: two possible models: tlv320aic3254/tlv320aic3204 (see other
tlv drivers) */
static const struct i2c_device_id aic32x4_i2c_id[] = {
{ "tlv320aic32x4", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, aic32x4_i2c_id);
static struct i2c_driver aic32x4_i2c_driver = {
.driver = {
.name = "tlv320aic32x4-codec",
.owner = THIS_MODULE,
},
.probe = aic32x4_i2c_probe,
.remove = __devexit_p(aic32x4_i2c_remove),
.id_table = aic32x4_i2c_id,
};
#endif
static int __init aic32x4_modinit(void)
{
int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
ret = i2c_add_driver(&aic32x4_i2c_driver);
if (ret != 0) {
printk(KERN_ERR "Failed to register aic32x4 I2C driver: %d\n",
ret);
}
#endif
return ret;
}
module_init(aic32x4_modinit);
static void __exit aic32x4_exit(void)
{
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
i2c_del_driver(&aic32x4_i2c_driver);
#endif
}
module_exit(aic32x4_exit);
MODULE_DESCRIPTION("ASoC tlv320aic32x4 codec driver");
MODULE_AUTHOR("Javier Martin <javier.martin at vista-silicon.com>");
MODULE_LICENSE("GPL");
--
Javier Martin
Vista Silicon S.L.
CDTUC - FASE C - Oficina S-345
Avda de los Castros s/n
39005- Santander. Cantabria. Spain
+34 942 25 32 60
www.vista-silicon.com
More information about the Alsa-devel
mailing list