| /* |
| * linux/sound/soc/codecs/tlv320aic326x_mini-dsp.c |
| * |
| * Copyright (C) 2012 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. |
| * |
| * The TLV320AIC3262 is a flexible, low-power, low-voltage stereo audio |
| * codec with digital microphone inputs and programmable outputs. |
| * |
| * History: |
| * |
| * Rev 0.1 Added the miniDSP Support 01-03-2011 |
| * |
| * Rev 0.2 Updated the code-base for miniDSP switching and |
| * mux control update. 21-03-2011 |
| * |
| * Rev 0.3 Updated the code-base to support Multi-Configuration feature |
| * of PPS GDE |
| */ |
| |
| /* |
| ***************************************************************************** |
| * INCLUDES |
| ***************************************************************************** |
| */ |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/init.h> |
| #include <linux/kernel.h> |
| #include <linux/fs.h> |
| #include <linux/types.h> |
| #include <linux/kdev_t.h> |
| #include <linux/cdev.h> |
| #include <linux/device.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/i2c.h> |
| #include <linux/platform_device.h> |
| #include <sound/soc.h> |
| #include <sound/core.h> |
| #include <sound/soc-dapm.h> |
| #include <sound/control.h> |
| #include <linux/time.h> /* For timing computations */ |
| #include "tlv320aic326x.h" |
| #include "tlv320aic326x_mini-dsp.h" |
| |
| #include "base_main_Rate48_pps_driver.h" |
| #include "second_rate_pps_driver.h" |
| //#include "one_mic_aec_nc_latest.h" |
| #ifdef CONFIG_MINI_DSP |
| |
| #ifdef REG_DUMP_MINIDSP |
| static void aic3262_dump_page(struct i2c_client *i2c, u8 page); |
| #endif |
| |
| /* |
| ***************************************************************************** |
| * LOCAL STATIC DECLARATIONS |
| ***************************************************************************** |
| */ |
| static int m_control_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo); |
| static int m_control_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol); |
| static int m_control_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol); |
| |
| /* |
| ***************************************************************************** |
| * MINIDSP RELATED GLOBALS |
| ***************************************************************************** |
| */ |
| /* The below variable is used to maintain the I2C Transactions |
| * to be carried out during miniDSP switching. |
| */ |
| #if 1 |
| minidsp_parser_data dsp_parse_data[MINIDSP_PARSER_ARRAY_SIZE*2]; |
| |
| struct i2c_msg i2c_transaction[MINIDSP_PARSER_ARRAY_SIZE * 2]; |
| /* Total count of I2C Messages are stored in the i2c_count */ |
| int i2c_count; |
| |
| /* The below array is used to store the burst array for I2C Multibyte |
| * Operations |
| */ |
| minidsp_i2c_page i2c_page_array[MINIDSP_PARSER_ARRAY_SIZE]; |
| int i2c_page_count; |
| #else |
| minidsp_parser_data dsp_parse_data; |
| |
| struct i2c_msg i2c_transaction; |
| /* Total count of I2C Messages are stored in the i2c_count */ |
| int i2c_count; |
| |
| /* The below array is used to store the burst array for I2C Multibyte |
| * Operations |
| */ |
| minidsp_i2c_page i2c_page_array; |
| int i2c_page_count; |
| #endif |
| |
| /* kcontrol structure used to register with ALSA Core layer */ |
| static struct snd_kcontrol_new snd_mux_controls[MAX_MUX_CONTROLS]; |
| |
| /* mode variables */ |
| static int amode; |
| static int dmode; |
| |
| /* k-control macros used for miniDSP related Kcontrols */ |
| #define SOC_SINGLE_VALUE_M(xmax, xinvert) \ |
| ((unsigned long)&(struct soc_mixer_control) \ |
| {.max = xmax, \ |
| .invert = xinvert}) |
| #define SOC_SINGLE_M(xname, max, invert) \ |
| {\ |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
| .info = m_control_info, .get = m_control_get,\ |
| .put = m_control_put, \ |
| .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ |
| .private_value = SOC_SINGLE_VALUE_M(max, invert) } |
| #define SOC_SINGLE_AIC3262_M(xname) \ |
| {\ |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
| .info = m_control_info, .get = m_control_get,\ |
| .put = m_control_put, \ |
| .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \ |
| } |
| |
| /* |
| * aic3262_minidsp_controls |
| * |
| * Contains the list of the Kcontrol macros required for modifying the |
| * miniDSP behavior at run-time. |
| */ |
| static const struct snd_kcontrol_new aic3262_minidsp_controls[] = { |
| SOC_SINGLE_AIC3262_M("Minidsp mode") , |
| SOC_SINGLE_AIC3262_M("ADC Adaptive mode Enable") , |
| SOC_SINGLE_AIC3262_M("DAC Adaptive mode Enable") , |
| SOC_SINGLE_AIC3262_M("Dump Regs Book0") , |
| SOC_SINGLE_AIC3262_M("Verify minidsp program") , |
| }; |
| |
| #ifdef REG_DUMP_MINIDSP |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : aic3262_dump_page |
| * Purpose : Read and display one codec register page, for debugging purpose |
| *---------------------------------------------------------------------------- |
| */ |
| static void aic3262_dump_page(struct i2c_client *i2c, u8 page) |
| { |
| int i; |
| u8 data; |
| u8 test_page_array[256]; |
| |
| aic3262_change_page(codec, page); |
| |
| data = 0x0; |
| |
| i2c_master_send(i2c, data, 1); |
| i2c_master_recv(i2c, test_page_array, 128); |
| |
| DBG("\n------- MINI_DSP PAGE %d DUMP --------\n", page); |
| for (i = 0; i < 128; i++) |
| DBG(KERN_INFO " [ %d ] = 0x%x\n", i, test_page_array[i]); |
| |
| } |
| #endif |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : update_kcontrols |
| * Purpose : Given the miniDSP process flow, this function reads the |
| * corresponding Page Numbers and then performs I2C Read for those |
| * Pages. |
| *---------------------------------------------------------------------------- |
| */ |
| void update_kcontrols(struct snd_soc_codec *codec, int process_flow) |
| { |
| int i, val1, array_size; |
| char **knames; |
| control *cntl; |
| |
| #if 0 |
| if (process_flow == 1) { |
| knames = Second_Rate_MUX_control_names; |
| cntl = Second_Rate_MUX_controls; |
| array_size = ARRAY_SIZE(Second_Rate_MUX_controls); |
| } else { |
| #endif |
| knames = main44_MUX_control_names; |
| cntl = main44_MUX_controls; |
| array_size = ARRAY_SIZE(main44_MUX_controls); |
| // } |
| |
| DBG(KERN_INFO "%s: ARRAY_SIZE = %d\tmode=%d\n", __func__, |
| array_size, process_flow); |
| for (i = 0; i < array_size; i++) { |
| aic3262_change_book(codec, cntl[i].control_book); |
| aic3262_change_page(codec, cntl[i].control_page); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, |
| cntl[i].control_base); |
| snd_mux_controls[i].private_value = 0; |
| } |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : byte_i2c_array_transfer |
| * Purpose : Function used only for debugging purpose. This function will |
| * be used while switching miniDSP Modes register by register. |
| * This needs to be used only during development. |
| *----------------------------------------------------------------------------- |
| */ |
| #if 1 |
| int byte_i2c_array_transfer(struct snd_soc_codec *codec, |
| reg_value *program_ptr, |
| int size) |
| { |
| int j; |
| u8 buf[3]; |
| |
| for (j = 0; j < size; j++) { |
| /* Check if current Reg offset is zero */ |
| if (program_ptr[j].reg_off == 0) { |
| /* Check for the Book Change Request */ |
| if ((j < (size - 1)) && |
| (program_ptr[j+1].reg_off == 127)) { |
| aic3262_change_book(codec, |
| program_ptr[j+1].reg_val); |
| /* Increment for loop counter across Book Change */ |
| j++; |
| continue; |
| } |
| /* Check for the Page Change Request in Current book */ |
| aic3262_change_page(codec, program_ptr[j].reg_val); |
| continue; |
| } |
| |
| buf[AIC3262_REG_OFFSET_INDEX] = program_ptr[j].reg_off % 128; |
| buf[AIC3262_REG_DATA_INDEX] = |
| program_ptr[j].reg_val & AIC3262_8BITS_MASK; |
| |
| if (codec->hw_write(codec->control_data, buf, 2) != 2) { |
| printk(KERN_ERR "Error in i2c write\n"); |
| return -EIO; |
| } |
| } |
| aic3262_change_book(codec, 0); |
| return 0; |
| } |
| |
| #else |
| int byte_i2c_array_transfer(struct snd_soc_codec *codec, |
| reg_value *program_ptr, |
| int size) |
| { |
| int j; |
| u8 buf[3]; |
| printk(KERN_INFO "%s: started with array size %d\n", __func__, size); |
| for (j = 0; j < size; j++) { |
| /* Check if current Reg offset is zero */ |
| buf[AIC3262_REG_OFFSET_INDEX] = program_ptr[j].reg_off % 128; |
| buf[AIC3262_REG_DATA_INDEX] = |
| program_ptr[j].reg_val & AIC3262_8BITS_MASK; |
| |
| if (codec->hw_write(codec->control_data, buf, 2) != 2) { |
| printk(KERN_ERR "Error in i2c write\n"); |
| return -EIO; |
| } |
| } |
| printk(KERN_INFO "%s: ended\n", __func__); |
| return 0; |
| } |
| #endif |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : byte_i2c_array_read |
| * Purpose : This function is used to perform Byte I2C Read. This is used |
| * only for debugging purposes to read back the Codec Page |
| * Registers after miniDSP Configuration. |
| *---------------------------------------------------------------------------- |
| */ |
| int byte_i2c_array_read(struct snd_soc_codec *codec, |
| reg_value *program_ptr, int size) |
| { |
| int j; |
| u8 val1; |
| u8 cur_page = 0; |
| u8 cur_book = 0; |
| for (j = 0; j < size; j++) { |
| /* Check if current Reg offset is zero */ |
| if (program_ptr[j].reg_off == 0) { |
| /* Check for the Book Change Request */ |
| if ((j < (size - 1)) && |
| (program_ptr[j+1].reg_off == 127)) { |
| aic3262_change_book(codec, |
| program_ptr[j+1].reg_val); |
| cur_book = program_ptr[j+1].reg_val; |
| /* Increment for loop counter across Book Change */ |
| j++; |
| continue; |
| } |
| /* Check for the Page Change Request in Current book */ |
| aic3262_change_page(codec, program_ptr[j].reg_val); |
| cur_page = program_ptr[j].reg_val; |
| continue; |
| } |
| |
| val1 = i2c_smbus_read_byte_data(codec->control_data, |
| program_ptr[j].reg_off); |
| if (val1 < 0) |
| printk(KERN_ERR "Error in smbus read\n"); |
| |
| if(val1 != program_ptr[j].reg_val) |
| /*printk(KERN_INFO "mismatch [%d][%d][%d] = %x %x\n", |
| cur_book, cur_page, program_ptr[j].reg_off, val1, program_ptr[j].reg_val);*/ |
| DBG(KERN_INFO "[%d][%d][%d]= %x\n", |
| cur_book, cur_page, program_ptr[j].reg_off, val1); |
| } |
| aic3262_change_book(codec, 0); |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : minidsp_get_burst |
| * Purpose : Format one I2C burst for transfer from mini dsp program array. |
| * This function will parse the program array and get next burst |
| * data for doing an I2C bulk transfer. |
| *---------------------------------------------------------------------------- |
| */ |
| static void |
| minidsp_get_burst(reg_value *program_ptr, |
| int program_size, |
| minidsp_parser_data *parse_data) |
| { |
| int index = parse_data->current_loc; |
| int burst_write_count = 0; |
| |
| /*DBG("GET_BURST: start\n");*/ |
| /* check if first location is page register, and populate page addr */ |
| if (program_ptr[index].reg_off == 0) { |
| if ((index < (program_size - 1)) && |
| (program_ptr[index+1].reg_off == 127)) { |
| parse_data->book_change = 1; |
| parse_data->book_no = program_ptr[index+1].reg_val; |
| index += 2; |
| goto finish_out; |
| |
| } |
| parse_data->page_num = program_ptr[index].reg_val; |
| parse_data->burst_array[burst_write_count++] = |
| program_ptr[index].reg_off; |
| parse_data->burst_array[burst_write_count++] = |
| program_ptr[index].reg_val; |
| index++; |
| goto finish_out; |
| } |
| |
| parse_data->burst_array[burst_write_count++] = |
| program_ptr[index].reg_off; |
| parse_data->burst_array[burst_write_count++] = |
| program_ptr[index].reg_val; |
| index++; |
| |
| for (; index < program_size; index++) { |
| if (program_ptr[index].reg_off != |
| (program_ptr[index - 1].reg_off + 1)) |
| break; |
| else |
| parse_data->burst_array[burst_write_count++] = |
| program_ptr[index].reg_val; |
| |
| } |
| finish_out: |
| parse_data->burst_size = burst_write_count; |
| if (index == program_size) |
| /* parsing completed */ |
| parse_data->current_loc = MINIDSP_PARSING_END; |
| else |
| parse_data->current_loc = index; |
| /*DBG("GET_BURST: end\n");*/ |
| } |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : minidsp_i2c_multibyte_transfer |
| * Purpose : Function used to perform multi-byte I2C Writes. Used to configure |
| * the miniDSP Pages. |
| *---------------------------------------------------------------------------- |
| */ |
| #if 1 |
| int |
| minidsp_i2c_multibyte_transfer(struct snd_soc_codec *codec, |
| reg_value *program_ptr, |
| int program_size) |
| { |
| struct i2c_client *client = codec->control_data; |
| |
| minidsp_parser_data parse_data; |
| int count = 0; |
| |
| #ifdef DEBUG_MINIDSP_LOADING |
| int i = 0, j = 0; |
| #endif |
| /* point the current location to start of program array */ |
| parse_data.current_loc = 0; |
| parse_data.page_num = 0; |
| parse_data.book_change = 0; |
| parse_data.book_no = 0; |
| |
| DBG(KERN_INFO "size is : %d", program_size); |
| do { |
| do { |
| /* Get first burst data */ |
| minidsp_get_burst(program_ptr, program_size, |
| &parse_data); |
| if (parse_data.book_change == 1) |
| break; |
| dsp_parse_data[count] = parse_data; |
| |
| i2c_transaction[count].addr = client->addr; |
| i2c_transaction[count].flags = |
| client->flags & I2C_M_TEN; |
| i2c_transaction[count].len = |
| dsp_parse_data[count].burst_size; |
| i2c_transaction[count].buf = |
| dsp_parse_data[count].burst_array; |
| |
| #ifdef DEBUG_MINIDSP_LOADING |
| DBG(KERN_INFO |
| "i: %d\taddr: %d\tflags: %d\tlen: %d\tbuf:", |
| i, client->addr, client->flags & I2C_M_TEN, |
| dsp_parse_data[count].burst_size); |
| |
| for (j = 0; j <= dsp_parse_data[count].burst_size; j++) |
| DBG(KERN_INFO "%x ", |
| dsp_parse_data[i].burst_array[j]); |
| |
| DBG(KERN_INFO "\n\n"); |
| i++; |
| #endif |
| |
| count++; |
| /* Proceed to the next burst reg_addr_incruence */ |
| } while (parse_data.current_loc != MINIDSP_PARSING_END); |
| |
| if (count > 0) { |
| if (i2c_transfer(client->adapter, |
| i2c_transaction, count) != count) { |
| printk(KERN_ERR "Write burst i2c data error!\n"); |
| } |
| } |
| if (parse_data.book_change == 1) { |
| aic3262_change_book(codec, parse_data.book_no); |
| parse_data.book_change = 0; |
| } |
| } while (parse_data.current_loc != MINIDSP_PARSING_END); |
| aic3262_change_book(codec, 0); |
| return 0; |
| } |
| #else |
| int |
| minidsp_i2c_multibyte_transfer(struct snd_soc_codec *codec, |
| reg_value *program_ptr, |
| int program_size) |
| { |
| struct i2c_client *client = codec->control_data; |
| |
| minidsp_parser_data parse_data; |
| int count = 1; |
| |
| #ifdef DEBUG_MINIDSP_LOADING |
| int i = 0, j = 0; |
| #endif |
| /* point the current location to start of program array */ |
| parse_data.current_loc = 0; |
| parse_data.page_num = 0; |
| parse_data.book_change = 0; |
| parse_data.book_no = 0; |
| |
| DBG(KERN_INFO "size is : %d", program_size); |
| |
| do { |
| /* Get first burst data */ |
| minidsp_get_burst(program_ptr, program_size, |
| &parse_data); |
| |
| dsp_parse_data = parse_data; |
| |
| i2c_transaction.addr = client->addr; |
| i2c_transaction.flags = |
| client->flags & I2C_M_TEN; |
| i2c_transaction.len = |
| dsp_parse_data.burst_size; |
| i2c_transaction.buf = |
| dsp_parse_data.burst_array; |
| |
| #ifdef DEBUG_MINIDSP_LOADING |
| DBG(KERN_INFO |
| "i: %d\taddr: %d\tflags: %d\tlen: %d\tbuf:", |
| i, client->addr, client->flags & I2C_M_TEN, |
| dsp_parse_data.burst_size); |
| |
| for (j = 0; j <= dsp_parse_data.burst_size; j++) |
| printk( "%x ", |
| dsp_parse_data.burst_array[j]); |
| |
| DBG(KERN_INFO "\n\n"); |
| i++; |
| #endif |
| |
| if (i2c_transfer(client->adapter, |
| &i2c_transaction, count) != count) { |
| printk(KERN_ERR "Write burst i2c data error!\n"); |
| } |
| if (parse_data.book_change == 1) { |
| aic3262_change_book(codec, parse_data.book_no); |
| parse_data.book_change = 0; |
| } |
| /* Proceed to the next burst reg_addr_incruence */ |
| } while (parse_data.current_loc != MINIDSP_PARSING_END); |
| |
| return 0; |
| } |
| #endif |
| /* |
| * Process_Flow Structure |
| * Structure used to maintain the mapping of each PFW like the miniDSP_A |
| * miniDSP_D array values and sizes. It also contains information about |
| * the patches required for each patch. |
| */ |
| struct process_flow{ |
| int init_size; |
| reg_value *miniDSP_init; |
| int A_size; |
| reg_value *miniDSP_A_values; |
| int D_size; |
| reg_value *miniDSP_D_values; |
| int post_size; |
| reg_value *miniDSP_post; |
| struct minidsp_config { |
| int a_patch_size; |
| reg_value *a_patch; |
| int d_patch_size; |
| reg_value *d_patch; |
| } configs[MAXCONFIG]; |
| |
| } miniDSP_programs[] = { |
| { |
| ARRAY_SIZE(main44_REG_Section_init_program), main44_REG_Section_init_program, |
| ARRAY_SIZE(main44_miniDSP_A_reg_values),main44_miniDSP_A_reg_values, |
| ARRAY_SIZE(main44_miniDSP_D_reg_values),main44_miniDSP_D_reg_values, |
| ARRAY_SIZE(main44_REG_Section_post_program),main44_REG_Section_post_program, |
| { |
| |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| |
| |
| }, |
| }, |
| { |
| ARRAY_SIZE(base_speaker_SRS_REG_init_Section_program),base_speaker_SRS_REG_init_Section_program, |
| ARRAY_SIZE(base_speaker_SRS_miniDSP_A_reg_values),base_speaker_SRS_miniDSP_A_reg_values, |
| ARRAY_SIZE(base_speaker_SRS_miniDSP_D_reg_values),base_speaker_SRS_miniDSP_D_reg_values, |
| ARRAY_SIZE(base_speaker_SRS_REG_post_Section_program),base_speaker_SRS_REG_post_Section_program, |
| |
| { |
| {0, 0, ARRAY_SIZE(SRS_ON_miniDSP_D_reg_values), SRS_ON_miniDSP_D_reg_values}, |
| {0, 0, ARRAY_SIZE(SRS_OFF_miniDSP_D_reg_values),SRS_OFF_miniDSP_D_reg_values}, |
| {0, 0, 0, 0}, |
| {0, 0, 0, 0}, |
| }, |
| }, |
| #if 0 |
| {ARRAY_SIZE(spkr_srs_REG_Section_init_program),spkr_srs_REG_Section_init_program, |
| ARRAY_SIZE(spkr_srs_miniDSP_A_reg_values),spkr_srs_miniDSP_A_reg_values, |
| ARRAY_SIZE(spkr_srs_miniDSP_D_reg_values),spkr_srs_miniDSP_D_reg_values, |
| ARRAY_SIZE(spkr_srs_REG_Section_post_program),spkr_srs_REG_Section_post_program, |
| { |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| { 0, 0, 0, 0}, |
| |
| }, |
| }, |
| #endif |
| }; |
| |
| int |
| set_minidsp_mode(struct snd_soc_codec *codec, int new_mode, int new_config) |
| { |
| struct aic3262_priv *aic326x; |
| struct snd_soc_dapm_context *dapm; |
| struct process_flow * pflows = &miniDSP_programs[new_mode]; |
| u8 pll_pow, ndac_pow, mdac_pow, nadc_pow; |
| u8 adc_status,dac_status; |
| |
| int (*ptransfer)(struct snd_soc_codec *codec, reg_value *program_ptr, |
| int size); |
| |
| printk("%s:New Switch mode = %d New Config= %d\n", __func__, new_mode,new_config); |
| |
| if (codec == NULL) { |
| printk(KERN_INFO "%s codec is NULL\n", __func__); |
| return 0; |
| } |
| aic326x = snd_soc_codec_get_drvdata(codec); |
| dapm = &codec->dapm; |
| |
| printk(KERN_INFO "%s:New Switch mode = %d New Config= %d\n", __func__, |
| new_mode, new_config); |
| |
| if (new_mode >= ARRAY_SIZE(miniDSP_programs)) |
| return 0; // error condition |
| if (new_config > MAXCONFIG) |
| return 0; |
| #ifndef MULTIBYTE_I2C |
| ptransfer = byte_i2c_array_transfer; |
| #else |
| ptransfer = minidsp_i2c_multibyte_transfer; |
| #endif |
| if (new_mode != aic326x->process_flow) { |
| |
| printk("== From PFW %d to PFW %d==\n", aic326x->process_flow , new_mode); |
| |
| /* Change to book 0 page 0 and turn off the DAC and snd_soc_dapm_disable_piADC, |
| * while turning them down, poll for the power down completion. |
| */ |
| aic3262_change_page(codec, 0); |
| aic3262_change_book(codec, 0); |
| |
| #if 0 |
| reg63 = aic3262_read(codec, PASI_DAC_DP_SETUP); |
| aic3262_write(codec, PASI_DAC_DP_SETUP, (reg63 & ~0xC0));/*dac power down*/ |
| mdelay (5); |
| counter = 0; |
| reg = DAC_FLAG_R1; |
| |
| dac_status = aic3262_read(codec, reg); |
| |
| do { |
| dac_status = snd_soc_read(codec, reg); |
| counter++;struct snd_soc_dapm_context *dapm |
| mdelay(5); |
| } while ((counter < 200) && ((dac_status & 0x88) == 1)); |
| printk (KERN_INFO "#%s: Polled Register %d Bits set 0x%X counter %d\n", |
| __func__, reg, dac_status, counter);snd_soc_dapm_disable_pi |
| struct snd_soc_dapm_context *dapm |
| reg81= aic3262_read(codec, ADC_CHANNEL_POW); |
| aic3262_write(codec, ADC_CHANNEL_POW, (reg81 & ~0xC0));/*adc power down*/ |
| mdelay (5); |
| |
| adc_status=aic3262_read(codec,ADC_FLAG_R1); |
| counter = 0; |
| reg = ADC_FLAG_R1; |
| do { |
| adc_status = snd_soc_read(codec, reg); |
| counter++; |
| mdelay(5); |
| } while ((counter < 200) && ((adc_status & 0x44) == 1)); |
| |
| printk (KERN_INFO "#%s: Polled Register %d Bits set 0x%X counter %d\n", |
| __func__, reg, adc_status, counter); |
| |
| dac_status = snd_soc_read(codec, DAC_FLAG_R1); |
| adc_status = snd_soc_read (codec, ADC_FLAG_R1); |
| |
| printk (KERN_INFO "#%s: Initial DAC_STATUS 0x%x ADC_STATUS 0x%X\n", |
| __func__, dac_status, adc_status); |
| |
| #endif |
| /* Instead of hard-coding the switching off DAC and ADC, we will use the DAPM |
| * to switch off the Playback Paths and the ADC |
| */ |
| snd_soc_dapm_disable_pin( dapm, "Headphone Jack"); |
| snd_soc_dapm_disable_pin( dapm, "EarPiece"); |
| snd_soc_dapm_disable_pin( dapm, "Int Spk"); |
| snd_soc_dapm_disable_pin( dapm, "SPK out"); |
| snd_soc_dapm_disable_pin( dapm, "Line Out"); |
| |
| snd_soc_dapm_disable_pin( dapm, "Mic Jack"); |
| snd_soc_dapm_disable_pin( dapm, "Linein"); |
| snd_soc_dapm_disable_pin( dapm, "Int Mic"); |
| |
| //snd_soc_dapm_disable_pin (codec, "Left DAC"); |
| //snd_soc_dapm_disable_pin (codec, "Right DAC"); |
| //snd_soc_dapm_disable_pin (codec, "Left ADC"); |
| //snd_soc_dapm_disable_pin (codec, "Right ADC"); |
| snd_soc_dapm_sync(dapm); |
| mdelay(10); |
| |
| mdac_pow = aic3262_read(codec, MDAC_DIV_POW_REG); |
| aic3262_write(codec, MDAC_DIV_POW_REG, (mdac_pow & ~0x80));/*mdac power down*/ |
| mdelay(5); |
| nadc_pow = aic3262_read(codec, MADC_DIV_POW_REG); |
| aic3262_write(codec, MADC_DIV_POW_REG, (nadc_pow & ~0x80));/*madc power down*/ |
| mdelay(5); |
| pll_pow = aic3262_read(codec, PLL_PR_POW_REG); |
| aic3262_write(codec, PLL_PR_POW_REG, (pll_pow & ~0x80));/*pll power down*/ |
| mdelay(5); |
| ndac_pow = aic3262_read(codec, NDAC_DIV_POW_REG); |
| aic3262_write(codec, NDAC_DIV_POW_REG, (ndac_pow & ~0x80)); /*ndac power down*/ |
| mdelay(5); |
| |
| dac_status = snd_soc_read(codec, DAC_FLAG_R1); |
| adc_status = snd_soc_read (codec, ADC_FLAG_R1); |
| |
| printk (KERN_INFO "#%s: Before Switching DAC_STATUS 0x%x ADC_STATUS 0x%X\n", |
| __func__, dac_status, adc_status); |
| |
| mdelay (10); |
| ptransfer(codec, pflows->miniDSP_init, pflows->init_size); |
| ptransfer(codec, pflows->miniDSP_A_values, pflows->A_size); |
| ptransfer(codec, pflows->miniDSP_D_values, pflows->D_size); |
| ptransfer(codec, pflows->miniDSP_post, pflows->post_size); |
| |
| |
| aic326x->process_flow = new_mode; |
| |
| aic3262_change_page(codec, 0); |
| aic3262_change_book(codec, 0); |
| #if 0 |
| |
| /* After the miniDSP Programming is completed, power up the DAC and ADC |
| * and poll for its power up operation. |
| */ |
| |
| aic3262_write(codec, PASI_DAC_DP_SETUP, reg63);/*reverting the old DAC values */ |
| mdelay(5); |
| |
| /* Poll for DAC Power-up first */ |
| /* For DAC Power-up and Power-down event, we will poll for |
| * Book0 Page0 Register 37 |
| */ |
| reg = DAC_FLAG_R1; |
| counter = 0; |
| do { |
| dac_status = snd_soc_read(codec, reg); |
| counter++; |
| mdelay(5); |
| } while ((counter < 200) && ((dac_status & 0x88) == 0)); |
| |
| printk (KERN_INFO "#%s: Polled Register %d Bits set 0x%X counter %d\n", |
| __func__, reg, dac_status, counter); |
| |
| aic3262_write(codec, ADC_CHANNEL_POW, reg81);/*reverting the old ADC values*/ |
| mdelay (5); |
| /* For ADC Power-up and Power-down event, we will poll for |
| * Book0 Page0 Register 36 |
| */ |
| reg = ADC_FLAG_R1; |
| counter = 0; |
| do { |
| adc_status = snd_soc_read(codec, reg); |
| counter++; |
| mdelay(5); |
| } while ((counter < 200) && ((adc_status & 0x44) == 0)); |
| |
| printk (KERN_INFO "#%s: Polled Register %d Bits set 0x%X counter %d\n", |
| __func__, reg, adc_status, counter); |
| aic3262_write(codec, PLL_PR_POW_REG, pll_pow);/*reverting the old pll values*/ |
| mdelay(10); |
| |
| aic3262_write(codec, MDAC_DIV_POW_REG, mdac_pow);/*reverting the old mdac values*/ |
| mdelay(5); |
| aic3262_write(codec, MADC_DIV_POW_REG, madc_pow);/*reverting the old madc values*/ |
| mdelay(5); |
| aic3262_write(codec, NDAC_DIV_POW_REG, ndac_pow);/*reverting the old ndac values*/ |
| mdelay(5); |
| |
| /*if (new_config == 0) { |
| aic326x->current_config = 0; |
| return 0; |
| } |
| aic326x->current_config = -1;*/ |
| |
| //aic3262_change_book(codec, 0); |
| //aic3262_change_page(codec, 0); |
| #endif |
| } |
| |
| #ifdef MULTICONFIG_SUPPORT |
| if (new_config < 0 ) |
| return 0; // No configs supported in this pfw |
| if (new_config == aic326x->current_config) |
| return 0; |
| if (pflows->configs[new_config].a_patch_size || pflows->configs[new_config].d_patch_size) |
| minidsp_multiconfig(codec, |
| pflows->configs[new_config].a_patch, pflows->configs[new_config].a_patch_size, |
| pflows->configs[new_config].d_patch, pflows->configs[new_config].d_patch_size); |
| #endif |
| |
| aic326x->current_config = new_config; |
| aic3262_change_book( codec, 0); |
| |
| DBG(KERN_INFO "%s: switch mode finished\n", __func__); |
| return 0; |
| } |
| |
| /* |
| * i2c_verify |
| * |
| * Function used to validate the contents written into the miniDSP |
| * pages after miniDSP Configuration. |
| */ |
| int i2c_verify(struct snd_soc_codec *codec) |
| { |
| |
| DBG(KERN_INFO "#%s: Invoked.. Resetting to page 0\n", __func__); |
| |
| aic3262_change_book(codec, 0); |
| DBG(KERN_INFO "#Reading reg_section_init_program\n"); |
| |
| byte_i2c_array_read(codec, main44_REG_Section_init_program, |
| ARRAY_SIZE(main44_REG_Section_init_program)); |
| |
| DBG(KERN_INFO "#Reading minidsp_A_reg_values\n"); |
| byte_i2c_array_read(codec, main44_miniDSP_A_reg_values, |
| (main44_miniDSP_A_reg_values_COEFF_SIZE + |
| main44_miniDSP_A_reg_values_INST_SIZE)); |
| |
| DBG(KERN_INFO "#Reading minidsp_D_reg_values\n"); |
| byte_i2c_array_read(codec, main44_miniDSP_D_reg_values, |
| (main44_miniDSP_D_reg_values_COEFF_SIZE + |
| main44_miniDSP_D_reg_values_INST_SIZE)); |
| |
| DBG(KERN_INFO "#Reading reg_section_post_program\n"); |
| byte_i2c_array_read(codec, main44_REG_Section_post_program, |
| ARRAY_SIZE(main44_REG_Section_post_program)); |
| |
| aic3262_change_book(codec, 0); |
| |
| DBG(KERN_INFO "i2c_verify completed\n"); |
| return 0; |
| } |
| |
| |
| int change_codec_power_status(struct snd_soc_codec * codec, int off_restore, int power_mask) |
| { |
| int minidsp_power_mask; |
| u8 dac_status; |
| u8 adc_status; |
| |
| minidsp_power_mask = 0; |
| |
| aic3262_change_page (codec, 0); |
| aic3262_change_book (codec, 0); |
| |
| |
| switch (off_restore) { |
| |
| case 0: /* Power-off the Codec */ |
| dac_status = snd_soc_read (codec, DAC_FLAG_R1); |
| |
| if(dac_status & 0x88) { |
| minidsp_power_mask |= 0x1; |
| snd_soc_update_bits(codec, PASI_DAC_DP_SETUP, 0xC0, 0x0); |
| |
| poll_dac(codec, 0x0, 0x0); |
| poll_dac(codec, 0x1, 0x0); |
| } |
| |
| adc_status = snd_soc_read (codec, ADC_FLAG_R1); |
| |
| if(adc_status & 0x44) { |
| minidsp_power_mask |= 0x2; |
| snd_soc_update_bits(codec, ADC_CHANNEL_POW, 0xC0, 0x0); |
| |
| poll_adc(codec, 0x0, 0x0); |
| poll_adc(codec, 0x1, 0x0); |
| } |
| break; |
| case 1: /* For Restoring Codec to Previous Power State */ |
| |
| if(power_mask & 0x1) { |
| |
| snd_soc_update_bits(codec, PASI_DAC_DP_SETUP, 0xC0, 0xC0); |
| |
| poll_dac(codec, 0x0, 0x1); |
| poll_dac(codec, 0x1, 0x1); |
| } |
| |
| if(power_mask & 0x2) { |
| |
| snd_soc_update_bits(codec, ADC_CHANNEL_POW, 0xC0, 0xC0); |
| |
| poll_adc(codec, 0x0, 0x1); |
| poll_adc(codec, 0x1, 0x1); |
| } |
| break; |
| default: |
| printk(KERN_ERR "#%s: Unknown Power State Requested..\n", |
| __func__); |
| } |
| |
| return minidsp_power_mask; |
| |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : boot_minidsp |
| * Purpose : for laoding the default minidsp mode for the first time . |
| *---------------------------------------------------------------------------- |
| */ |
| int |
| boot_minidsp(struct snd_soc_codec *codec, int new_mode) |
| { |
| struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); |
| struct process_flow * pflows = &miniDSP_programs[new_mode]; |
| int minidsp_stat; |
| |
| int (*ptransfer)(struct snd_soc_codec *codec, |
| reg_value *program_ptr, |
| int size); |
| |
| DBG("%s: switch mode start\n", __func__); |
| if (new_mode >= ARRAY_SIZE(miniDSP_programs)) |
| return 0; // error condition |
| if (new_mode == aic326x->process_flow) |
| return 0; |
| |
| |
| #ifndef MULTIBYTE_I2C |
| ptransfer = byte_i2c_array_transfer; |
| #else |
| ptransfer = minidsp_i2c_multibyte_transfer; |
| #endif |
| |
| minidsp_stat = change_codec_power_status (codec, 0x0, 0x3); |
| |
| ptransfer(codec, pflows->miniDSP_init, pflows->init_size); |
| ptransfer(codec, pflows->miniDSP_A_values, pflows->A_size); |
| ptransfer(codec, pflows->miniDSP_D_values, pflows->D_size); |
| ptransfer(codec, pflows->miniDSP_post, pflows->post_size); |
| |
| aic326x->process_flow = new_mode; |
| |
| change_codec_power_status(codec, 1, minidsp_stat); |
| |
| aic3262_change_page( codec,0); |
| aic3262_change_book( codec,0); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : aic3262_minidsp_program |
| * Purpose : Program mini dsp for AIC3262 codec chip. This routine is |
| * called from the aic3262 codec driver, if mini dsp programming |
| * is enabled. |
| *---------------------------------------------------------------------------- |
| */ |
| int aic3262_minidsp_program(struct snd_soc_codec *codec) |
| { |
| struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); |
| DBG(KERN_INFO "#AIC3262: programming mini dsp\n"); |
| |
| #if defined(PROGRAM_MINI_DSP_first) |
| #ifdef DEBUG |
| DBG("#Verifying book 0\n"); |
| i2c_verify_book0(codec); |
| #endif |
| aic3262_change_book(codec, 0); |
| boot_minidsp(codec, 1); |
| aic326x->process_flow = 0; |
| aic3262_change_book(codec, 0); |
| #ifdef DEBUG |
| DBG("#verifying book 0\n"); |
| i2c_verify_book0(codec); |
| #endif |
| #endif |
| #if defined(PROGRAM_MINI_DSP_second) |
| #ifdef DEBUG |
| DBG("#Verifying book 0\n"); |
| aic3262_change_book(codec, 0); |
| #endif |
| boot_minidsp(codec, 0); |
| aic326x->process_flow = 1; |
| #ifdef DEBUG |
| DBG("#verifying book 0\n"); |
| aic3262_change_book(codec, 0); |
| #endif |
| #endif |
| return 0; |
| } |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : m_control_info |
| * Purpose : This function is to initialize data for new control required to |
| * program the AIC3262 registers. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int m_control_info(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| uinfo->count = 1; |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->value.integer.min = 0; |
| uinfo->value.integer.max = 1; |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : m_control_get |
| * Purpose : This function is to read data of new control for |
| * program the AIC3262 registers. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int m_control_get(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); |
| u32 val; |
| u8 val1; |
| |
| if (!strcmp(kcontrol->id.name, "Minidsp mode")) { |
| val = aic3262->process_flow; |
| ucontrol->value.integer.value[0] = val; |
| DBG(KERN_INFO "control get : mode=%d\n", aic3262->process_flow); |
| } |
| if (!strcmp(kcontrol->id.name, "DAC Adaptive mode Enable")) { |
| aic3262_change_book(codec, 80); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, 1); |
| ucontrol->value.integer.value[0] = ((val1>>1)&0x01); |
| DBG(KERN_INFO "control get : mode=%d\n", aic3262->process_flow); |
| aic3262_change_book(codec,0); |
| } |
| if (!strcmp(kcontrol->id.name, "ADC Adaptive mode Enable")) { |
| aic3262_change_book(codec, 40); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, 1); |
| ucontrol->value.integer.value[0] = ((val1>>1)&0x01); |
| DBG(KERN_INFO "control get : mode=%d\n", dmode); |
| aic3262_change_book(codec,0); |
| } |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : m_new_control_put |
| * Purpose : new_control_put is called to pass data from user/application to |
| * the driver. |
| * |
| *---------------------------------------------------------------------------- |
| */ |
| static int m_control_put(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); |
| |
| u32 val; |
| u8 val1; |
| int mode = aic3262->process_flow; |
| |
| DBG("n_control_put\n"); |
| val = ucontrol->value.integer.value[0]; |
| if (!strcmp(kcontrol->id.name, "Minidsp mode")) { |
| DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", |
| aic3262->process_flow, val); |
| if (val != mode) { |
| if (aic3262->mute_codec == 1) { |
| i2c_verify_book0(codec); |
| aic3262_change_book(codec, 0); |
| boot_minidsp(codec, val); |
| |
| aic3262_change_book(codec, 0); |
| i2c_verify_book0(codec); |
| /* update_kcontrols(codec, val);*/ |
| } else { |
| printk(KERN_ERR |
| " Cant Switch Processflows, Playback in progress"); |
| } |
| } |
| } |
| |
| if (!strcmp(kcontrol->id.name, "DAC Adaptive mode Enable")) { |
| DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", |
| aic3262->process_flow, val); |
| if (val != amode) { |
| aic3262_change_book(codec, 80); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, 1); |
| aic3262_write(codec, 1, (val1&0xfb)|(val<<1)); |
| aic3262_change_book(codec,0); |
| } |
| amode = val; |
| } |
| |
| if (!strcmp(kcontrol->id.name, "ADC Adaptive mode Enable")) { |
| DBG(KERN_INFO "\nMini dsp put\n mode = %d, val=%d\n", |
| aic3262->process_flow, val); |
| if (val != dmode) { |
| aic3262_change_book(codec, 40); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, 1); |
| aic3262_write(codec, 1, (val1&0xfb)|(val<<1)); |
| aic3262_change_book(codec,0); |
| } |
| dmode = val; |
| } |
| |
| if (!strcmp(kcontrol->id.name, "Dump Regs Book0")) |
| i2c_verify_book0(codec); |
| |
| #if 0 |
| if (!strcmp(kcontrol->id.name, "Verify minidsp program")) { |
| |
| if (mode == 0) { |
| DBG("Current mod=%d\nVerifying minidsp_D_regs", mode); |
| byte_i2c_array_read(codec, main44_miniDSP_D_reg_values, |
| (main44_miniDSP_D_reg_values_COEFF_SIZE + |
| main44_miniDSP_D_reg_values_INST_SIZE)); |
| } else { |
| byte_i2c_array_read(codec, |
| Second_Rate_miniDSP_A_reg_values, |
| (Second_Rate_miniDSP_A_reg_values_COEFF_SIZE + |
| Second_Rate_miniDSP_A_reg_values_INST_SIZE)); |
| byte_i2c_array_read(codec, |
| Second_Rate_miniDSP_D_reg_values, |
| (Second_Rate_miniDSP_D_reg_values_COEFF_SIZE + |
| Second_Rate_miniDSP_D_reg_values_INST_SIZE)); |
| } |
| } |
| #endif |
| DBG("\nmode = %d\n", mode); |
| return mode; |
| } |
| |
| /************************** MUX CONTROL section *****************************/ |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_info_minidsp_mux |
| * Purpose : info routine for mini dsp mux control amixer kcontrols |
| *---------------------------------------------------------------------------- |
| */ |
| static int __new_control_info_minidsp_mux(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| int index,index2; |
| int ret_val = -1; |
| |
| |
| for (index = 0; index < ARRAY_SIZE(main44_MUX_controls); index++) { |
| if (strstr(kcontrol->id.name, main44_MUX_control_names[index])) |
| break; |
| } |
| if (index < ARRAY_SIZE(main44_MUX_controls)) |
| { |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->count = 1; |
| uinfo->value.integer.min = MIN_MUX_CTRL; |
| uinfo->value.integer.max = MAX_MUX_CTRL; |
| ret_val = 0; |
| } |
| |
| #if 1 |
| else{ |
| printk(" The second rate kcontrol id name is====== %s\n",kcontrol->id.name); |
| |
| |
| for (index2 = 0; index < ARRAY_SIZE(base_speaker_SRS_MUX_controls); index2++) { |
| if (strstr(kcontrol->id.name, base_speaker_SRS_MUX_control_names[index2])) |
| break; |
| } |
| if (index < ARRAY_SIZE(base_speaker_SRS_MUX_controls)) |
| { |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->count = 1; |
| uinfo->value.integer.min = MIN_MUX_CTRL; |
| uinfo->value.integer.max = MAX_MUX_CTRL; |
| ret_val = 0; |
| } |
| } |
| |
| #endif |
| |
| return ret_val; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_get_minidsp_mux |
| * |
| * Purpose : get routine for mux control amixer kcontrols, |
| * read current register values to user. |
| * Used for for mini dsp 'MUX control' amixer controls. |
| *---------------------------------------------------------------------------- |
| */ |
| static int __new_control_get_minidsp_mux(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| |
| ucontrol->value.integer.value[0] = kcontrol->private_value; |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_put_minidsp_mux |
| * |
| * Purpose : put routine for amixer kcontrols, write user values to registers |
| * values. Used for for mini dsp 'MUX control' amixer controls. |
| *---------------------------------------------------------------------------- |
| */ |
| static int __new_control_put_minidsp_mux(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| u8 data[MUX_CTRL_REG_SIZE + 1]; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| int index = 1; |
| int user_value = ucontrol->value.integer.value[0]; |
| struct i2c_client *i2c; |
| u8 value[2], swap_reg_pre, swap_reg_post; |
| u8 page; |
| int ret_val = -1, array_size; |
| control *array; |
| char **array_names; |
| char *control_name, *control_name1; |
| struct aic3262_priv *aic326x = snd_soc_codec_get_drvdata(codec); |
| i2c = codec->control_data; |
| |
| |
| |
| if (aic326x->process_flow == 0) { |
| DBG("#the current process flow is %d", aic326x->process_flow); |
| array = main44_MUX_controls; |
| array_size = ARRAY_SIZE(main44_MUX_controls); |
| array_names = main44_MUX_control_names; |
| control_name = "Stereo_Mux_TwoToOne_1"; |
| control_name1 = "Mono_Mux_1_1"; |
| } |
| |
| #if 0 |
| |
| /* Configure only for process flow 1 controls */ |
| if (strcmp(kcontrol->id.name, control_name) && |
| strcmp(kcontrol->id.name, control_name1)) |
| return 0; |
| } else { |
| array = Second_Rate_MUX_controls; |
| array_size = ARRAY_SIZE(Second_Rate_MUX_controls); |
| array_names = Second_Rate_MUX_control_names; |
| control_name = "Stereo_Mux_TwoToOne_1_Second"; |
| control_name1 = "Mono_Mux_1_Second"; |
| control_name2 = "Mono_Mux_4_Second"; |
| |
| /* Configure only for process flow 2 controls */ |
| if (strcmp(kcontrol->id.name, control_name1) && |
| strcmp(kcontrol->id.name, control_name2)) |
| return 0; |
| } |
| |
| #endif |
| |
| page = array[index].control_page; |
| |
| DBG("#user value = 0x%x\n", user_value); |
| for (index = 0; index < array_size; index++) { |
| if (strstr(kcontrol->id.name, array_names[index])) |
| break; |
| } |
| if (index < array_size) { |
| DBG(KERN_INFO "#Index %d Changing to Page %d\n", index, |
| array[index].control_page); |
| |
| aic3262_change_book(codec, |
| array[index].control_book); |
| aic3262_change_page(codec, |
| array[index].control_page); |
| |
| if (!strcmp(array_names[index], control_name)) { |
| if (user_value > 0) { |
| data[1] = 0x00; |
| data[2] = 0x00; |
| data[3] = 0x00; |
| } else { |
| data[1] = 0xFF; |
| data[2] = 0xFf; |
| data[3] = 0xFF; |
| } |
| } else { |
| if (user_value > 0) { |
| data[1] = |
| (u8) ((user_value >> 16) & |
| AIC3262_8BITS_MASK); |
| data[2] = |
| (u8) ((user_value >> 8) & |
| AIC3262_8BITS_MASK); |
| data[3] = |
| (u8)((user_value) & AIC3262_8BITS_MASK); |
| } |
| } |
| /* start register address */ |
| data[0] = array[index].control_base; |
| |
| DBG(KERN_INFO |
| "#Writing %d %d %d \r\n", data[0], data[1], data[2]); |
| |
| ret_val = i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); |
| |
| if (ret_val != MUX_CTRL_REG_SIZE + 1) |
| printk(KERN_ERR "i2c_master_send transfer failed\n"); |
| else { |
| /* store the current level */ |
| kcontrol->private_value = user_value; |
| ret_val = 0; |
| /* Enable adaptive filtering for ADC/DAC */ |
| } |
| |
| /* Perform a BUFFER SWAP Command. Check if we are currently not |
| * in Page 8, if so, swap to Page 8 first |
| */ |
| |
| value[0] = 1; |
| |
| if (i2c_master_send(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| /* Read the Value of the Page 8 Register 1 which controls the |
| Adaptive Switching Mode */ |
| if (i2c_master_recv(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not read codec registers\n"); |
| |
| swap_reg_pre = value[0]; |
| /* Write the Register bit updates */ |
| value[1] = value[0] | 1; |
| value[0] = 1; |
| |
| if (i2c_master_send(i2c, value, 2) != 2) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| value[0] = 1; |
| /* verify buffer swap */ |
| if (i2c_master_send(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| /* Read the Value of the Page 8 Register 1 which controls the |
| Adaptive Switching Mode */ |
| if (i2c_master_recv(i2c, &swap_reg_post, 1) != 1) |
| printk(KERN_ERR "Can not read codec registers\n"); |
| |
| if ((swap_reg_pre == 4 && swap_reg_post == 6) |
| || (swap_reg_pre == 6 && swap_reg_post == 4)) |
| DBG("Buffer swap success\n"); |
| else |
| printk(KERN_ERR |
| "Buffer swap...FAILED\nswap_reg_pre=%x, \ |
| swap_reg_post=%x\n", swap_reg_pre, swap_reg_post); |
| |
| } |
| /* update the new buffer value in the old, just swapped out buffer */ |
| aic3262_change_book(codec, array[index].control_book); |
| aic3262_change_page(codec, array[index].control_page); |
| ret_val = i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); |
| ret_val = 0; |
| |
| aic3262_change_book(codec, 0); |
| return ret_val; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : minidsp_mux_ctrl_mixer_controls |
| * |
| * Purpose : Add amixer kcontrols for mini dsp mux controls, |
| *---------------------------------------------------------------------------- |
| */ |
| static int minidsp_mux_ctrl_mixer_controls(struct snd_soc_codec *codec, |
| int size, control *cntl, |
| char **name) |
| { |
| int i, err; |
| int val1; |
| |
| printk("%d mixer controls for mini dsp MUX\n", size); |
| if (size) { |
| for (i = 0; i < size; i++) { |
| |
| snd_mux_controls[i].name = name[i]; |
| snd_mux_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; |
| snd_mux_controls[i].access = |
| SNDRV_CTL_ELEM_ACCESS_READWRITE; |
| snd_mux_controls[i].info = |
| __new_control_info_minidsp_mux; |
| snd_mux_controls[i].get = __new_control_get_minidsp_mux; |
| snd_mux_controls[i].put = __new_control_put_minidsp_mux; |
| /* |
| * TBD: read volume reg and update the index number |
| */ |
| aic3262_change_book(codec, cntl[i].control_book); |
| aic3262_change_page(codec, cntl[i].control_page); |
| val1 = i2c_smbus_read_byte_data(codec->control_data, |
| cntl[i].control_base); |
| DBG(KERN_INFO "Control data %x\n", val1); |
| /* |
| if( val1 >= 0 ) |
| snd_mux_controls[i].private_value = val1; |
| else |
| snd_mux_controls[i].private_value = 0; |
| */ |
| DBG(KERN_INFO |
| "the value of amixer control mux=%d", val1); |
| if (val1 >= 0 && val1 != 255) |
| snd_mux_controls[i].private_value = val1; |
| else |
| snd_mux_controls[i].private_value = 0; |
| |
| snd_mux_controls[i].count = 0; |
| |
| err = snd_ctl_add(codec->card->snd_card, |
| snd_ctl_new1(&snd_mux_controls[i], |
| codec)); |
| if (err < 0) |
| printk(KERN_ERR |
| "%s:Invalid control %s\n", __FILE__, |
| snd_mux_controls[i].name); |
| } |
| } |
| return 0; |
| } |
| |
| /*------------------------- Volume Controls -----------------------*/ |
| static int volume_lite_table[] = { |
| |
| 0x00000D, 0x00000E, 0x00000E, 0x00000F, |
| 0x000010, 0x000011, 0x000012, 0x000013, |
| 0x000015, 0x000016, 0x000017, 0x000018, |
| 0x00001A, 0x00001C, 0x00001D, 0x00001F, |
| 0x000021, 0x000023, 0x000025, 0x000027, |
| 0x000029, 0x00002C, 0x00002F, 0x000031, |
| 0x000034, 0x000037, 0x00003B, 0x00003E, |
| 0x000042, 0x000046, 0x00004A, 0x00004F, |
| 0x000053, 0x000058, 0x00005D, 0x000063, |
| 0x000069, 0x00006F, 0x000076, 0x00007D, |
| 0x000084, 0x00008C, 0x000094, 0x00009D, |
| 0x0000A6, 0x0000B0, 0x0000BB, 0x0000C6, |
| 0x0000D2, 0x0000DE, 0x0000EB, 0x0000F9, |
| 0x000108, 0x000118, 0x000128, 0x00013A, |
| 0x00014D, 0x000160, 0x000175, 0x00018B, |
| 0x0001A3, 0x0001BC, 0x0001D6, 0x0001F2, |
| 0x000210, 0x00022F, 0x000250, 0x000273, |
| 0x000298, 0x0002C0, 0x0002E9, 0x000316, |
| 0x000344, 0x000376, 0x0003AA, 0x0003E2, |
| 0x00041D, 0x00045B, 0x00049E, 0x0004E4, |
| 0x00052E, 0x00057C, 0x0005D0, 0x000628, |
| 0x000685, 0x0006E8, 0x000751, 0x0007C0, |
| 0x000836, 0x0008B2, 0x000936, 0x0009C2, |
| 0x000A56, 0x000AF3, 0x000B99, 0x000C49, |
| 0x000D03, 0x000DC9, 0x000E9A, 0x000F77, |
| 0x001062, 0x00115A, 0x001262, 0x001378, |
| 0x0014A0, 0x0015D9, 0x001724, 0x001883, |
| 0x0019F7, 0x001B81, 0x001D22, 0x001EDC, |
| 0x0020B0, 0x0022A0, 0x0024AD, 0x0026DA, |
| 0x002927, 0x002B97, 0x002E2D, 0x0030E9, |
| 0x0033CF, 0x0036E1, 0x003A21, 0x003D93, |
| 0x004139, 0x004517, 0x00492F, 0x004D85, |
| 0x00521D, 0x0056FA, 0x005C22, 0x006197, |
| 0x006760, 0x006D80, 0x0073FD, 0x007ADC, |
| 0x008224, 0x0089DA, 0x009205, 0x009AAC, |
| 0x00A3D7, 0x00B7D4, 0x00AD8C, 0x00C2B9, |
| 0x00CE43, 0x00DA7B, 0x00E76E, 0x00F524, |
| 0x0103AB, 0x01130E, 0x01235A, 0x01349D, |
| 0x0146E7, 0x015A46, 0x016ECA, 0x018486, |
| 0x019B8C, 0x01B3EE, 0x01CDC3, 0x01E920, |
| 0x02061B, 0x0224CE, 0x024553, 0x0267C5, |
| 0x028C42, 0x02B2E8, 0x02DBD8, 0x030736, |
| 0x033525, 0x0365CD, 0x039957, 0x03CFEE, |
| 0x0409C2, 0x044703, 0x0487E5, 0x04CCA0, |
| 0x05156D, 0x05628A, 0x05B439, 0x060ABF, |
| 0x066666, 0x06C77B, 0x072E50, 0x079B3D, |
| 0x080E9F, 0x0888D7, 0x090A4D, 0x09936E, |
| 0x0A24B0, 0x0ABE8D, 0x0B6188, 0x0C0E2B, |
| 0x0CC509, 0x0D86BD, 0x0E53EB, 0x0F2D42, |
| 0x101379, 0x110754, 0x1209A3, 0x131B40, |
| 0x143D13, 0x157012, 0x16B543, 0x180DB8, |
| 0x197A96, 0x1AFD13, 0x1C9676, 0x1E481C, |
| 0x201373, 0x21FA02, 0x23FD66, 0x261F54, |
| 0x28619A, 0x2AC625, 0x2D4EFB, 0x2FFE44, |
| 0x32D646, 0x35D96B, 0x390A41, 0x3C6B7E, |
| 0x400000, 0x43CAD0, 0x47CF26, 0x4C106B, |
| 0x50923B, 0x55586A, 0x5A6703, 0x5FC253, |
| 0x656EE3, 0x6B7186, 0x71CF54, 0x788DB4, |
| 0x7FB260, |
| }; |
| |
| static struct snd_kcontrol_new snd_vol_controls[MAX_VOLUME_CONTROLS]; |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_info_main44_minidsp_volume |
| * Purpose : info routine for volumeLite amixer kcontrols |
| *---------------------------------------------------------------------------- |
| */ |
| |
| static int |
| __new_control_info_minidsp_volume(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| int index, index8; |
| int ret_val = -1; |
| |
| for (index = 0; index < ARRAY_SIZE(main44_VOLUME_controls); index++) { |
| if (strstr |
| (kcontrol->id.name, main44_VOLUME_control_names[index])) |
| break; |
| } |
| |
| for (index8 = 0; index8 < ARRAY_SIZE(base_speaker_SRS_VOLUME_controls); |
| index8++) { |
| if (strstr |
| (kcontrol->id.name, |
| base_speaker_SRS_VOLUME_control_names[index])) |
| break; |
| } |
| |
| if ((index < ARRAY_SIZE(main44_VOLUME_controls)) |
| |
| || (index8 < ARRAY_SIZE(base_speaker_SRS_VOLUME_controls))) { |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->count = 1; |
| uinfo->value.integer.min = MIN_VOLUME; |
| uinfo->value.integer.max = MAX_VOLUME; |
| ret_val = 0; |
| } |
| return ret_val; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_get_main44_minidsp_vol |
| * Purpose : get routine for amixer kcontrols, read current register |
| * values. Used for for mini dsp 'VolumeLite' amixer controls. |
| *---------------------------------------------------------------------------- |
| */ |
| static int |
| __new_control_get_minidsp_volume(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = kcontrol->private_value; |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : __new_control_put_main44_minidsp_volume |
| * Purpose : put routine for amixer kcontrols, write user values to registers |
| * values. Used for for mini dsp 'VolumeLite' amixer controls. |
| *---------------------------------------------------------------------------- |
| */ |
| static int |
| __new_control_put_minidsp_volume(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| u8 data[4]; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| int user_value = ucontrol->value.integer.value[0]; |
| struct i2c_client *i2c = codec->control_data; |
| int ret_val = -1; |
| int coeff; |
| u8 value[2], swap_reg_pre, swap_reg_post; |
| struct aic3262_priv *aic3262 = snd_soc_codec_get_drvdata(codec); |
| |
| control *volume_controls = NULL; |
| printk(KERN_INFO "user value = 0x%x\n", user_value); |
| |
| if (aic3262->process_flow == 0) |
| volume_controls = main44_VOLUME_controls; |
| |
| else |
| volume_controls = base_speaker_SRS_VOLUME_controls; |
| |
| |
| aic3262_change_book(codec, volume_controls->control_book); |
| aic3262_change_page(codec, volume_controls->control_page); |
| |
| coeff = volume_lite_table[user_value << 1]; |
| |
| data[1] = (u8) ((coeff >> 16) & AIC3262_8BITS_MASK); |
| data[2] = (u8) ((coeff >> 8) & AIC3262_8BITS_MASK); |
| data[3] = (u8) ((coeff) & AIC3262_8BITS_MASK); |
| |
| /* Start register address */ |
| data[0] = volume_controls->control_base; |
| ret_val = i2c_master_send(i2c, data, VOLUME_REG_SIZE + 1); |
| if (ret_val != VOLUME_REG_SIZE + 1) |
| printk(KERN_ERR "i2c_master_send transfer failed\n"); |
| else { |
| /* store the current level */ |
| kcontrol->private_value = user_value; |
| ret_val = 0; |
| } |
| /* Initiate buffer swap */ |
| value[0] = 1; |
| |
| if (i2c_master_send(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| /* Read the Value of the Page 8 Register 1 which controls the |
| Adaptive Switching Mode */ |
| if (i2c_master_recv(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not read codec registers\n"); |
| |
| swap_reg_pre = value[0]; |
| /* Write the Register bit updates */ |
| value[1] = value[0] | 1; |
| value[0] = 1; |
| if (i2c_master_send(i2c, value, 2) != 2) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| value[0] = 1; |
| /* verify buffer swap */ |
| if (i2c_master_send(i2c, value, 1) != 1) |
| printk(KERN_ERR "Can not write register address\n"); |
| |
| /* Read the Value of the Page 8 Register 1 which controls the |
| Adaptive Switching Mode */ |
| if (i2c_master_recv(i2c, &swap_reg_post, 1) != 1) |
| printk(KERN_ERR "Can not read codec registers\n"); |
| |
| if ((swap_reg_pre == 4 && swap_reg_post == 6) |
| || (swap_reg_pre == 6 && swap_reg_post == 4)) |
| DBG("Buffer swap success\n"); |
| else |
| DBG("Buffer swap...FAILED\nswap_reg_pre=%x, swap_reg_post=%x\n", |
| swap_reg_pre, swap_reg_post); |
| |
| /* update the new buffer value in the old, just swapped out buffer */ |
| aic3262_change_book(codec, volume_controls->control_book); |
| aic3262_change_page(codec, volume_controls->control_page); |
| i2c_master_send(i2c, data, MUX_CTRL_REG_SIZE + 1); |
| |
| aic3262_change_book(codec, 0); |
| |
| return 0; |
| } |
| |
| /* |
| *---------------------------------------------------------------------------- |
| * Function : minidsp_volume_main44_mixer_controls |
| * Purpose : Add amixer kcontrols for mini dsp volume Lite controls, |
| *---------------------------------------------------------------------------- |
| */ |
| static int minidsp_volume_mixer_controls(struct snd_soc_codec *codec) |
| { |
| int i, err, no_volume_controls; |
| static char volume_control_name[MAX_VOLUME_CONTROLS][40]; |
| |
| /* ADD first process volume controls */ |
| no_volume_controls = ARRAY_SIZE(main44_VOLUME_controls); |
| |
| printk(KERN_INFO " %d mixer controls for mini dsp 'volumeLite'\n", |
| no_volume_controls); |
| |
| if (no_volume_controls) { |
| |
| for (i = 0; i < no_volume_controls; i++) { |
| strcpy(volume_control_name[i], |
| main44_VOLUME_control_names[i]); |
| strcat(volume_control_name[i], VOLUME_KCONTROL_NAME); |
| |
| printk(KERN_ERR "Volume controls: %s\n", |
| volume_control_name[i]); |
| |
| snd_vol_controls[i].name = volume_control_name[i]; |
| snd_vol_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; |
| snd_vol_controls[i].access = |
| SNDRV_CTL_ELEM_ACCESS_READWRITE; |
| snd_vol_controls[i].info = |
| __new_control_info_minidsp_volume; |
| snd_vol_controls[i].get = |
| __new_control_get_minidsp_volume; |
| snd_vol_controls[i].put = |
| __new_control_put_minidsp_volume; |
| /* |
| * TBD: read volume reg and update the index number |
| */ |
| snd_vol_controls[i].private_value = 0; |
| snd_vol_controls[i].count = 0; |
| |
| err = snd_ctl_add(codec->card->snd_card, |
| snd_ctl_new1(&snd_vol_controls[i], |
| codec)); |
| if (err < 0) { |
| printk(KERN_ERR |
| "%s:Invalid control %s\n", __FILE__, |
| snd_vol_controls[i].name); |
| } |
| } |
| } |
| |
| |
| /* ADD second process volume controls */ |
| no_volume_controls = ARRAY_SIZE(base_speaker_SRS_VOLUME_controls); |
| |
| printk(KERN_ERR " %d mixer controls for mini dsp 'volumeLite'\n", |
| no_volume_controls); |
| |
| if (no_volume_controls) { |
| |
| for (i = 0; i < no_volume_controls; i++) { |
| strcpy(volume_control_name[i], |
| base_speaker_SRS_VOLUME_control_names[i]); |
| strcat(volume_control_name[i], VOLUME_KCONTROL_NAME); |
| |
| printk(KERN_ERR "Volume controls: %s\n", |
| volume_control_name[i]); |
| |
| snd_vol_controls[i].name = volume_control_name[i]; |
| snd_vol_controls[i].iface = SNDRV_CTL_ELEM_IFACE_MIXER; |
| snd_vol_controls[i].access = |
| SNDRV_CTL_ELEM_ACCESS_READWRITE; |
| snd_vol_controls[i].info = |
| __new_control_info_minidsp_volume; |
| snd_vol_controls[i].get = |
| __new_control_get_minidsp_volume; |
| snd_vol_controls[i].put = |
| __new_control_put_minidsp_volume; |
| /* |
| * TBD: read volume reg and update the index number |
| */ |
| snd_vol_controls[i].private_value = 0; |
| snd_vol_controls[i].count = 0; |
| |
| err = snd_ctl_add(codec->card->snd_card, |
| snd_ctl_new1(&snd_vol_controls[i], |
| codec)); |
| if (err < 0) { |
| printk(KERN_ERR |
| "%s:Invalid control %s\n", __FILE__, |
| snd_vol_controls[i].name); |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* |
| *-------------------------------------------------------------------------- |
| * Function : aic3262_add_minidsp_controls |
| * Purpose : Configures the AMIXER Control Interfaces that can be exercised by |
| * the user at run-time. Utilizes the the snd_adaptive_controls[] |
| * array to specify two run-time controls. |
| *--------------------------------------------------------------------------- |
| */ |
| int aic3262_add_minidsp_controls(struct snd_soc_codec *codec) |
| { |
| #ifdef ADD_MINI_DSP_CONTROLS |
| int i, err, no_mux_controls,no_mux_controls1; |
| /* add mode k control */ |
| for (i = 0; i < ARRAY_SIZE(aic3262_minidsp_controls); i++) { |
| err = snd_ctl_add(codec->card->snd_card, |
| snd_ctl_new1(&aic3262_minidsp_controls[i], codec)); |
| if (err < 0) { |
| printk(KERN_ERR "Invalid control\n"); |
| return err; |
| } |
| } |
| |
| |
| /* add mux controls */ |
| no_mux_controls = ARRAY_SIZE(main44_MUX_controls); |
| minidsp_mux_ctrl_mixer_controls(codec, no_mux_controls, |
| main44_MUX_controls, main44_MUX_control_names); |
| |
| |
| no_mux_controls1 = ARRAY_SIZE(base_speaker_SRS_MUX_controls); |
| minidsp_mux_ctrl_mixer_controls(codec, no_mux_controls1, |
| base_speaker_SRS_MUX_controls, base_speaker_SRS_MUX_control_names); |
| |
| |
| /* add volume controls*/ |
| minidsp_volume_mixer_controls(codec); |
| #endif /* ADD_MINI_DSP_CONTROLS */ |
| return 0; |
| } |
| |
| MODULE_DESCRIPTION("ASoC TLV320AIC3262 miniDSP driver"); |
| MODULE_AUTHOR("Y Preetam Sashank Reddy <preetam@mistralsolutions.com>"); |
| MODULE_LICENSE("GPL"); |
| #endif /* End of CONFIG_MINI_DSP */ |