diff options
author | Kevin | 2014-11-15 10:00:36 +0800 |
---|---|---|
committer | Kevin | 2014-11-15 10:00:36 +0800 |
commit | 9d40ac5867b9aefe0722bc1f110b965ff294d30d (patch) | |
tree | de942df665fac4bac0d9cb7ae86910fe937b0c1a /ANDROID_3.4.5/sound/soc | |
parent | 392e8802486cb573b916e746010e141a75f507e6 (diff) | |
download | FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.tar.gz FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.tar.bz2 FOSSEE-netbook-kernel-source-9d40ac5867b9aefe0722bc1f110b965ff294d30d.zip |
add via modify part source code for wm8880 4.4 kitkat
Diffstat (limited to 'ANDROID_3.4.5/sound/soc')
29 files changed, 8121 insertions, 21 deletions
diff --git a/ANDROID_3.4.5/sound/soc/Kconfig b/ANDROID_3.4.5/sound/soc/Kconfig index 91c98559..5727227d 100644 --- a/ANDROID_3.4.5/sound/soc/Kconfig +++ b/ANDROID_3.4.5/sound/soc/Kconfig @@ -48,6 +48,7 @@ source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" +source "sound/soc/wmt/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/ANDROID_3.4.5/sound/soc/Makefile b/ANDROID_3.4.5/sound/soc/Makefile index 2feaf376..4dca9714 100644 --- a/ANDROID_3.4.5/sound/soc/Makefile +++ b/ANDROID_3.4.5/sound/soc/Makefile @@ -5,6 +5,7 @@ snd-soc-dmaengine-pcm-objs := soc-dmaengine-pcm.o obj-$(CONFIG_SND_SOC_DMAENGINE_PCM) += snd-soc-dmaengine-pcm.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o +obj-$(CONFIG_SND_SOC) += wmt/ obj-$(CONFIG_SND_SOC) += codecs/ obj-$(CONFIG_SND_SOC) += atmel/ obj-$(CONFIG_SND_SOC) += au1x/ diff --git a/ANDROID_3.4.5/sound/soc/codecs/Kconfig b/ANDROID_3.4.5/sound/soc/codecs/Kconfig index 59d8efaa..7077ac69 100644 --- a/ANDROID_3.4.5/sound/soc/codecs/Kconfig +++ b/ANDROID_3.4.5/sound/soc/codecs/Kconfig @@ -335,6 +335,18 @@ config SND_SOC_WM8741 config SND_SOC_WM8750 tristate +config SND_SOC_VT1602 + tristate + depends on SND_SOC + +config SND_SOC_VT1603 + tristate + depends on SND_SOC + +config SND_SOC_HWDAC + tristate + depends on SND_SOC + config SND_SOC_WM8753 tristate @@ -437,3 +449,7 @@ config SND_SOC_MAX9877 config SND_SOC_TPA6130A2 tristate + +# echo cancellation +config SND_SOC_WMT_FM34 + tristate diff --git a/ANDROID_3.4.5/sound/soc/codecs/Makefile b/ANDROID_3.4.5/sound/soc/codecs/Makefile index 6662eb0c..83175a64 100644 --- a/ANDROID_3.4.5/sound/soc/codecs/Makefile +++ b/ANDROID_3.4.5/sound/soc/codecs/Makefile @@ -66,6 +66,9 @@ snd-soc-wm8731-objs := wm8731.o snd-soc-wm8737-objs := wm8737.o snd-soc-wm8741-objs := wm8741.o snd-soc-wm8750-objs := wm8750.o +snd-soc-vt1602-objs := wmt_vt1602.o +snd-soc-vt1603-objs := vt1603.o +snd-soc-hwdac-objs := wmt_hwdac.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8770-objs := wm8770.o snd-soc-wm8776-objs := wm8776.o @@ -102,6 +105,9 @@ snd-soc-wm-hubs-objs := wm_hubs.o snd-soc-max9877-objs := max9877.o snd-soc-tpa6130a2-objs := tpa6130a2.o +# echo cancellation +snd-soc-wmt-fm34-objs := wmt_fm34.o + obj-$(CONFIG_SND_SOC_88PM860X) += snd-soc-88pm860x.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o @@ -170,6 +176,9 @@ obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8737) += snd-soc-wm8737.o obj-$(CONFIG_SND_SOC_WM8741) += snd-soc-wm8741.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o +obj-$(CONFIG_SND_SOC_VT1602) += snd-soc-vt1602.o +obj-$(CONFIG_SND_SOC_VT1603) += snd-soc-vt1603.o +obj-$(CONFIG_SND_SOC_HWDAC) += snd-soc-hwdac.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o @@ -205,3 +214,6 @@ obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o + +# echo cancellation +obj-$(CONFIG_SND_SOC_WMT_FM34) += snd-soc-wmt-fm34.o diff --git a/ANDROID_3.4.5/sound/soc/codecs/vt1603.c b/ANDROID_3.4.5/sound/soc/codecs/vt1603.c new file mode 100755 index 00000000..eaa3d28e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/vt1603.c @@ -0,0 +1,1249 @@ +/*++ + * linux/sound/soc/codecs/vt1603.c + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/version.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 <sound/tlv.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/workqueue.h> +#include <linux/switch.h> +#include <linux/slab.h> +#include <linux/proc_fs.h> +#include <linux/notifier.h> +#include <linux/reboot.h> + +#include "vt1603.h" +#include <linux/mfd/vt1603/core.h> + +#define VT1603_TIMER_INTERVAL 100 // ms + +static struct snd_soc_codec *gvt1603_codec = NULL; // + +struct vt1603_priv { + unsigned int sysclk; + struct switch_dev hp_switch; // state of headphone insert or not + struct work_struct work; + struct snd_soc_codec *codec; + struct timer_list timer; // timer for detecting headphone + struct proc_dir_entry *proc; + struct notifier_block reboot_notifier; +}; + +struct vt1603_boardinfo { + int hp_level; // 0: headset level low; 1: high + int ignore_hp_event; + int rec_src; // 0: AMIC-IN; 1: DMIC-IN + + int hpd_rm; //add 2013-11-26 + int hp_debounce; //add 2014-1-14 + int timer_del; //2014-1-20 tvbox + + int out_on; //2014-4-29 av out on for tvbox noisy. hw not good! +}; +static struct vt1603_boardinfo vt1603_boardinfo; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +static const u16 vt1603_regs[] = { + 0x0020, 0x00D7, 0x00D7, 0x0004, /* 0-3 */ + 0x0000, 0x0040, 0x0000, 0x00c0, /* 4-7 */ + 0x00c0, 0x0010, 0x0001, 0x0041, /* 8-11 */ + 0x0000, 0x0000, 0x000b, 0x0093, /* 12-15 */ + 0x0004, 0x0016, 0x0026, 0x0060, /* 16-19 */ + 0x001a, 0x0000, 0x0002, 0x0000, /* 20-23 */ + 0x0000, 0x000a, 0x0000, 0x0010, /* 24-27 */ + 0x0000, 0x00e2, 0x0000, 0x0000, /* 28-31 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 32-35 */ + 0x0000, 0x000c, 0x0054, 0x0000, /* 36-39 */ + 0x0001, 0x000c, 0x000c, 0x000c, /* 40-43 */ + 0x000c, 0x000c, 0x00c0, 0x00d5, /* 44-47 */ + 0x00c5, 0x0012, 0x0085, 0x002b, /* 48-51 */ + 0x00cd, 0x00cd, 0x008e, 0x008d, /* 52-55 */ + 0x00e0, 0x00a6, 0x00a5, 0x0050, /* 56-59 */ + 0x00e9, 0x00cf, 0x0040, 0x0000, /* 60-63 */ + 0x0000, 0x0008, 0x0004, 0x0000, /* 64-67 */ + 0x0040, 0x0000, 0x0040, 0x0000, /* 68-71 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 72-75 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 76-79 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 80-83 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 84-87 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 88-91 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 92-95 */ + 0x00c4, 0x0079, 0x000c, 0x0024, /* 96-99 */ + 0x0016, 0x0016, 0x0060, 0x0002, /* 100-103 */ + 0x005b, 0x0003, 0x0039, 0x0039, /* 104-107 */ + 0x00fe, 0x0012, 0x0034, 0x0000, /* 108-111 */ + 0x0004, 0x00f0, 0x0000, 0x0000, /* 112-115 */ + 0x0000, 0x0000, 0x0003, 0x0000, /* 116-119 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 120-123 */ + 0x00f2, 0x0020, 0x0023, 0x006e, /* 124-127 */ + 0x0019, 0x0040, 0x0000, 0x0004, /* 128-131 */ + 0x000a, 0x0075, 0x0035, 0x00b0, /* 132-135 */ + 0x0040, 0x0000, 0x0000, 0x0000, /* 136-139 */ + 0x0088, 0x0013, 0x000c, 0x0003, /* 140-143 */ + 0x0000, 0x0000, 0x0008, 0x0000, /* 144-147 */ + 0x0003, 0x0020, 0x0030, 0x0018, /* 148-151 */ + 0x001b, +}; + +/*----------------------------------------------------------------------*/ + + +/*---------------------Control Interface Functions----------------------*/ + +static int vt1603_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + int ret = vt1603_reg_write(codec->control_data, reg, value); + if (ret == 0) + ((u16 *)codec->reg_cache)[reg] = value; + return ret; +} + +static unsigned int vt1603_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return ((u16 *)codec->reg_cache)[reg]; +} + +static int vt1603_hw_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + return vt1603_reg_write(codec->control_data, reg, value); +} + +static unsigned int vt1603_hw_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 val; + int ret = vt1603_reg_read(codec->control_data, reg, &val); + if (ret == 0) + return val; + else + return ((u16 *)codec->reg_cache)[reg]; +} + +/*----------------------------------------------------------------------*/ + + +/*----------------------IRQ handle Functions----------------------------*/ + +static void vt1603_set_switch_state(struct snd_soc_codec *codec) +{ + /* Just report hp state, don't switch hw device, app will handle it. */ + + struct vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + unsigned int val = vt1603_hw_read(codec, VT1603_R21); + + if (vt1603_boardinfo.hp_level) { + if (val & BIT1) { + val &= ~BIT1; + snd_soc_write(codec, VT1603_R21, val); + if (!vt1603_boardinfo.ignore_hp_event) + switch_set_state(&vt1603->hp_switch, 0); + pr_info("<<<vt1603: hp out!\n"); + } + else { + val |= BIT1; + snd_soc_write(codec, VT1603_R21, val); + if (!vt1603_boardinfo.ignore_hp_event) + switch_set_state(&vt1603->hp_switch, 1); + pr_info("<<<vt1603: hp in!\n"); + } + } + else { + if (val & BIT1) { + val &= ~BIT1; + snd_soc_write(codec, VT1603_R21, val); + if (!vt1603_boardinfo.ignore_hp_event) + switch_set_state(&vt1603->hp_switch, 1); + pr_info("<<<vt1603: hp in!\n"); + } + else { + val |= BIT1; + snd_soc_write(codec, VT1603_R21, val); + if (!vt1603_boardinfo.ignore_hp_event) + switch_set_state(&vt1603->hp_switch, 0); + pr_info("<<<vt1603: hp out!\n"); + } + } +} + +static void vt1603_codec_irq_handle(struct work_struct *work) +{ + struct vt1603_priv *priv = container_of(work, struct vt1603_priv, work); + struct snd_soc_codec *codec = priv->codec; + unsigned int val; + static unsigned int count = 0; + int hp_state = 0; //switch_get_state(&priv->hp_switch); + if (!vt1603_boardinfo.hpd_rm) + hp_state = switch_get_state(&priv->hp_switch); + + unsigned int debounce = (hp_state == 0) ? 3 : 1; // sw-debounce for headset plugin + + if (vt1603_boardinfo.hp_debounce > 0) + debounce = (hp_state == 0) ? vt1603_boardinfo.hp_debounce : 1; + + + + val = vt1603_hw_read(codec, VT1603_R51); + if (val & BIT1) { + val = vt1603_hw_read(codec, VT1603_R1b); + val |= BIT1; + snd_soc_write(codec, VT1603_R1b, val); + val &= ~BIT1; + snd_soc_write(codec, VT1603_R1b, val); + count++; + } + else { + count = 0; + } + + if (count == debounce) { + pr_info("vt1603: hpdetect\n"); + vt1603_set_switch_state(codec); + // clear headphone irq mask + val = vt1603_hw_read(codec,VT1603_R1b); + val |= BIT1; + snd_soc_write(codec, VT1603_R1b, val); + val &= ~BIT1; + snd_soc_write(codec, VT1603_R1b, val); + count = 0; + } + + val = vt1603_hw_read(codec, VT1603_R52); + if (val & BIT4) { + pr_info("vt1603: left cld oc\n"); + val = vt1603_hw_read(codec,VT1603_R1c); + val |= BIT4; + snd_soc_write(codec, VT1603_R1c, val); + val &= ~BIT4; + snd_soc_write(codec, VT1603_R1c, val); + } + if (val & BIT3) { + pr_info("vt1603: right cld oc\n"); + val = vt1603_hw_read(codec,VT1603_R1c); + val |= BIT3; + snd_soc_write(codec, VT1603_R1c, val); + val &= ~BIT3; + snd_soc_write(codec, VT1603_R1c, val); + } +} + +static void vt1603_timer_handler(unsigned long data) +{ + struct vt1603_priv *vt1603 = (struct vt1603_priv *)data; + schedule_work(&vt1603->work); + mod_timer(&vt1603->timer, jiffies + msecs_to_jiffies(VT1603_TIMER_INTERVAL)); +} + +/*----------------------------------------------------------------------*/ + + +/*--------------------Mixer controls & dapm-----------------------------*/ + +static const DECLARE_TLV_DB_SCALE(digital_tlv, -9550, 50, 1); +static const DECLARE_TLV_DB_SCALE(digital_clv, -4350, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_boost_clv, 0, 1000, 1); +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(adc_pga_clv, -1650, 150, 1); +static const DECLARE_TLV_DB_SCALE(analog_tlv, -2100, 300, 1); +static const DECLARE_TLV_DB_SCALE(hp_mixer_clv, -5700, 100, 1); +static const DECLARE_TLV_DB_SCALE(cld_mixer_clv, -300, 300, 1); + +static const char *adc_cutoff_freq_txt[] = { + "Hi-fi mode", + "Voice mode 1", + "Voice mode 2", + "Voice mode 3", +}; + +static const struct soc_enum adc_cutoff_freq = +SOC_ENUM_SINGLE(VT1603_R00, 3, 4, adc_cutoff_freq_txt); + +static const char *lr_txt[] = { + "Left", "Right" +}; + +static const char *clamper_limit_voltage_txt[] = { + "Vih=2.40V, Vil=0.90V, P=0.63W", + "Vih=2.45V, Vil=0.85V, P=0.72W", + "Vih=2.50V, ViL=0.80V, P=0.81W", + "Vih=2.55V, Vil=0.75V, P=0.91W", + "Vih=2.60V, Vil=0.70V, P=1.02W", + "Vih=2.65V, Vil=0.65V, P=1.12W", + "Vih=2.70V, Vil=0.60V, P=1.24W", + "Vih=2.75V, Vil=0.55V, P=1.36W", + "Vih=2.80V, Vil=0.50V, P=1.49W", + "Vih=2.85V, Vil=0.45V, P=1.62W", + "Vih=2.90V, Vil=0.40V, P=1.76W", + "Vih=2.95V, Vil=0.35V, P=1.90W", + "Vih=3.00V, Vil=0.30V, P=2.05W", + "Vih=3.05V, Vil=0.25V, P=2.20W", + "Vih=3.10V, Vil=0.20V, P=2.37W", + "Vih=3.15V, Vil=0.15V, P=2.53W", +}; + +static const char *clamper_work_voltage_txt[] = { + "Voh=3.10V, Vol=1.90V, P=0.36W", + "Voh=3.25V, Vol=1.75V, P=0.56W", + "Voh=3.40V, Vol=1.60V, P=0.81W", + "Voh=3.55V, Vol=1.45V, P=1.10W", + "Voh=3.70V, Vol=1.30V, P=1.44W", + "Voh=3.85V, Vol=1.15V, P=1.82W", + "Voh=4.00V, Vol=1.00V, P=2.25W", + "Voh=4.15V, Vol=0.85V, P=2.72W", +}; + +static const struct soc_enum dacl_src = +SOC_ENUM_SINGLE(VT1603_R0b, 7, 2, lr_txt); + +static const struct soc_enum dacr_src = +SOC_ENUM_SINGLE(VT1603_R0b, 6, 2, lr_txt); + +static const struct soc_enum adcl_src = +SOC_ENUM_SINGLE(VT1603_R03, 3, 2, lr_txt); + +static const struct soc_enum adcr_src = +SOC_ENUM_SINGLE(VT1603_R03, 2, 2, lr_txt); + +static const struct soc_enum clamper_limit_voltage = +SOC_ENUM_SINGLE(VT1603_R87, 4, 16, clamper_limit_voltage_txt); + +static const struct soc_enum clamper_work_voltage = +SOC_ENUM_SINGLE(VT1603_R87, 0, 8, clamper_work_voltage_txt); + +static const struct snd_kcontrol_new vt1603_snd_controls[] = { +/* Volume */ +SOC_DOUBLE_R_TLV("Digital Capture Volume", VT1603_R01, VT1603_R02, 0, 0x7f, 0, digital_clv), +SOC_DOUBLE_R_TLV("Digital Playback Volume", VT1603_R07, VT1603_R08, 0, 0xc0, 0, digital_tlv), +SOC_DOUBLE_R_TLV("Analog Playback Volume", VT1603_R72, VT1603_R73, 0, 0x07, 1, analog_tlv), +SOC_DOUBLE_R_TLV("Input Boost Volume", VT1603_R64, VT1603_R65, 6, 4, 0, adc_boost_clv), +SOC_DOUBLE_R_TLV("Input PGA Volume", VT1603_R64, VT1603_R65, 1, 0x1f, 0, adc_pga_clv), +SOC_DOUBLE_TLV("Output Boost Volume", VT1603_R06, 0, 3, 7, 0, dac_boost_tlv), +SOC_DOUBLE_TLV("Output CLD Volume", VT1603_R91, 5, 2, 7, 0, cld_mixer_clv), +SOC_DOUBLE_R_TLV("Output HP Volume", VT1603_R6a, VT1603_R6b, 0, 0x3f, 0, hp_mixer_clv), + +/* Switch */ +SOC_SINGLE("EQ Switch", VT1603_R28, 0, 1, 0), + +SOC_SINGLE("DAC Mono Mix Switch", VT1603_R0d, 4, 1, 0), +SOC_SINGLE("Left DAC Switch", VT1603_R62, 5, 1, 0), +SOC_SINGLE("Right DAC Switch", VT1603_R62, 4, 1, 0), + +SOC_ENUM("Left DAC Source", dacl_src), +SOC_ENUM("Right DAC Source", dacr_src), + +SOC_ENUM("Digital Capture Voice Mode", adc_cutoff_freq), +SOC_ENUM("Left ADC Source", adcl_src), +SOC_ENUM("Right ADC Source", adcr_src), + +SOC_DOUBLE("Analog Playback Switch", VT1603_R71, 7, 6, 1, 1), +SOC_SINGLE("Digital Playback Switch", VT1603_R0b, 0, 1, 1), +SOC_DOUBLE("Digital Capture Switch", VT1603_R63, 7, 6, 1, 0), + +SOC_SINGLE("Headset Switch", VT1603_R68, 4, 1, 1), +SOC_SINGLE("Headfree Switch", VT1603_R25, 1, 1, 0), + +SOC_SINGLE("Left CLD Switch", VT1603_R82, 3, 1, 1), +SOC_SINGLE("Right CLD Switch", VT1603_R82, 4, 1, 1), + +SOC_ENUM("Limit Voltage of Power Clamper", clamper_limit_voltage), +SOC_ENUM("Voltage of Clamper Work", clamper_work_voltage), +}; + +/* DAPM Controls */ + +static const struct snd_kcontrol_new linmix_controls[] = { +SOC_DAPM_SINGLE("Left Mic Switch", VT1603_R8e, 5, 1, 0), +SOC_DAPM_SINGLE("Left Linein Switch", VT1603_R8e, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rinmix_controls[] = { +SOC_DAPM_SINGLE("Right Mic Switch", VT1603_R8e, 4, 1, 0), +SOC_DAPM_SINGLE("Right Linein Switch", VT1603_R8e, 6, 1, 0), +}; + +static const struct snd_kcontrol_new loutmix_controls[] = { +SOC_DAPM_SINGLE("Left Input Mixer Switch", VT1603_R71, 3, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", VT1603_R71, 1, 1, 0), +}; + +static const struct snd_kcontrol_new routmix_controls[] = { +SOC_DAPM_SINGLE("Right Input Mixer Switch", VT1603_R71, 2, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", VT1603_R71, 0, 1, 0), +}; + +static const struct snd_kcontrol_new lcld_controls[] = { +SOC_DAPM_SINGLE("Left Output Mixer Switch", VT1603_R90, 5, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", VT1603_R90, 4, 1, 0), +}; + +static const struct snd_kcontrol_new rcld_controls[] = { +SOC_DAPM_SINGLE("Right Output Mixer Switch", VT1603_R90, 3, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", VT1603_R90, 2, 1, 0), +}; + +static const struct snd_kcontrol_new lhp_controls[] = { +SOC_DAPM_SINGLE("Left Output Mixer Switch", VT1603_R69, 5, 1, 0), +SOC_DAPM_SINGLE("DACL Switch", VT1603_R69, 7, 1, 0), +}; + +static const struct snd_kcontrol_new rhp_controls[] = { +SOC_DAPM_SINGLE("Right Output Mixer Switch", VT1603_R69, 2, 1, 0), +SOC_DAPM_SINGLE("DACR Switch", VT1603_R69, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget vt1603_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("Mic"), +SND_SOC_DAPM_INPUT("Linein"), +SND_SOC_DAPM_INPUT("Internal ADC Source"), + +SND_SOC_DAPM_PGA("Left Input Boost", VT1603_R67, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input Boost", VT1603_R67, 6, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Input PGA", VT1603_R67, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input PGA", VT1603_R67, 4, 0, NULL, 0), + + +SND_SOC_DAPM_MIXER("Left Input Mixer", VT1603_R66, 6, 1, + linmix_controls, ARRAY_SIZE(linmix_controls)), +SND_SOC_DAPM_MIXER("Right Input Mixer", VT1603_R66, 5, 1, + rinmix_controls, ARRAY_SIZE(rinmix_controls)), + +SND_SOC_DAPM_ADC("ADCL", "Left Capture", VT1603_R01, 7, 1), +SND_SOC_DAPM_ADC("ADCR", "Right Capture", VT1603_R02, 7, 1), + +//SND_SOC_DAPM_DAC("DACL", "Left Playback", VT1603_R62, 5, 0), +//SND_SOC_DAPM_DAC("DACR", "Right Playback", VT1603_R62, 4, 0), + +SND_SOC_DAPM_INPUT("DACL"), +SND_SOC_DAPM_INPUT("DACR"), + +SND_SOC_DAPM_MIXER("Left Output Mixer", VT1603_R72, 6, 0, + loutmix_controls, ARRAY_SIZE(loutmix_controls)), +SND_SOC_DAPM_MIXER("Right Output Mixer", VT1603_R73, 6, 0, + routmix_controls, ARRAY_SIZE(routmix_controls)), + +SND_SOC_DAPM_MIXER("Left CLD Mixer", VT1603_R91, 1, 0, + lcld_controls, ARRAY_SIZE(lcld_controls)), +SND_SOC_DAPM_MIXER("Right CLD Mixer", VT1603_R91, 0, 0, + rcld_controls, ARRAY_SIZE(rcld_controls)), + +SND_SOC_DAPM_PGA("Left CLD PGA", VT1603_R82, 3, 1, NULL, 0), +SND_SOC_DAPM_PGA("Right CLD PGA", VT1603_R82, 4, 1, NULL, 0), + +SND_SOC_DAPM_MIXER("Left HP Mixer", VT1603_R68, 1, 1, + lhp_controls, ARRAY_SIZE(lhp_controls)), +SND_SOC_DAPM_MIXER("Right HP Mixer", VT1603_R68, 0, 1, + rhp_controls, ARRAY_SIZE(rhp_controls)), + +SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +SND_SOC_DAPM_OUTPUT("Left HP"), +SND_SOC_DAPM_OUTPUT("Right HP"), +SND_SOC_DAPM_OUTPUT("Left SPK"), +SND_SOC_DAPM_OUTPUT("Right SPK"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + //{"Internal DAC Sink", NULL, "DACL"}, + //{"Internal DAC Sink", NULL, "DACR"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + //{"ADCL", NULL, "Internal ADC Source"}, + //{"ADCR", NULL, "Internal ADC Source"}, + + + /* Capture route */ + + { "Left Input Mixer", "Left Linein Switch", "Linein" }, + { "Left Input Mixer", "Left Mic Switch", "Mic" }, + { "Right Input Mixer", "Right Linein Switch", "Linein" }, + { "Right Input Mixer", "Right Mic Switch", "Mic" }, + + { "Left Input Boost", NULL, "Left Input Mixer"}, + { "Right Input Boost", NULL, "Right Input Mixer"}, + + { "Left Input PGA", NULL, "Left Input Boost"}, + { "Right Input PGA", NULL, "Right Input Boost"}, + + { "ADCL", NULL, "Left Input PGA" }, + { "ADCR", NULL, "Right Input PGA" }, + + //{ "Left Input Mixer", NULL, "Left Input PGA" }, + //{ "Right Input Mixer", NULL, "Right Input PGA" }, + + /* Playback route */ + + //{ "Left Output Mixer", "Left Input Mixer Switch", "Left Input Mixer" }, + { "Left Output Mixer", "Left Input Mixer Switch", "Left Input PGA"}, + { "Left Output Mixer", "DACL Switch", "DACL" }, + //{ "Right Output Mixer", "Right Input Mixer Switch", "Right Input Mixer" }, + { "Right Output Mixer", "Right Input Mixer Switch", "Right Input PGA"}, + { "Right Output Mixer", "DACR Switch", "DACR" }, + + { "Left CLD Mixer", "Left Output Mixer Switch", "Left Output Mixer" }, + { "Left CLD Mixer", "DACL Switch", "DACL" }, + { "Right CLD Mixer", "Right Output Mixer Switch", "Right Output Mixer" }, + { "Right CLD Mixer", "DACR Switch", "DACR" }, + + { "Left HP Mixer", "Left Output Mixer Switch", "Left Output Mixer" }, + { "Left HP Mixer", "DACL Switch", "DACL" }, + { "Right HP Mixer", "Right Output Mixer Switch", "Right Output Mixer" }, + { "Right HP Mixer", "DACR Switch", "DACR" }, + + { "Left CLD PGA", NULL, "Left CLD Mixer" }, + { "Right CLD PGA", NULL, "Right CLD Mixer" }, + + { "Left HP", NULL, "Left HP Mixer" }, + { "Right HP", NULL, "Right HP Mixer" }, + + { "Left SPK", NULL, "Left CLD PGA" }, + { "Right SPK", NULL, "Right CLD PGA" }, +}; + +/*----------------------------------------------------------------------*/ + + +/*----------------------codec dai functions-----------------------------*/ + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr; + u8 bclk_div; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x4, 0x0}, + /* 11.025k */ + {11289600, 11025, 1024, 0x8, 0x0}, + /* 16k */ + {12288000, 16000, 768, 0x5, 0x0}, + /* 22.05k */ + {11289600, 22050, 512, 0x9, 0x0}, + /* 32k */ + {12288000, 32000, 384, 0x7, 0x0}, + /* 44.1k */ + {11289600, 44100, 256, 0x6, 0x07}, + /* 48k */ + {12288000, 48000, 256, 0x0, 0x07}, + /* 96k */ + {12288000, 96000, 128, 0x1, 0x04}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return -EINVAL; +} + +static int vt1603_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 vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + vt1603->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int vt1603_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = snd_soc_read(codec, VT1603_R19); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= BIT5 ; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, VT1603_R19, iface); + return 0; +} + +static int vt1603_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + u16 val; + u16 iface = snd_soc_read(codec, VT1603_R19) & 0x73; + int coeff = get_coeff(vt1603->sysclk, params_rate(params)); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x04; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x08; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x0c; + break; + } + + /* set iface & srate */ + snd_soc_write(codec, VT1603_R19, iface); + + if (coeff >= 0) { + val = snd_soc_read(codec, VT1603_R42); + val &= 0xf0; + val |= coeff_div[coeff].bclk_div; + snd_soc_write(codec, VT1603_R42, val); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + val = snd_soc_read(codec, VT1603_R05); + val &= 0xf0; + val |= coeff_div[coeff].sr; + snd_soc_write(codec, VT1603_R05, val); + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + val = snd_soc_read(codec, VT1603_R03); + val &= 0x0f; + val |= ((coeff_div[coeff].sr<<4) & 0xf0); + snd_soc_write(codec, VT1603_R03, val); + } + } + + return 0; +} + +static int vt1603_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + val = snd_soc_read(codec, VT1603_R96); + val |= BIT4+BIT5; + snd_soc_write(codec, VT1603_R96, val); + break; + + case SND_SOC_BIAS_STANDBY: + break; + + case SND_SOC_BIAS_OFF: + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static struct snd_soc_dai_ops vt1603_dai_ops = { + .hw_params = vt1603_pcm_hw_params, + .set_fmt = vt1603_set_dai_fmt, + .set_sysclk = vt1603_set_dai_sysclk, +}; + +struct snd_soc_dai_driver vt1603_dai = { + .name = "VT1603", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &vt1603_dai_ops, +}; + +/*----------------------------------------------------------------------*/ + + +/*----------------------------------------------------------------------*/ + +static void vt1603_get_boardinfo(void) +{ + int ret = 0; + unsigned char buf[64]; + int varlen = sizeof(buf); + + memset(buf, 0x0, sizeof(buf)); + ret = wmt_getsyspara("wmt.audio.i2s", buf, &varlen); + if (ret == 0) { + sscanf(buf, "vt1603:%d:%d:%d", + &vt1603_boardinfo.hp_level, + &vt1603_boardinfo.ignore_hp_event, + &vt1603_boardinfo.rec_src); + } + else { + vt1603_boardinfo.hp_level = 0; + vt1603_boardinfo.ignore_hp_event = 0; + vt1603_boardinfo.rec_src = 0; + } + + memset(buf, 0x0, sizeof(buf)); + vt1603_boardinfo.hpd_rm = 0; + ret = wmt_getsyspara("wmt.vt1603.hpd.rm", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", + &vt1603_boardinfo.hpd_rm); + } + else { + vt1603_boardinfo.hpd_rm = 0; + + } + printk("<<<<%s hpd_rm %d\n", __func__, vt1603_boardinfo.hpd_rm); + + memset(buf, 0x0, sizeof(buf)); + vt1603_boardinfo.hp_debounce = 0; + ret = wmt_getsyspara("wmt.vt1603.hp.debounce", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", + &vt1603_boardinfo.hp_debounce); + } + else { + vt1603_boardinfo.hp_debounce = 0; + + } + printk("<<<<%s hp_debounce %d\n", __func__, vt1603_boardinfo.hp_debounce); + + + memset(buf, 0x0, sizeof(buf)); + vt1603_boardinfo.timer_del = 0; + ret = wmt_getsyspara("wmt.vt1603.timer.del", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", + &vt1603_boardinfo.timer_del); + } + else { + vt1603_boardinfo.timer_del = 0; + + } + printk("<<<<%s timer_del %d\n", __func__, vt1603_boardinfo.timer_del); + + memset(buf, 0x0, sizeof(buf)); + vt1603_boardinfo.out_on = 0; + ret = wmt_getsyspara("wmt.vt1603.out_on", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", + &vt1603_boardinfo.out_on); + } + else { + vt1603_boardinfo.out_on = 0; + + } + printk("<<<<%s out_on %d\n", __func__, vt1603_boardinfo.out_on); +} + +static int vt1603_readproc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + struct vt1603_priv *vt1603 = data; + pr_info("\nvt1603 regs dump:\n"); + vt1603_regs_dump(vt1603->codec->control_data); + return 0; +} + +static void vt1603_codec_reset(struct snd_soc_codec *codec) +{ + unsigned int val; + + vt1603_hw_write(codec, 0xc2, 0x01); + + // reset all registers to default settings + vt1603_hw_write(codec, VT1603_REG_RESET, 0xff); + vt1603_hw_write(codec, VT1603_REG_RESET, 0x00); + + val = vt1603_hw_read(codec, VT1603_R60); + val &= ~(BIT6 + BIT7); + vt1603_hw_write(codec, VT1603_R60, val); + val |= BIT6 + BIT7; + vt1603_hw_write(codec, VT1603_R60, val); +} + +static void vt1603_codec_init(struct snd_soc_codec *codec) +{ + unsigned int val; + + // vt1603 touch uses irq low active??? + if (vt1603_boardinfo.hp_level) { + //hp high + snd_soc_write(codec, VT1603_R21, 0xfd); + } + else { + //hp low + snd_soc_write(codec, VT1603_R21, 0xff); + } + + // vt1603 codec irq settings + snd_soc_write(codec, VT1603_R1b, 0xff); + snd_soc_write(codec, VT1603_R1b, 0xfd); + snd_soc_write(codec, VT1603_R1c, 0xff); + snd_soc_write(codec, VT1603_R1d, 0xff); + snd_soc_write(codec, VT1603_R24, 0x04); + + snd_soc_write(codec, VT1603_R95, 0x00); + snd_soc_write(codec, VT1603_R60, 0xc7); + snd_soc_write(codec, VT1603_R93, 0x02); + snd_soc_write(codec, VT1603_R7b, 0x10); + snd_soc_write(codec, VT1603_R92, 0x2c); + + // r4 is reserved, I don't know more about it. + snd_soc_write(codec, VT1603_R04, 0x00); + // r9[5] is reserved, I don't know more about it. + val = snd_soc_read(codec, VT1603_R09); + val |= BIT5; + snd_soc_write(codec, VT1603_R09, val); + // r79 is reserved, I don't know more about it. + val = snd_soc_read(codec, VT1603_R79); + val |= BIT2; + val &= ~(BIT0+BIT1); + snd_soc_write(codec, VT1603_R79, val); + // r7a is reserved, I don't know more about it. + val = snd_soc_read(codec, VT1603_R7a); + val |= BIT3+BIT4+BIT7; + snd_soc_write(codec, VT1603_R7a, val); + // r87 is reserved, I don't know more about it. + snd_soc_write(codec, VT1603_R87, 0x90); + // r88 is reserved, I don't know more about it. + snd_soc_write(codec, VT1603_R88, 0x28); + // r8a is reserved, I don't know more about it. + val = snd_soc_read(codec, VT1603_R8a); + val |= BIT1; + snd_soc_write(codec, VT1603_R8a, val); + // enable CLK_SYS, CLK_DSP, CLK_AIF + val = snd_soc_read(codec, VT1603_R40); + val |= BIT4+BIT5+BIT6; + snd_soc_write(codec, VT1603_R40, val); + // set DAC sample rate divier=CLK_SYS/1, DAC clock divider=DAC_CLK_512X + val = snd_soc_read(codec, VT1603_R41); + val &= ~(BIT3+BIT2+BIT1+BIT0); + val |= BIT1;//add for mclk 24.576M, 10Khz 17kHz sine distortion 2014-6-12 + snd_soc_write(codec, VT1603_R41, val); + // set BCLK rate=CLK_SYS/8 + val = snd_soc_read(codec, VT1603_R42); + val |= BIT0+BIT1+BIT2; + val &= ~BIT3; + snd_soc_write(codec, VT1603_R42, val); + // enable VREF_SC_DA buffer + val = snd_soc_read(codec, VT1603_R61); + val |= BIT7; + snd_soc_write(codec, VT1603_R61, val); + // ADC VREF_SC offset voltage + snd_soc_write(codec, VT1603_R93, 0x20); + // set mic bias voltage = 90% * AVDD + val = snd_soc_read(codec, VT1603_R92); + val |= BIT2; + snd_soc_write(codec, VT1603_R92, val); + // enable bypass for AOW0 parameterized HPF + val = snd_soc_read(codec, VT1603_R0a); + val |= BIT6; + snd_soc_write(codec, VT1603_R0a, val); + // CPVEE=2.2uF, the ripple frequency ON CPVEE above 22kHz, that will reduce HP noise + val = snd_soc_read(codec, VT1603_R7a); + val |= BIT6; + val &= ~BIT5; + snd_soc_write(codec, VT1603_R7a, val); + + // set DAC soft mute mode + val = snd_soc_read(codec, VT1603_R0b); + val |= BIT1+BIT2; + snd_soc_write(codec, VT1603_R0b, val); + // DAC High Voltage Switch Control Signal enable??? + val = snd_soc_read(codec, VT1603_R62); + val |= BIT7+BIT6; + val &= ~BIT3; + snd_soc_write(codec, VT1603_R62, val); + // enable microphone bias + val = snd_soc_read(codec, VT1603_R60); + val |= 0x0f; + snd_soc_write(codec, VT1603_R60, val); + // capture pga settings?? + val = snd_soc_read(codec, VT1603_R8e); + val |= 0x0f; + snd_soc_write(codec, VT1603_R8e, val); + // capture pga settings?? + val = snd_soc_read(codec, VT1603_R66); + val |= BIT1+BIT2+BIT3+BIT4; // + snd_soc_write(codec, VT1603_R66, val); + // PGA Zero Cross Detector Enable, Change gain on zero cross only + val = snd_soc_read(codec, VT1603_R64); + val |= BIT0; + snd_soc_write(codec, VT1603_R64, val); + val = snd_soc_read(codec, VT1603_R65); + val |= BIT0; + snd_soc_write(codec, VT1603_R65, val); + // enable hp output mode + val = snd_soc_read(codec, VT1603_R68); + val |= BIT2; + snd_soc_write(codec, VT1603_R68, val); + // enable class-d, unmute channels + snd_soc_write(codec, VT1603_R7c, 0xe0); + + // set DAC gain update + val = snd_soc_read(codec, VT1603_R05); + val |= BIT6+BIT7; + snd_soc_write(codec, VT1603_R05, val); + // set class-d AC boost gain=1.6 + snd_soc_write(codec, VT1603_R97, 0x1c); + val = snd_soc_read(codec, VT1603_R97); + val = (val & (~0x07)) + 0x04; + snd_soc_write(codec, VT1603_R97, val); + // set ADC gain update, enable ADCDAT output + val = snd_soc_read(codec, VT1603_R00); + val |= BIT5+BIT6+BIT7; + snd_soc_write(codec, VT1603_R00, val); + + if (vt1603_boardinfo.out_on) + { + val = snd_soc_read(codec, VT1603_R68); + val &= ~(1<<4); + snd_soc_write(codec, VT1603_R68, val); + + val = snd_soc_read(codec, VT1603_R69); + val |= BIT2 +BIT5; + snd_soc_write(codec, VT1603_R69, val); + + + val = snd_soc_read(codec, VT1603_R25); + val |= BIT1; + snd_soc_write(codec, VT1603_R25, val); + + val = snd_soc_read(codec, VT1603_R90); + val |= BIT5 +BIT3; + snd_soc_write(codec, VT1603_R90, val); + + } + // DA DRC settings + //snd_soc_write(codec, VT1603_R0e, 0x0b); + //snd_soc_write(codec, VT1603_R0f, 0x94); + //snd_soc_write(codec, VT1603_R10, 0x02); + //snd_soc_write(codec, VT1603_R11, 0x00); + //snd_soc_write(codec, VT1603_R12, 0x60); +} + +static int vt1603_reboot_notify(struct notifier_block *this, + unsigned long code, void *unused) +{ + unsigned int val; + struct vt1603_priv *priv = container_of(this, struct vt1603_priv, reboot_notifier); + struct snd_soc_codec *codec = priv->codec; + + printk("vt1603_reboot_notify [%lu]\n", code); + + // power down headphone + val = snd_soc_read(codec, VT1603_R68); + val |= BIT4; + snd_soc_write(codec, VT1603_R68, val); + + // power down class-d + val = snd_soc_read(codec, VT1603_R25); + val &= ~BIT1; + snd_soc_write(codec, VT1603_R25, val); + + return NOTIFY_DONE; +} + +/*----------------------------------------------------------------------*/ + + +/*---------------------codec driver functions---------------------------*/ + +static int vt1603_codec_suspend(struct snd_soc_codec *codec) +{ + struct vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + if (!vt1603_boardinfo.timer_del) { //add 2014-1-22 tvbox + del_timer_sync(&vt1603->timer); + } + return 0; +} + +static int vt1603_codec_resume(struct snd_soc_codec *codec) +{ + struct vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + int reg; + u16 *cache = codec->reg_cache; + unsigned int val; + int state = 0;//switch_get_state(&vt1603->hp_switch); + if (!vt1603_boardinfo.hpd_rm) + state = switch_get_state(&vt1603->hp_switch); + // reset codec + vt1603_codec_reset(codec); + + // restore codec registers + for (reg = 0; reg < ARRAY_SIZE(vt1603_regs); reg++) { + if (reg == VT1603_REG_RESET) + continue; + snd_soc_write(codec, reg, cache[reg]); + } + + // restore headphone irq status + val = vt1603_hw_read(codec, VT1603_R21); + if ((vt1603_boardinfo.hp_level && state) || + (!vt1603_boardinfo.hp_level && !state)) { + val |= BIT1; + snd_soc_write(codec, VT1603_R21, val); + } + else { + val &= ~BIT1; + snd_soc_write(codec, VT1603_R21, val); + } + + // open headphone detect + val = vt1603_hw_read(codec,VT1603_R1b); + val |= BIT1; + snd_soc_write(codec, VT1603_R1b, val); + val &= ~BIT1; + snd_soc_write(codec, VT1603_R1b, val); + + // clear touch interrupt status??? why here??? + snd_soc_write(codec, 0xca, 0x0f); + + if (!vt1603_boardinfo.timer_del) { //add 2014-1-22 tvbox + mod_timer(&vt1603->timer, jiffies + msecs_to_jiffies(50)); + } + + return 0; +} + +static int vt1603_codec_probe(struct snd_soc_codec *codec) +{ + struct vt1603_priv *vt1603; + int ret; + + vt1603 = kzalloc(sizeof(struct vt1603_priv), GFP_KERNEL); + if (vt1603 == NULL) + return -ENOMEM; + + gvt1603_codec = codec;//add 2013-9-2 + snd_soc_codec_set_drvdata(codec, vt1603); + + codec->control_data = dev_get_platdata(codec->dev); + + vt1603->codec = codec; +// need close to use wmt-switch.c 2013-11-27 + if (!vt1603_boardinfo.hpd_rm) { + vt1603->hp_switch.name = "h2w"; + switch_dev_register(&vt1603->hp_switch); + } +//need close + vt1603_codec_reset(codec); + vt1603_codec_init(codec); + vt1603_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + + INIT_WORK(&vt1603->work, vt1603_codec_irq_handle); + if (!vt1603_boardinfo.timer_del) { //add 2014-1-20 tvbox + init_timer(&vt1603->timer); + vt1603->timer.data = (unsigned long)vt1603; + vt1603->timer.function = vt1603_timer_handler; + mod_timer(&vt1603->timer, jiffies + msecs_to_jiffies(2000)); + } + + vt1603->proc = create_proc_read_entry("vt1603_dumpregs", 0666, NULL, + vt1603_readproc, vt1603); + + // register reboot notifier event + vt1603->reboot_notifier.notifier_call = vt1603_reboot_notify; + ret = register_reboot_notifier(&vt1603->reboot_notifier); + if (ret != 0) + pr_err("cannot register reboot notifier (err=%d)\n", ret); + + return 0; +} + +static int vt1603_codec_remove(struct snd_soc_codec *codec) +{ + struct vt1603_priv *vt1603 = snd_soc_codec_get_drvdata(codec); + remove_proc_entry("vt1603_dumpregs", NULL); + if (!vt1603_boardinfo.hpd_rm) { + switch_dev_unregister(&vt1603->hp_switch); + } + del_timer_sync(&vt1603->timer); + vt1603_set_bias_level(codec, SND_SOC_BIAS_OFF); + kfree(vt1603); + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_vt1603 = { + .probe = vt1603_codec_probe, + .remove = vt1603_codec_remove, + .suspend = vt1603_codec_suspend, + .resume = vt1603_codec_resume, + .read = vt1603_read, + .write = vt1603_write, + .set_bias_level = vt1603_set_bias_level, + .reg_cache_size = ARRAY_SIZE(vt1603_regs), + .reg_word_size = sizeof(u16), + .reg_cache_default = vt1603_regs, + + .controls = vt1603_snd_controls, + .num_controls = ARRAY_SIZE(vt1603_snd_controls), + .dapm_widgets = vt1603_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(vt1603_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map), +}; + +static int __devinit vt1603_probe(struct platform_device *pdev) +{ + pr_info("<<<<%s \n", __FUNCTION__); + return snd_soc_register_codec(&pdev->dev, &soc_codec_dev_vt1603, + &vt1603_dai, 1); +} + +static int __devexit vt1603_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver vt1603_codec_driver = { + .driver = { + .name = "vt1603-codec", + .owner = THIS_MODULE, + }, + .probe = vt1603_probe, + .remove = __devexit_p(vt1603_remove), +}; + +static __init int vt1603_init(void) +{ + vt1603_get_boardinfo(); + return platform_driver_register(&vt1603_codec_driver); +} +module_init(vt1603_init); + +static __exit void vt1603_exit(void) +{ + platform_driver_unregister(&vt1603_codec_driver); +} +module_exit(vt1603_exit); + +int vt1603_hwdep_ioctl(u8 rw_flag, u16 offset, u16 value) +{ + struct snd_soc_codec *codec = gvt1603_codec; + if (!codec){ + printk("%s NULL!\n", __FUNCTION__); + return -1; + } + //info("rw_flag=%d, offset=0x%x, value=0x%x", rw_flag, offset, value); + + if (rw_flag) { + if ((offset >= VT1603_R00) && (offset <= VT1603_R97)) { + snd_soc_write(codec, offset, value); + } + else { + printk("write to offset 0x%x is not allowed", offset); + } + return 0; + } + else { + return snd_soc_read(codec, offset); + } +} +EXPORT_SYMBOL(vt1603_hwdep_ioctl); + +MODULE_DESCRIPTION("WMT [ALSA SoC/codec] driver"); +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:vt1603-codec"); diff --git a/ANDROID_3.4.5/sound/soc/codecs/vt1603.h b/ANDROID_3.4.5/sound/soc/codecs/vt1603.h new file mode 100755 index 00000000..ef130f10 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/vt1603.h @@ -0,0 +1,119 @@ +/*++ + * linux/sound/soc/codecs/vt1603.h + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#ifndef _VT1603_H +#define _VT1603_H + +#define BIT0 0x00000001 +#define BIT1 0x00000002 +#define BIT2 0x00000004 +#define BIT3 0x00000008 +#define BIT4 0x00000010 +#define BIT5 0x00000020 +#define BIT6 0x00000040 +#define BIT7 0x00000080 +#define BIT8 0x00000100 + +#define VT1603_REG_RESET 0x15 + +#define VT1603_R00 0x00 +#define VT1603_R01 0x01 +#define VT1603_R02 0x02 +#define VT1603_R03 0x03 +#define VT1603_R04 0x04 +#define VT1603_R05 0x05 +#define VT1603_R06 0x06 +#define VT1603_R07 0x07 +#define VT1603_R08 0x08 +#define VT1603_R09 0x09 +#define VT1603_R0a 0x0a +#define VT1603_R0b 0x0b +#define VT1603_R0c 0x0c +#define VT1603_R0d 0x0d +#define VT1603_R0e 0x0e +#define VT1603_R0f 0x0f +#define VT1603_R10 0x10 +#define VT1603_R11 0x11 +#define VT1603_R12 0x12 +#define VT1603_R13 0x13 +#define VT1603_R15 0x15 +#define VT1603_R19 0x19 +#define VT1603_R1b 0x1b +#define VT1603_R1c 0x1c +#define VT1603_R1d 0x1d +#define VT1603_R20 0x20 +#define VT1603_R21 0x21 +#define VT1603_R23 0x23 +#define VT1603_R24 0x24 +#define VT1603_R25 0x25 +#define VT1603_R28 0x28 +#define VT1603_R29 0x29 +#define VT1603_R2a 0x2a +#define VT1603_R2b 0x2b +#define VT1603_R2c 0x2c +#define VT1603_R2d 0x2d +#define VT1603_R40 0x40 +#define VT1603_R41 0x41 +#define VT1603_R42 0x42 +#define VT1603_R47 0x47 +#define VT1603_R51 0x51 +#define VT1603_R52 0x52 +#define VT1603_R53 0x53 +#define VT1603_R5f 0x5f +#define VT1603_R60 0x60 +#define VT1603_R61 0x61 +#define VT1603_R62 0x62 +#define VT1603_R63 0x63 +#define VT1603_R64 0x64 +#define VT1603_R65 0x65 +#define VT1603_R66 0x66 +#define VT1603_R67 0x67 +#define VT1603_R68 0x68 +#define VT1603_R69 0x69 +#define VT1603_R6a 0x6a +#define VT1603_R6b 0x6b +#define VT1603_R6d 0x6d +#define VT1603_R6e 0x6e +#define VT1603_R70 0x70 +#define VT1603_R71 0x71 +#define VT1603_R72 0x72 +#define VT1603_R73 0x73 +#define VT1603_R77 0x77 +#define VT1603_R79 0x79 +#define VT1603_R7a 0x7a +#define VT1603_R7b 0x7b +#define VT1603_R7c 0x7c +#define VT1603_R82 0x82 +#define VT1603_R87 0x87 +#define VT1603_R88 0x88 +#define VT1603_R8a 0x8a +#define VT1603_R8e 0x8e +#define VT1603_R90 0x90 +#define VT1603_R91 0x91 +#define VT1603_R92 0x92 +#define VT1603_R93 0x93 +#define VT1603_R95 0x95 +#define VT1603_R96 0x96 +#define VT1603_R97 0x97 + +#endif diff --git a/ANDROID_3.4.5/sound/soc/codecs/wm8994.c b/ANDROID_3.4.5/sound/soc/codecs/wm8994.c index f351b933..ab30c796 100644 --- a/ANDROID_3.4.5/sound/soc/codecs/wm8994.c +++ b/ANDROID_3.4.5/sound/soc/codecs/wm8994.c @@ -30,6 +30,8 @@ #include <sound/tlv.h> #include <trace/events/asoc.h> +#include <linux/proc_fs.h> + #include <linux/mfd/wm8994/core.h> #include <linux/mfd/wm8994/registers.h> #include <linux/mfd/wm8994/pdata.h> @@ -46,6 +48,9 @@ #define WM8994_NUM_DRC 3 #define WM8994_NUM_EQ 3 +static int aif1clk_on = 1; +static struct proc_dir_entry *wm8994_proc; + static struct { unsigned int reg; unsigned int mask; @@ -493,6 +498,10 @@ static const char *aif_chan_src_text[] = { "Left", "Right" }; +static const char *aif_mono_text[] = { + "Stereo", "Mono" +}; + static const struct soc_enum aif1adcl_src = SOC_ENUM_SINGLE(WM8994_AIF1_CONTROL_1, 15, 2, aif_chan_src_text); @@ -517,6 +526,9 @@ static const struct soc_enum aif2dacl_src = static const struct soc_enum aif2dacr_src = SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 14, 2, aif_chan_src_text); +static const struct soc_enum aif2dac_mono = + SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 8, 2, aif_mono_text); + static const char *osr_text[] = { "Low Power", "High Performance", }; @@ -527,6 +539,16 @@ static const struct soc_enum dac_osr = static const struct soc_enum adc_osr = SOC_ENUM_SINGLE(WM8994_OVERSAMPLING, 1, 2, osr_text); +static const char *compand_mode_text[] = { + "u-law", "a-law", +}; + +static const struct soc_enum aif2dac_compand_mode = + SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 3, 2, compand_mode_text); + +static const struct soc_enum aif2adc_compand_mode = + SOC_ENUM_SINGLE(WM8994_AIF2_CONTROL_2, 1, 2, compand_mode_text); + static const struct snd_kcontrol_new wm8994_snd_controls[] = { SOC_DOUBLE_R_TLV("AIF1ADC1 Volume", WM8994_AIF1_ADC1_LEFT_VOLUME, WM8994_AIF1_ADC1_RIGHT_VOLUME, @@ -548,6 +570,8 @@ SOC_ENUM("AIF1DACR Source", aif1dacr_src), SOC_ENUM("AIF2DACL Source", aif2dacl_src), SOC_ENUM("AIF2DACR Source", aif2dacr_src), +SOC_ENUM("AIF2DAC Mono", aif2dac_mono), + SOC_DOUBLE_R_TLV("AIF1DAC1 Volume", WM8994_AIF1_DAC1_LEFT_VOLUME, WM8994_AIF1_DAC1_RIGHT_VOLUME, 1, 96, 0, digital_tlv), SOC_DOUBLE_R_TLV("AIF1DAC2 Volume", WM8994_AIF1_DAC2_LEFT_VOLUME, @@ -629,6 +653,15 @@ SOC_SINGLE_TLV("AIF2DAC 3D Stereo Volume", WM8994_AIF2_DAC_FILTERS_2, 10, 15, 0, wm8994_3d_tlv), SOC_SINGLE("AIF2DAC 3D Stereo Switch", WM8994_AIF2_DAC_FILTERS_2, 8, 1, 0), + +SOC_SINGLE("MICBIAS2 Switch", WM8994_POWER_MANAGEMENT_1, 5, 1, 0), +SOC_SINGLE("MICBIAS1 Switch", WM8994_POWER_MANAGEMENT_1, 4, 1, 0), + +SOC_SINGLE("AIF2DAC Compand Switch", WM8994_AIF2_CONTROL_2, 4, 1, 0), +SOC_SINGLE("AIF2ADC Compand Switch", WM8994_AIF2_CONTROL_2, 2, 1, 0), + +SOC_ENUM("AIF2DAC Compand Mode", aif2dac_compand_mode), +SOC_ENUM("AIF2ADC Compand Mode", aif2adc_compand_mode), }; static const struct snd_kcontrol_new wm8994_eq_controls[] = { @@ -1161,9 +1194,9 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w, snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_4, WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA, adc); - snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + /*snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, WM8994_AIF2DACL_ENA | - WM8994_AIF2DACR_ENA, dac); + WM8994_AIF2DACR_ENA, dac);*/ snd_soc_update_bits(codec, WM8994_CLOCKING_1, WM8994_AIF2DSPCLK_ENA | WM8994_SYSDSPCLK_ENA, @@ -1174,11 +1207,11 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w, WM8994_AIF2ADCR_ENA, WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA); - snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + /*snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, WM8994_AIF2DACL_ENA | WM8994_AIF2DACR_ENA, WM8994_AIF2DACL_ENA | - WM8994_AIF2DACR_ENA); + WM8994_AIF2DACR_ENA);*/ break; case SND_SOC_DAPM_POST_PMU: @@ -1190,9 +1223,9 @@ static int aif2clk_ev(struct snd_soc_dapm_widget *w, case SND_SOC_DAPM_PRE_PMD: case SND_SOC_DAPM_POST_PMD: - snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + /*snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, WM8994_AIF2DACL_ENA | - WM8994_AIF2DACR_ENA, 0); + WM8994_AIF2DACR_ENA, 0);*/ snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_4, WM8994_AIF2ADCL_ENA | WM8994_AIF2ADCR_ENA, 0); @@ -1289,10 +1322,14 @@ static int late_disable_ev(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_POST_PMD: if (wm8994->aif1clk_disable) { - aif1clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMD); - snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_PRE_PMD); + if (!aif1clk_on){ + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, WM8994_AIF1CLK_ENA_MASK, 0); - aif1clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMD); + } + aif1clk_ev(w, kcontrol, SND_SOC_DAPM_POST_PMD); + wm8994->aif1clk_disable = 0; } if (wm8994->aif2clk_disable) { @@ -1917,6 +1954,10 @@ static const struct snd_soc_dapm_route intercon[] = { { "AIF2ADC Mux", "AIF2ADCDAT", "AIF2ADCR" }, { "AIF2ADC Mux", "AIF3DACDAT", "AIF3ADCDAT" }, + { "AIF3ADC Mux", "AIF1ADCDAT", "AIF1ADCDAT" }, + { "AIF3ADC Mux", "AIF2ADCDAT", "AIF2ADCDAT" }, + { "AIF3ADC Mux", "AIF2DACDAT", "AIF2ADCDAT" }, + /* DAC1 inputs */ { "DAC1L Mixer", "AIF2 Switch", "AIF2DACL" }, { "DAC1L Mixer", "AIF1.2 Switch", "AIF1DAC2L" }, @@ -2695,7 +2736,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - bclk_rate = params_rate(params) * 2; + bclk_rate = params_rate(params) * 4; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: bclk_rate *= 16; @@ -2780,7 +2821,7 @@ static int wm8994_hw_params(struct snd_pcm_substream *substream, lrclk, bclk_rate / lrclk); snd_soc_update_bits(codec, aif1_reg, WM8994_AIF1_WL_MASK, aif1); - snd_soc_update_bits(codec, aif2_reg, WM8994_AIF1_MONO, aif2); + //snd_soc_update_bits(codec, aif2_reg, WM8994_AIF1_MONO, aif2); snd_soc_update_bits(codec, bclk_reg, WM8994_AIF1_BCLK_DIV_MASK, bclk); snd_soc_update_bits(codec, lrclk_reg, WM8994_AIF1DAC_RATE_MASK, lrclk); @@ -3053,6 +3094,11 @@ static int wm8994_codec_suspend(struct snd_soc_codec *codec) switch (control->type) { case WM8994: snd_soc_update_bits(codec, WM8994_MICBIAS, WM8994_MICD_ENA, 0); + if (aif1clk_on){ + snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1, + WM8994_AIF1CLK_ENA_MASK, 0); + //printk("<<<<%s\n", __FUNCTION__); + } //add 2013-7-11 break; case WM1811: snd_soc_update_bits(codec, WM8994_ANTIPOP_2, @@ -3779,6 +3825,43 @@ static irqreturn_t wm8994_temp_shut(int irq, void *data) return IRQ_HANDLED; } +static int wm8994_readproc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ +#define WM8994_CACHE_SIZE 1570 + + int ret; + unsigned short reg; + struct wm8994_priv *wm8994 = data; + + printk(KERN_INFO "\nwm8994 regs dump:\n"); + printk(KERN_INFO "\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + for (reg = 0; reg < WM8994_CACHE_SIZE; reg++) { + ret = wm8994_reg_read(wm8994->wm8994, reg); + if (ret < 0) { + printk(KERN_INFO "\nwm8994_reg_read reg[%04x] error\n", reg); + goto out; + } + if (reg%4 == 0) printk("\n"); + printk("reg[%04x]=%04x ", reg, ret); + } + + printk("\n\nGPIO Pin settings:\n"); + for (reg = WM8994_GPIO_1; reg < WM8994_GPIO_1+11; reg++) { + ret = wm8994_reg_read(wm8994->wm8994, reg); + if (ret < 0) { + printk(KERN_INFO "\nwm8994_reg_read reg[%04x] error\n", reg); + goto out; + } + if (reg%4 == 0) printk("\n"); + printk("reg[%04x]=%04x ", reg, ret); + } + +out: + printk(KERN_INFO "\n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n\n"); + return 0; +} + static int wm8994_codec_probe(struct snd_soc_codec *codec) { struct wm8994 *control = dev_get_drvdata(codec->dev->parent); @@ -4145,6 +4228,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; } + snd_soc_update_bits(codec, WM8994_POWER_MANAGEMENT_5, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA, + WM8994_AIF2DACL_ENA | + WM8994_AIF2DACR_ENA); + + wm8994_proc = create_proc_read_entry("wm8994_dumpregs", 0666, NULL, + wm8994_readproc, wm8994); + return 0; err_irq: diff --git a/ANDROID_3.4.5/sound/soc/codecs/wmt_fm34.c b/ANDROID_3.4.5/sound/soc/codecs/wmt_fm34.c new file mode 100755 index 00000000..cee4dce9 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/wmt_fm34.c @@ -0,0 +1,830 @@ +/*++ + * linux/sound/soc/codecs/wmt_fm34.c + * WonderMedia echo cancellation driver + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#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/version.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> + +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/kdev_t.h> +#include <asm/page.h> +#include <linux/cdev.h> +#include <linux/device.h> + +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> + +#define FM34_DEVID 0x501a +#define FM34_I2C_BUS_NR 4 + +#define FM34_CTRL_NAME "wmt_echo" +static int fm34_ctrl_major = 0; +static struct cdev fm34_ctrl_dev; +static struct class *fm34_ctrl_class; + +// FM34 control interface commands +#define FM34_CMD_ECHO _IOWR('E', 0x01, int) +#define FM34_CMD_BYPASS _IOWR('E', 0x02, int) +#define FM34_CMD_INCALL_MAIN_MIC _IOWR('E', 0x04, int) +#define FM34_CMD_INCALL_HEADSET_MIC _IOWR('E', 0x08, int) +#define FM34_CMD_CAPTURE_MAIN_MIC _IOWR('E', 0x10, int) +#define FM34_CMD_CAPTURE_HEADSET_MIC _IOWR('E', 0x20, int) + +enum FM34_MODE_TYPE { + FM34_MODE_ECHO, + FM34_MODE_BYPASS, + FM34_MODE_MAX, +}; + +enum FM34_MIC_SOURCE { + FM34_INCALL_MIC_MAIN, + FM34_INCALL_MIC_HEADSET, + FM34_CAPTURE_MIC_MAIN, + FM34_CAPTURE_MIC_HEADSET, + FM34_MIC_MAX, +}; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +struct fm34_ctrl_gpo { + int enable; + int gpio; + int active; +}; + +static struct fm34_ctrl_gpo fm34_reset; +static struct fm34_ctrl_gpo fm34_powerdown; +static struct fm34_ctrl_gpo fm34_bypass; + +struct fm34 { + int (*read_dev)(struct fm34 *fm34, + const u8 wdata[], int wsize, + u8 rdata[], int rsize); + int (*write_dev)(struct fm34 *fm34, const u8 wdata[], int wsize); + void *control_data; + struct device *dev; + struct delayed_work delaywork; +}; + +static struct fm34 *fm34_context; +static enum FM34_MODE_TYPE fm_mode = FM34_MODE_MAX; +static enum FM34_MIC_SOURCE fm_mic = FM34_MIC_MAX; + +static const u8 wmt_fm34_incall_main_mic[] = { + 0xFC, 0xF3, 0x3B, 0x22, 0xC0, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x22, 0xC1, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x22, 0xC2, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xC3, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xC6, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC7, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC8, 0x00, 0x2C, + 0xFC, 0xF3, 0x3B, 0x22, 0xD2, 0x12, 0x94, + 0xFC, 0xF3, 0x3B, 0x22, 0xEE, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF2, 0x00, 0x40, + 0xFC, 0xF3, 0x3B, 0x22, 0xF6, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xF8, 0x80, 0x05, + 0xFC, 0xF3, 0x3B, 0x22, 0xF9, 0x20, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xFA, 0x22, 0x8B, + 0xFC, 0xF3, 0x3B, 0x23, 0x01, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x02, 0x01, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x03, 0x09, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x04, 0x03, 0x1E, + 0xFC, 0xF3, 0x3B, 0x23, 0x05, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x07, 0xF0, 0xF0, + 0xFC, 0xF3, 0x3B, 0x23, 0x08, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0A, 0x1B, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0C, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x48, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x49, 0x06, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6E, 0x20, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6F, 0x0A, 0x0A, + 0xFC, 0xF3, 0x3B, 0x23, 0x70, 0x0F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x73, 0x22, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x74, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x75, 0x12, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x77, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x7B, 0x00, 0x08, + 0xFC, 0xF3, 0x3B, 0x23, 0x86, 0x48, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x8C, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9C, 0x60, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9D, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9E, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xBD, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCE, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCF, 0x0A, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD0, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD1, 0x02, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD2, 0x05, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xED, 0x05, 0x40, + 0xFC, 0xF3, 0x3B, 0x23, 0xEE, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD5, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x80, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x23, 0x81, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x82, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x83, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x84, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x23, 0x85, 0x00, 0x05, + 0xFC, 0xF3, 0x3B, 0x23, 0x8E, 0x70, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x90, 0xA9, 0x87, + 0xFC, 0xF3, 0x3B, 0x23, 0x91, 0x76, 0x54, + 0xFC, 0xF3, 0x3B, 0x23, 0x93, 0x44, 0x55, + 0xFC, 0xF3, 0x3B, 0x23, 0x94, 0x55, 0x66, + 0xFC, 0xF3, 0x3B, 0x23, 0x95, 0x66, 0x77, + 0xFC, 0xF3, 0x3B, 0x23, 0x96, 0x20, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x97, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x98, 0x00, 0x20, + 0xFC, 0xF3, 0x3B, 0x23, 0xA5, 0x00, 0x06, + 0xFC, 0xF3, 0x3B, 0x23, 0xA8, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xA9, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAA, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAB, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAC, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAD, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAE, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAF, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB0, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB1, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x09, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB3, 0x00, 0x20, + 0xFC, 0xF3, 0x3B, 0x23, 0xB4, 0x00, 0x18, + 0xFC, 0xF3, 0x3B, 0x23, 0x10, 0x48, 0x90, + 0xFC, 0xF3, 0x3B, 0x23, 0x2F, 0x01, 0x40, + 0xFC, 0xF3, 0x3B, 0x23, 0x32, 0x00, 0x30, + 0xFC, 0xF3, 0x3B, 0x23, 0x33, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x23, 0x37, 0x00, 0x02, + 0xFC, 0xF3, 0x3B, 0x23, 0x39, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x22, 0xFB, 0x00, 0x00 +}; + +static const u8 wmt_fm34_incall_headset_mic[] = { + 0xFC, 0xF3, 0x3B, 0x22, 0xC0, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x22, 0xC1, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x22, 0xC2, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xC3, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xC6, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC7, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC8, 0x00, 0x2C, + 0xFC, 0xF3, 0x3B, 0x22, 0xD2, 0x12, 0x94, + 0xFC, 0xF3, 0x3B, 0x22, 0xEE, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF2, 0x00, 0x40, + 0xFC, 0xF3, 0x3B, 0x22, 0xF6, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xF8, 0x80, 0x05, + 0xFC, 0xF3, 0x3B, 0x22, 0xF9, 0x20, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xFA, 0x22, 0x8B, + 0xFC, 0xF3, 0x3B, 0x23, 0x01, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x02, 0x01, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x03, 0x09, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x04, 0x03, 0x1E, + 0xFC, 0xF3, 0x3B, 0x23, 0x05, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x07, 0xF0, 0xF0, + 0xFC, 0xF3, 0x3B, 0x23, 0x08, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0A, 0x1A, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0C, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x48, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x49, 0x06, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6E, 0x20, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6F, 0x0A, 0x0A, + 0xFC, 0xF3, 0x3B, 0x23, 0x70, 0x0F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x73, 0x22, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x74, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x75, 0x12, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x77, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x7B, 0x00, 0x08, + 0xFC, 0xF3, 0x3B, 0x23, 0x86, 0x48, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x8C, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9C, 0x60, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9D, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x9E, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xBD, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCE, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCF, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD0, 0x06, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD1, 0x02, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD2, 0x05, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xED, 0x05, 0x40, + 0xFC, 0xF3, 0x3B, 0x23, 0xEE, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD5, 0x20, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x80, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x23, 0x81, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x82, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x83, 0x04, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x84, 0x00, 0x04, + 0xFC, 0xF3, 0x3B, 0x23, 0x85, 0x00, 0x05, + 0xFC, 0xF3, 0x3B, 0x23, 0x8E, 0x70, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x90, 0xA9, 0x87, + 0xFC, 0xF3, 0x3B, 0x23, 0x91, 0x76, 0x54, + 0xFC, 0xF3, 0x3B, 0x23, 0x93, 0x44, 0x55, + 0xFC, 0xF3, 0x3B, 0x23, 0x94, 0x55, 0x66, + 0xFC, 0xF3, 0x3B, 0x23, 0x95, 0x66, 0x77, + 0xFC, 0xF3, 0x3B, 0x23, 0x96, 0x20, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x97, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x98, 0x00, 0x20, + 0xFC, 0xF3, 0x3B, 0x23, 0xA5, 0x00, 0x06, + 0xFC, 0xF3, 0x3B, 0x23, 0xA8, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xA9, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAA, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAB, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAC, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAD, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAE, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xAF, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB0, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB1, 0x7F, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x09, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xB3, 0x00, 0x06, + 0xFC, 0xF3, 0x3B, 0x23, 0xB4, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x10, 0x48, 0x90, + 0xFC, 0xF3, 0x3B, 0x23, 0x2F, 0x01, 0x40, + 0xFC, 0xF3, 0x3B, 0x23, 0x32, 0x00, 0x30, + 0xFC, 0xF3, 0x3B, 0x23, 0x33, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x23, 0x37, 0x00, 0x02, + 0xFC, 0xF3, 0x3B, 0x23, 0x39, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x22, 0xFB, 0x00, 0x00 +}; + +static const u8 wmt_fm34_capture_main_mic[] = { + 0xFC, 0xF3, 0x3B, 0x22, 0x9F, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x22, 0xC6, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC7, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC8, 0x00, 0x2C, + 0xFC, 0xF3, 0x3B, 0x22, 0xEE, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF2, 0x00, 0x40, + 0xFC, 0xF3, 0x3B, 0x22, 0xF6, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xF7, 0x01, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF8, 0x80, 0x02, + 0xFC, 0xF3, 0x3B, 0x22, 0xF9, 0x67, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xFA, 0x23, 0x8B, + 0xFC, 0xF3, 0x3B, 0x23, 0x01, 0x00, 0x02, + 0xFC, 0xF3, 0x3B, 0x23, 0x02, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x03, 0x29, 0x88, + 0xFC, 0xF3, 0x3B, 0x23, 0x04, 0x20, 0x0E, + 0xFC, 0xF3, 0x3B, 0x23, 0x05, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x07, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x08, 0x0C, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0A, 0x1B, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0C, 0x02, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x48, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6E, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x23, 0x6F, 0x0B, 0x05, + 0xFC, 0xF3, 0x3B, 0x23, 0x70, 0x0C, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x73, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x74, 0x21, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x75, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x7B, 0x00, 0x08, + 0xFC, 0xF3, 0x3B, 0x23, 0x82, 0x05, 0x80, + 0xFC, 0xF3, 0x3B, 0x23, 0x83, 0x03, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x84, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x23, 0x86, 0x48, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x8C, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x90, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x91, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x92, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x93, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0xBD, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCE, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCF, 0x12, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD0, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD1, 0x07, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD2, 0x05, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD5, 0x70, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xED, 0x0A, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xEE, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x28, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xF1, 0xD0, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xFB, 0x00, 0x00 +}; + +static const u8 wmt_fm34_capture_headset_mic[] = { + 0xFC, 0xF3, 0x3B, 0x22, 0x9F, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x22, 0xC6, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC7, 0x00, 0x0C, + 0xFC, 0xF3, 0x3B, 0x22, 0xC8, 0x00, 0x2C, + 0xFC, 0xF3, 0x3B, 0x22, 0xEE, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF2, 0x00, 0x40, + 0xFC, 0xF3, 0x3B, 0x22, 0xF6, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x22, 0xF7, 0x01, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xF8, 0x80, 0x02, + 0xFC, 0xF3, 0x3B, 0x22, 0xF9, 0x67, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xFA, 0x23, 0x8B, + 0xFC, 0xF3, 0x3B, 0x23, 0x01, 0x00, 0x02, + 0xFC, 0xF3, 0x3B, 0x23, 0x02, 0x00, 0x01, + 0xFC, 0xF3, 0x3B, 0x23, 0x03, 0x29, 0x88, + 0xFC, 0xF3, 0x3B, 0x23, 0x04, 0x20, 0x0E, + 0xFC, 0xF3, 0x3B, 0x23, 0x05, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x07, 0x00, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x08, 0x0C, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0A, 0x1A, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x0C, 0x02, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x48, 0x08, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x6E, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x23, 0x6F, 0x0B, 0x05, + 0xFC, 0xF3, 0x3B, 0x23, 0x70, 0x0C, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x73, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x74, 0x21, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x75, 0x18, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x7B, 0x00, 0x08, + 0xFC, 0xF3, 0x3B, 0x23, 0x82, 0x05, 0x80, + 0xFC, 0xF3, 0x3B, 0x23, 0x83, 0x03, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x84, 0x00, 0x03, + 0xFC, 0xF3, 0x3B, 0x23, 0x86, 0x48, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x8C, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x90, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x91, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x92, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0x93, 0x44, 0x44, + 0xFC, 0xF3, 0x3B, 0x23, 0xBD, 0x30, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCE, 0x40, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xCF, 0x12, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD0, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD1, 0x07, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD2, 0x05, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xD5, 0x70, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xED, 0x0A, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0xEE, 0x10, 0x00, + 0xFC, 0xF3, 0x3B, 0x23, 0x28, 0x7F, 0xFF, + 0xFC, 0xF3, 0x3B, 0x22, 0xF1, 0xD0, 0x00, + 0xFC, 0xF3, 0x3B, 0x22, 0xFB, 0x00, 0x00 +}; + +static int fm34_check_functionality(struct fm34 *fm34) +{ + u8 rdata1, rdata2; + int devid = 0; + const u8 wdata1[] = { + 0xFC, 0xF3, 0x27, 0x37, 0xFF + }; + const u8 wdata2[] = { + 0xFC, 0xF3, 0x60, 0x25 + }; + const u8 wdata3[] = { + 0xFC, 0xF3, 0x60, 0x26 + }; + + if (fm34->write_dev(fm34, wdata1, sizeof(wdata1))) + return 0; + + if (fm34->read_dev(fm34, wdata2, sizeof(wdata2), &rdata1, 1) || + fm34->read_dev(fm34, wdata3, sizeof(wdata3), &rdata2, 1)) + return 0; + + devid = (rdata2 << 8) | rdata1; + printk("-->fm34_check_functionality: %x\n", devid); + if (devid == FM34_DEVID) + return 1; + + return 0; +} + +static int fm34_i2c_write(struct fm34 *fm34, const u8 wdata[], int wsize) +{ + int ret; + struct i2c_client *i2c = fm34->control_data; + struct i2c_msg xfer[1]; + + /* + * [MSG1]: fill the register address data + * fill the data Tx buffer + */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0 ; + xfer[0].flags &= ~(I2C_M_RD); + xfer[0].buf = (__u8 *)wdata; + xfer[0].len = wsize; + + /* i2c_transfer returns number of messages transferred */ + ret = i2c_transfer(i2c->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) { + pr_err("fm34_i2c_write err[%d]\n", ret); + if (ret < 0) + return ret; + else + return -EIO; + } else + return 0; +} + +static int fm34_i2c_read(struct fm34 *fm34, + const u8 wdata[], int wsize, + u8 rdata[], int rsize) +{ + int ret; + struct i2c_client *i2c = fm34->control_data; + struct i2c_msg xfer[2]; + + /* [MSG1] fill the register address data */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 2; /* Read the register val */ + xfer[0].buf = (__u8 *)wdata; + xfer[0].len = wsize; + + /* [MSG2] fill the data rx buffer */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; /* Read the register val */ + xfer[1].len = rsize; /* only n bytes */ + xfer[1].buf = rdata; + + /* i2c_transfer returns number of messages transferred */ + ret = i2c_transfer(i2c->adapter, xfer, ARRAY_SIZE(xfer)); + if (ret != ARRAY_SIZE(xfer)) { + pr_err("vt1603_i2c_read err[%d]\n", ret); + if (ret < 0) + return ret; + else + return -EIO; + } else { + return 0; + } +} + +static void fm34_timing_init(void) +{ + // pwdn high to power up fm34 module + if (fm34_powerdown.enable) { + gpio_set_value_cansleep(fm34_powerdown.gpio, !!fm34_powerdown.active); + msleep(100); + } + + // reset fm34 module + if (fm34_reset.enable) { + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(10); + gpio_set_value_cansleep(fm34_reset.gpio, !fm34_reset.active); + msleep(10); + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(100); + } + + // disable fm34 bypass + if (fm34_bypass.enable) { + gpio_set_value_cansleep(fm34_bypass.gpio, !!fm34_bypass.active); + } +} + +static void fm34_do_work(struct work_struct *work) +{ + struct fm34 *fm34 = container_of(work, struct fm34, delaywork.work); + + fm34_timing_init(); + + if (fm34_check_functionality(fm34) == 0) { + pr_err("fm34_check_functionality: wrong device id\n"); + } + + fm34->write_dev(fm34, wmt_fm34_incall_main_mic, sizeof(wmt_fm34_incall_main_mic)); + //fm_mode = FM34_MODE_ECHO; + fm_mic = FM34_INCALL_MIC_MAIN; +} + +static int __devinit fm34_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct fm34 *fm34; + int ret; + + if (i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C) == 0) { + pr_err("i2c_check_functionality: can't talk I2C?\n"); + return -EIO; + } + + fm34 = kzalloc(sizeof(struct fm34), GFP_KERNEL); + if (fm34 == NULL) + return -ENOMEM; + + fm34->dev = &i2c->dev; + fm34->control_data = i2c; + fm34->read_dev = fm34_i2c_read; + fm34->write_dev = fm34_i2c_write; + + i2c_set_clientdata(i2c, fm34); + dev_set_drvdata(fm34->dev, fm34); + + INIT_DELAYED_WORK(&fm34->delaywork, fm34_do_work); + + if (fm34_powerdown.enable && gpio_is_valid(fm34_powerdown.gpio)) { + ret = gpio_request(fm34_powerdown.gpio, "FM34-powerdown"); + if (ret < 0){ + dev_warn(fm34->dev, "cann't register fm34_powerdown.gpio-%d\n", fm34_powerdown.gpio); + fm34_powerdown.enable = 0; + } + else + gpio_direction_output(fm34_powerdown.gpio, 0); + } + + if (fm34_reset.enable && gpio_is_valid(fm34_reset.gpio)) { + ret = gpio_request(fm34_reset.gpio, "FM34-reset"); + if (ret < 0) { + dev_warn(fm34->dev, "cann't register fm34_reset.gpio-%d\n", fm34_reset.gpio); + fm34_reset.enable = 0; + } + else + gpio_direction_output(fm34_reset.gpio, 0); + } + + if (fm34_bypass.enable && gpio_is_valid(fm34_bypass.gpio)) { + ret = gpio_request(fm34_bypass.gpio, "FM34-bypass"); + if (ret < 0) { + dev_warn(fm34->dev, "cann't register fm34_bypass.gpio-%d\n", fm34_bypass.gpio); + fm34_bypass.enable = 0; + } + else + gpio_direction_output(fm34_bypass.gpio, 0); + } + + fm34_timing_init(); + + if (fm34_check_functionality(fm34) == 0) { + pr_err("fm34_check_functionality: wrong device id\n"); + return -EIO; + } + + fm34_context = fm34; + + /* write the firmware to device */ + fm34->write_dev(fm34, wmt_fm34_incall_main_mic, sizeof(wmt_fm34_incall_main_mic)); + fm_mode = FM34_MODE_ECHO; // default is echo mode + fm_mic = FM34_INCALL_MIC_MAIN; + return 0; +} + +static int __devexit fm34_i2c_remove(struct i2c_client *i2c) +{ + struct fm34 *fm34 = i2c_get_clientdata(i2c); + + if (fm34_powerdown.enable) { + gpio_free(fm34_powerdown.gpio); + } + if (fm34_reset.enable) { + gpio_free(fm34_reset.gpio); + } + if (fm34_bypass.enable) { + gpio_free(fm34_bypass.gpio); + } + + kfree(fm34); + return 0; +} + +static int fm34_suspend(struct i2c_client *i2c, pm_message_t mesg) +{ + struct fm34 *fm34 = i2c_get_clientdata(i2c); + cancel_delayed_work_sync(&fm34->delaywork); + return 0; +} +static int fm34_resume(struct i2c_client *i2c) +{ + struct fm34 *fm34 = i2c_get_clientdata(i2c); + schedule_delayed_work(&fm34->delaywork, msecs_to_jiffies(2000)); + return 0; +} + +static const struct i2c_device_id fm34_i2c_id[] = { + { "wmt-fm34", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, fm34_i2c_id); + +static struct i2c_driver fm34_i2c_driver = { + .driver = { + .name = "wmt-fm34", + .owner = THIS_MODULE, + }, + .probe = fm34_i2c_probe, + .remove = __devexit_p(fm34_i2c_remove), + .suspend = fm34_suspend, + .resume = fm34_resume, + .id_table = fm34_i2c_id, +}; + +static struct i2c_board_info fm34_i2c_board_info = { + I2C_BOARD_INFO("wmt-fm34", 0xc0>>1), +}; + +static int fm34_ctrl_open (struct inode *inode, struct file *filp) +{ + printk("%s %d\n", __func__, __LINE__); + return 0; +} + +static int fm34_ctrl_release(struct inode *inode, struct file *filp) +{ + printk("%s %d\n", __func__, __LINE__); + return 0; +} + +static ssize_t fm34_ctrl_read(struct file *filp, char __user *buf, size_t size, loff_t *offp) +{ + printk("%s %d\n", __func__, __LINE__); + return size; +} + +static long fm34_ctrl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + const u8 echo[] = { 0xFC, 0xF3, 0x3B, 0x23, 0x00, 0x00, 0x00 }; + const u8 bypass[] = { 0xFC, 0xF3, 0x3B, 0x23, 0x00, 0x00, 0x01 }; + //const u8 main_mic[] = { 0xFC, 0xF3, 0x3B, 0x23, 0x0a, 0x1a, 0x00 }; + //const u8 headset_mic[] = { 0xFC, 0xF3, 0x3B, 0x23, 0x0a, 0x1b, 0x00 }; + + switch (cmd) { + case FM34_CMD_ECHO: + if (fm_mode != FM34_MODE_ECHO) { + printk("FM34 cmd: echo\n"); + if (fm34_context->write_dev(fm34_context, echo, sizeof(echo))) + return -EIO; + fm_mode = FM34_MODE_ECHO; + } + break; + + case FM34_CMD_BYPASS: + if (fm_mode != FM34_MODE_BYPASS) { + printk("FM34 cmd: bypass\n"); + if (fm34_context->write_dev(fm34_context, bypass, sizeof(bypass))) + return -EIO; + fm_mode = FM34_MODE_BYPASS; + } + break; + + case FM34_CMD_INCALL_MAIN_MIC: + if (fm_mic != FM34_INCALL_MIC_MAIN) { + printk("FM34 cmd: incall main-mic\n"); + if (fm34_reset.enable) { + gpio_set_value_cansleep(fm34_reset.gpio, !fm34_reset.active); + msleep(20); + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(20); + } + if (fm34_context->write_dev(fm34_context, wmt_fm34_incall_main_mic, sizeof(wmt_fm34_incall_main_mic))) + return -EIO; + fm_mic = FM34_INCALL_MIC_MAIN; + } + break; + + case FM34_CMD_INCALL_HEADSET_MIC: + if (fm_mic != FM34_INCALL_MIC_HEADSET) { + printk("FM34 cmd: incall headset-mic\n"); + if (fm34_reset.enable) { + gpio_set_value_cansleep(fm34_reset.gpio, !fm34_reset.active); + msleep(20); + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(20); + } + if (fm34_context->write_dev(fm34_context, wmt_fm34_incall_headset_mic, sizeof(wmt_fm34_incall_headset_mic))) + return -EIO; + fm_mic = FM34_INCALL_MIC_HEADSET; + } + break; + + case FM34_CMD_CAPTURE_MAIN_MIC: + if (fm_mic != FM34_CMD_CAPTURE_MAIN_MIC) { + printk("FM34 cmd: capture main-mic\n"); + if (fm34_reset.enable) { + gpio_set_value_cansleep(fm34_reset.gpio, !fm34_reset.active); + msleep(20); + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(20); + } + if (fm34_context->write_dev(fm34_context, wmt_fm34_capture_main_mic, sizeof(wmt_fm34_capture_main_mic))) + return -EIO; + fm_mic = FM34_CMD_CAPTURE_MAIN_MIC; + } + break; + + case FM34_CMD_CAPTURE_HEADSET_MIC: + if (fm_mic != FM34_CMD_CAPTURE_HEADSET_MIC) { + printk("FM34 cmd: capture headset-mic\n"); + if (fm34_reset.enable) { + gpio_set_value_cansleep(fm34_reset.gpio, !fm34_reset.active); + msleep(20); + gpio_set_value_cansleep(fm34_reset.gpio, !!fm34_reset.active); + msleep(20); + } + if (fm34_context->write_dev(fm34_context, wmt_fm34_capture_headset_mic, sizeof(wmt_fm34_capture_headset_mic))) + return -EIO; + fm_mic = FM34_CMD_CAPTURE_HEADSET_MIC; + } + break; + + default: + printk(KERN_ERR "FM34: Not support cmd[%d]\n", cmd); + break; + } + + return 0; +} + +static struct file_operations fm34_ctrl_ops = { + .owner = THIS_MODULE, + .open = fm34_ctrl_open, + .release = fm34_ctrl_release, + .read = fm34_ctrl_read, + .unlocked_ioctl = fm34_ctrl_ioctl, +}; + +static int fm34_ctrl_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops) +{ + int ret, devno = MKDEV(fm34_ctrl_major, minor); + cdev_init(dev, fops); + dev->owner = THIS_MODULE; + dev->ops = fops; + ret = cdev_add(dev, devno, 1); + if (ret) + printk(KERN_ERR "Error %d adding wmt_echo %d", ret, minor); + return ret; +} + +static int __init fm34_init(void) +{ + int ret = -1; + int len = 64; + char buf[64]; + dev_t dev; + struct i2c_adapter *adapter; + struct i2c_client *client; + + ret = wmt_getsyspara("wmt.echo.cancellation", buf, &len); + if (ret == 0) { + sscanf(buf, "fm34:[%d:%d:%d]:[%d:%d:%d]:[%d:%d:%d]", + &fm34_powerdown.enable, &fm34_powerdown.gpio, &fm34_powerdown.active, + &fm34_reset.enable, &fm34_reset.gpio, &fm34_reset.active, + &fm34_bypass.enable, &fm34_bypass.gpio, &fm34_bypass.active); + } + else { + fm34_reset.enable = 0; + fm34_powerdown.enable = 0; + fm34_bypass.enable = 0; + } + + ret = alloc_chrdev_region(&dev, 0, 1, FM34_CTRL_NAME); + if (ret == 0) + fm34_ctrl_major = MAJOR(dev); + else + printk(KERN_ERR "fm34: alloc_chrdev_region failed!\n"); + + ret = fm34_ctrl_setup_cdev(&fm34_ctrl_dev, 0, &fm34_ctrl_ops); + if (ret == 0) { + fm34_ctrl_class = class_create(THIS_MODULE, FM34_CTRL_NAME); + if (IS_ERR(fm34_ctrl_class)) { + printk("fm34: class_create failed!\n"); + } + else + device_create(fm34_ctrl_class, NULL, MKDEV(fm34_ctrl_major, 0), NULL, FM34_CTRL_NAME); + } + + adapter = i2c_get_adapter(FM34_I2C_BUS_NR); + client = i2c_new_device(adapter, &fm34_i2c_board_info); + i2c_put_adapter(adapter); + + ret = i2c_add_driver(&fm34_i2c_driver); + if (ret != 0) + pr_err("Failed to register FM34 I2C driver: %d\n", ret); + return ret; +} +module_init(fm34_init); + +static void __exit fm34_exit(void) +{ + cdev_del(&fm34_ctrl_dev); + device_destroy(fm34_ctrl_class, MKDEV(fm34_ctrl_major, 0)); + class_destroy(fm34_ctrl_class); + unregister_chrdev_region(MKDEV(fm34_ctrl_major, 0), 1); + + i2c_del_driver(&fm34_i2c_driver); +} +module_exit(fm34_exit); + +MODULE_DESCRIPTION("WMT [ALSA SoC/echocancellation] driver"); +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/codecs/wmt_hwdac.c b/ANDROID_3.4.5/sound/soc/codecs/wmt_hwdac.c new file mode 100755 index 00000000..ae31bb5e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/wmt_hwdac.c @@ -0,0 +1,195 @@ +/*++ + * linux/sound/soc/codecs/wmt_hwdac.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/initval.h> + + +/* + * Debug + */ +#define AUDIO_NAME "HW_DAC" +//#define WMT_HWDAC_DEBUG 1 +//#define WMT_HWDAC_DEBUG_DETAIL 1 + +#ifdef WMT_HWDAC_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_HWDAC_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + + +static int hwdac_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + DBG_DETAIL(); + + return 0; +} + + +#define HWDAC_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define HWDAC_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_FLOAT) + +static struct snd_soc_dai_ops hwdac_dai_ops = { + .hw_params = hwdac_pcm_hw_params, +}; + +struct snd_soc_dai_driver hwdac_dai = { + .name = "HWDAC", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = HWDAC_RATES, + .formats = HWDAC_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = HWDAC_RATES, + .formats = HWDAC_FORMATS, + }, + .ops = &hwdac_dai_ops, +}; + +static int hwdac_probe(struct snd_soc_codec *codec) +{ + DBG_DETAIL(); + + return 0; +} + +/* remove everything here */ +static int hwdac_remove(struct snd_soc_codec *codec) +{ + DBG_DETAIL(); + + return 0; +} + +struct snd_soc_codec_driver soc_codec_dev_hwdac = { + .probe = hwdac_probe, + .remove = hwdac_remove, +}; + +static int __devinit hwdac_platform_probe(struct platform_device *pdev) +{ + int ret = 0; + + DBG_DETAIL(); + + ret = snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_hwdac, &hwdac_dai, 1); + + if (ret) + info("snd_soc_register_codec fail"); + + return ret; +} + +static int __devexit hwdac_platform_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver wmt_hwdac_driver = { + .driver = { + .name = "wmt-i2s-hwdac", + .owner = THIS_MODULE, + }, + + .probe = hwdac_platform_probe, + .remove = __devexit_p(hwdac_platform_remove), +}; + +static int __init wmt_hwdac_platform_init(void) +{ + char buf[80]; + int varlen = 80; + char codec_name[5]; + int ret = 0; + + DBG_DETAIL(); + + /*ret = wmt_getsyspara("wmt.audio.i2s", buf, &varlen); + + if (ret == 0) { + sscanf(buf, "%5s", codec_name); + + if (strcmp(codec_name, "hwdac")) { + info("hwdac string not found"); + return -EINVAL; + } + }*/ + + return platform_driver_register(&wmt_hwdac_driver); +} +module_init(wmt_hwdac_platform_init); + +static void __exit wmt_hwdac_platform_exit(void) +{ + DBG_DETAIL(); + + platform_driver_unregister(&wmt_hwdac_driver); +} +module_exit(wmt_hwdac_platform_exit); + +MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.c b/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.c new file mode 100755 index 00000000..fd625c95 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.c @@ -0,0 +1,981 @@ +/*++ + * linux/sound/soc/codecs/wmt_vt1602.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#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/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 <sound/tlv.h> + +#include "wmt_vt1602.h" + +#define VT1602_VERSION "0.20" +#define I2C_DRIVERID_VT1602 90 + +/* + * Debug + */ +#define AUDIO_NAME "VT1602" +//#define WMT_VT1602_DEBUG 1 +//#define WMT_VT1602_DEBUG_DETAIL 1 + +#ifdef WMT_VT1602_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_VT1602_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +/* codec private data */ +struct vt1602_priv { + unsigned int sysclk; + enum snd_soc_control_type control_type; + struct snd_pcm_substream *master_substream; + struct snd_pcm_substream *slave_substream; +}; + +/* + * vt1602 register cache + * We can't read the VT1602 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 vt1602_reg[] = { + 0x0050, 0x0050, 0x007f, 0x007f, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00fc, 0x00fc, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0100, 0x0000, /* 32 */ + 0x0000, 0x0100, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +#define vt1602_reset(c) snd_soc_write(c, VT1602_RESET, 0) + +/* + * VT1602 Controls + */ +static const char *vt1602_bass[] = {"Linear Control", "Adaptive Boost"}; +static const char *vt1602_bass_filter[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const char *vt1602_treble[] = {"8kHz", "4kHz"}; +static const char *vt1602_3d_lc[] = {"200Hz", "500Hz"}; +static const char *vt1602_3d_uc[] = {"2.2kHz", "1.5kHz"}; +static const char *vt1602_3d_func[] = {"Capture", "Playback"}; +static const char *vt1602_alc_func[] = {"Off", "Right", "Left", "Stereo"}; +static const char *vt1602_ng_type[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const char *vt1602_line_mux[] = {"Line 1", "Line 2", "Line 3", "PGA", + "Differential"}; +static const char *vt1602_pga_sel[] = {"Line 1", "Line 2", "Line 3", + "Differential"}; +static const char *vt1602_out3[] = {"VREF", "ROUT1 + Vol", "MonoOut", + "ROUT1"}; +static const char *vt1602_diff_sel[] = {"Line 1", "Line 2"}; +static const char *vt1602_adcpol[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const char *vt1602_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *vt1602_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; + +static const struct soc_enum vt1602_enum[] = { +SOC_ENUM_SINGLE(VT1602_BASS, 7, 2, vt1602_bass), +SOC_ENUM_SINGLE(VT1602_BASS, 6, 2, vt1602_bass_filter), +SOC_ENUM_SINGLE(VT1602_TREBLE, 6, 2, vt1602_treble), +SOC_ENUM_SINGLE(VT1602_3D, 5, 2, vt1602_3d_lc), +SOC_ENUM_SINGLE(VT1602_3D, 6, 2, vt1602_3d_uc), +SOC_ENUM_SINGLE(VT1602_3D, 7, 2, vt1602_3d_func), +SOC_ENUM_SINGLE(VT1602_ALC1, 7, 4, vt1602_alc_func), +SOC_ENUM_SINGLE(VT1602_NGATE, 1, 2, vt1602_ng_type), +SOC_ENUM_SINGLE(VT1602_LOUTM1, 0, 5, vt1602_line_mux), +SOC_ENUM_SINGLE(VT1602_ROUTM1, 0, 5, vt1602_line_mux), +SOC_ENUM_SINGLE(VT1602_LADCIN, 6, 4, vt1602_pga_sel), /* 10 */ +SOC_ENUM_SINGLE(VT1602_RADCIN, 6, 4, vt1602_pga_sel), +SOC_ENUM_SINGLE(VT1602_ADCTL2, 7, 4, vt1602_out3), +SOC_ENUM_SINGLE(VT1602_ADCIN, 8, 2, vt1602_diff_sel), +SOC_ENUM_SINGLE(VT1602_ADCDAC, 5, 4, vt1602_adcpol), +SOC_ENUM_SINGLE(VT1602_ADCDAC, 1, 4, vt1602_deemph), +SOC_ENUM_SINGLE(VT1602_ADCIN, 6, 4, vt1602_mono_mux), /* 16 */ + +}; + +static const struct snd_kcontrol_new vt1602_snd_controls[] = { + +SOC_DOUBLE_R("Capture Volume", VT1602_LINVOL, VT1602_RINVOL, 0, 63, 0), +SOC_DOUBLE_R("Capture ZC Switch", VT1602_LINVOL, VT1602_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", VT1602_LINVOL, VT1602_RINVOL, 7, 1, 1), + +SOC_DOUBLE_R("Headphone Playback ZC Switch", VT1602_LOUT1V, + VT1602_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R("Speaker Playback ZC Switch", VT1602_LOUT2V, + VT1602_ROUT2V, 7, 1, 0), + +SOC_ENUM("Playback De-emphasis", vt1602_enum[15]), + +SOC_ENUM("Capture Polarity", vt1602_enum[14]), +SOC_SINGLE("Playback 6dB Attenuate", VT1602_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", VT1602_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R("Master Volume", VT1602_LOUT1V, VT1602_ROUT1V, 0, 127, 0), +//SOC_DOUBLE_R("PCM Volume", VT1602_LOUT1V, VT1602_ROUT1V, 0, 127, 0), + +/* +SOC_ENUM("Bass Boost", vt1602_enum[0]), +SOC_ENUM("Bass Filter", vt1602_enum[1]), +SOC_SINGLE("Bass Volume", VT1602_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", VT1602_TREBLE, 0, 15, 0), +SOC_ENUM("Treble Cut-off", vt1602_enum[2]), + +SOC_SINGLE("3D Switch", VT1602_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", VT1602_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", vt1602_enum[3]), +SOC_ENUM("3D Upper Cut-off", vt1602_enum[4]), +SOC_ENUM("3D Mode", vt1602_enum[5]), + +SOC_SINGLE("ALC Capture Target Volume", VT1602_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", VT1602_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", vt1602_enum[6]), +SOC_SINGLE("ALC Capture ZC Switch", VT1602_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", VT1602_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", VT1602_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", VT1602_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", VT1602_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", vt1602_enum[4]), +SOC_SINGLE("ALC Capture NG Switch", VT1602_NGATE, 0, 1, 0), +*/ + +SOC_SINGLE("Left ADC Capture Volume", VT1602_LADC, 0, 255, 0), +SOC_SINGLE("Right ADC Capture Volume", VT1602_RADC, 0, 255, 0), + +SOC_SINGLE("ZC Timeout Switch", VT1602_ADCTL1, 0, 1, 0), +SOC_SINGLE("Playback Invert Switch", VT1602_ADCTL1, 1, 1, 0), + +SOC_SINGLE("Right Speaker Playback Invert Switch", VT1602_ADCTL2, 4, 1, 0), + +/* Unimplemented */ +/* ADCDAC Bit 0 - ADCHPD */ +/* ADCDAC Bit 4 - HPOR */ +/* ADCTL1 Bit 2,3 - DATSEL */ +/* ADCTL1 Bit 4,5 - DMONOMIX */ +/* ADCTL1 Bit 6,7 - VSEL */ +/* ADCTL2 Bit 2 - LRCM */ +/* ADCTL2 Bit 3 - TRI */ +/* ADCTL3 Bit 5 - HPFLREN */ +/* ADCTL3 Bit 6 - VROI */ +/* ADCTL3 Bit 7,8 - ADCLRM */ +/* ADCIN Bit 4 - LDCM */ +/* ADCIN Bit 5 - RDCM */ + +SOC_DOUBLE_R("Mic Boost", VT1602_LADCIN, VT1602_RADCIN, 4, 3, 0), + +SOC_DOUBLE_R("Bypass Left Playback Volume", VT1602_LOUTM1, + VT1602_LOUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Right Playback Volume", VT1602_ROUTM1, + VT1602_ROUTM2, 4, 7, 1), +SOC_DOUBLE_R("Bypass Mono Playback Volume", VT1602_MOUTM1, + VT1602_MOUTM2, 4, 7, 1), + +SOC_SINGLE("Mono Playback ZC Switch", VT1602_MOUTV, 7, 1, 0), + +SOC_DOUBLE_R("Headphone Playback Volume", VT1602_LOUT1V, VT1602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Speaker Playback Volume", VT1602_LOUT2V, VT1602_ROUT2V, + 0, 127, 0), + +SOC_SINGLE("Mono Playback Volume", VT1602_MOUTV, 0, 127, 0), + +}; + +/* + * DAPM Controls + */ + +/* Left Mixer */ +static const struct snd_kcontrol_new vt1602_left_mixer_controls[] = { +SOC_DAPM_SINGLE("Playback Switch", VT1602_LOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", VT1602_LOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", VT1602_LOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", VT1602_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new vt1602_right_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", VT1602_ROUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", VT1602_ROUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", VT1602_ROUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", VT1602_ROUTM2, 7, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new vt1602_mono_mixer_controls[] = { +SOC_DAPM_SINGLE("Left Playback Switch", VT1602_MOUTM1, 8, 1, 0), +SOC_DAPM_SINGLE("Left Bypass Switch", VT1602_MOUTM1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Playback Switch", VT1602_MOUTM2, 8, 1, 0), +SOC_DAPM_SINGLE("Right Bypass Switch", VT1602_MOUTM2, 7, 1, 0), +}; + +/* Left Line Mux */ +static const struct snd_kcontrol_new vt1602_left_line_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[8]); + +/* Right Line Mux */ +static const struct snd_kcontrol_new vt1602_right_line_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[9]); + +/* Left PGA Mux */ +static const struct snd_kcontrol_new vt1602_left_pga_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[10]); + +/* Right PGA Mux */ +static const struct snd_kcontrol_new vt1602_right_pga_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[11]); + +/* Out 3 Mux */ +static const struct snd_kcontrol_new vt1602_out3_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[12]); + +/* Differential Mux */ +static const struct snd_kcontrol_new vt1602_diffmux_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[13]); + +/* Mono ADC Mux */ +static const struct snd_kcontrol_new vt1602_monomux_controls = +SOC_DAPM_ENUM("Route", vt1602_enum[16]); + +static const struct snd_soc_dapm_widget vt1602_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &vt1602_left_mixer_controls[0], + ARRAY_SIZE(vt1602_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &vt1602_right_mixer_controls[0], + ARRAY_SIZE(vt1602_right_mixer_controls)), + /* + SND_SOC_DAPM_MIXER("Mono Mixer", VT1602_PWR2, 2, 0, + &vt1602_mono_mixer_controls[0], + ARRAY_SIZE(vt1602_mono_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", VT1602_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", VT1602_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", VT1602_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", VT1602_PWR2, 6, 0, NULL, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", VT1602_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", VT1602_PWR2, 8, 0), + */ + /* + SND_SOC_DAPM_MICBIAS("Mic Bias", VT1602_PWR1, 1, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", VT1602_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", VT1602_PWR1, 3, 0), + + SND_SOC_DAPM_MUX("Left PGA Mux", VT1602_PWR1, 5, 0, + &vt1602_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", VT1602_PWR1, 4, 0, + &vt1602_right_pga_controls), + */ + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &vt1602_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &vt1602_right_line_controls), + + SND_SOC_DAPM_MUX("Out3 Mux", SND_SOC_NOPM, 0, 0, &vt1602_out3_controls), + /* + SND_SOC_DAPM_PGA("Out 3", VT1602_PWR2, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out 1", VT1602_PWR2, 2, 0, NULL, 0), + */ + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &vt1602_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &vt1602_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &vt1602_monomux_controls), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("MONO1"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("LINPUT3"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT3"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* left mixer */ + {"Left Mixer", "Playback Switch", "Left DAC"}, + {"Left Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Left Mixer", "Right Playback Switch", "Right DAC"}, + {"Left Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* right mixer */ + {"Right Mixer", "Left Playback Switch", "Left DAC"}, + {"Right Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Right Mixer", "Playback Switch", "Right DAC"}, + {"Right Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* left out 1 */ + {"Left Out 1", NULL, "Left Mixer"}, + {"LOUT1", NULL, "Left Out 1"}, + + /* left out 2 */ + {"Left Out 2", NULL, "Left Mixer"}, + {"LOUT2", NULL, "Left Out 2"}, + + /* right out 1 */ + {"Right Out 1", NULL, "Right Mixer"}, + {"ROUT1", NULL, "Right Out 1"}, + + /* right out 2 */ + {"Right Out 2", NULL, "Right Mixer"}, + {"ROUT2", NULL, "Right Out 2"}, + + /* mono mixer */ + {"Mono Mixer", "Left Playback Switch", "Left DAC"}, + {"Mono Mixer", "Left Bypass Switch", "Left Line Mux"}, + {"Mono Mixer", "Right Playback Switch", "Right DAC"}, + {"Mono Mixer", "Right Bypass Switch", "Right Line Mux"}, + + /* mono out */ + {"Mono Out 1", NULL, "Mono Mixer"}, + {"MONO1", NULL, "Mono Out 1"}, + + /* out 3 */ + {"Out3 Mux", "VREF", "VREF"}, + {"Out3 Mux", "ROUT1 + Vol", "ROUT1"}, + {"Out3 Mux", "ROUT1", "Right Mixer"}, + {"Out3 Mux", "MonoOut", "MONO1"}, + {"Out 3", NULL, "Out3 Mux"}, + {"OUT3", NULL, "Out 3"}, + + /* Left Line Mux */ + {"Left Line Mux", "Line 1", "LINPUT1"}, + {"Left Line Mux", "Line 2", "LINPUT2"}, + {"Left Line Mux", "Line 3", "LINPUT3"}, + {"Left Line Mux", "PGA", "Left PGA Mux"}, + {"Left Line Mux", "Differential", "Differential Mux"}, + + /* Right Line Mux */ + {"Right Line Mux", "Line 1", "RINPUT1"}, + {"Right Line Mux", "Line 2", "RINPUT2"}, + {"Right Line Mux", "Line 3", "RINPUT3"}, + {"Right Line Mux", "PGA", "Right PGA Mux"}, + {"Right Line Mux", "Differential", "Differential Mux"}, + + /* Left PGA Mux */ + {"Left PGA Mux", "Line 1", "LINPUT1"}, + {"Left PGA Mux", "Line 2", "LINPUT2"}, + {"Left PGA Mux", "Line 3", "LINPUT3"}, + {"Left PGA Mux", "Differential", "Differential Mux"}, + + /* Right PGA Mux */ + {"Right PGA Mux", "Line 1", "RINPUT1"}, + {"Right PGA Mux", "Line 2", "RINPUT2"}, + {"Right PGA Mux", "Line 3", "RINPUT3"}, + {"Right PGA Mux", "Differential", "Differential Mux"}, + + /* Differential Mux */ + {"Differential Mux", "Line 1", "LINPUT1"}, + {"Differential Mux", "Line 1", "RINPUT1"}, + {"Differential Mux", "Line 2", "LINPUT2"}, + {"Differential Mux", "Line 2", "RINPUT2"}, + + /* Left ADC Mux */ + {"Left ADC Mux", "Stereo", "Left PGA Mux"}, + {"Left ADC Mux", "Mono (Left)", "Left PGA Mux"}, + {"Left ADC Mux", "Digital Mono", "Left PGA Mux"}, + + /* Right ADC Mux */ + {"Right ADC Mux", "Stereo", "Right PGA Mux"}, + {"Right ADC Mux", "Mono (Right)", "Right PGA Mux"}, + {"Right ADC Mux", "Digital Mono", "Right PGA Mux"}, + + /* ADC */ + {"Left ADC", NULL, "Left ADC Mux"}, + {"Right ADC", NULL, "Right ADC Mux"}, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + DBG_DETAIL("mclk=%d, rate=%d", mclk, rate); + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + printk(KERN_ERR "vt1602: could not get coeff for mclk %d @ rate %d\n", + mclk, rate); + return -EINVAL; +} + +static int vt1602_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + u16 iface = snd_soc_read(codec, VT1602_IFACE) & 0x1f3; + //int coeff = get_coeff(vt1602->sysclk, params_rate(params)); + + DBG_DETAIL(); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + snd_soc_write(codec, VT1602_IFACE, iface); + + return 0; +} + +static int vt1602_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = snd_soc_read(codec, VT1602_ADCDAC) & 0xfff7; + + DBG_DETAIL(); + + if (mute) + snd_soc_write(codec, VT1602_ADCDAC, mute_reg | 0x8); + else + snd_soc_write(codec, VT1602_ADCDAC, mute_reg); + return 0; +} + +static int vt1602_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 vt1602_priv *vt1602 = snd_soc_codec_get_drvdata(codec); + + DBG_DETAIL(); + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + vt1602->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int vt1602_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + DBG_DETAIL(); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, VT1602_IFACE, iface); + return 0; +} + +static int vt1602_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level ) +{ + DBG_DETAIL(); + + switch (level) { + case SND_SOC_BIAS_ON: + /* set vmid to 50k and unmute dac */ + break; + case SND_SOC_BIAS_PREPARE: + /* set vmid to 5k for quick power up */ + break; + case SND_SOC_BIAS_STANDBY: + /* mute dac and set vmid to 500k, enable VREF */ + break; + case SND_SOC_BIAS_OFF: + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static void vt1602_work(struct work_struct *work) +{ + struct snd_soc_codec *codec = + container_of(work, struct snd_soc_codec, dapm.delayed_work.work); + + DBG_DETAIL(); + + vt1602_set_bias_level(codec, codec->dapm.bias_level); +} + +#define VT1602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define VT1602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_FLOAT) + +static const struct snd_soc_dai_ops vt1602_dai_ops = { + .hw_params = vt1602_pcm_hw_params, + .digital_mute = vt1602_mute, + .set_sysclk = vt1602_set_dai_sysclk, + .set_fmt = vt1602_set_dai_fmt, +}; + +struct snd_soc_dai_driver vt1602_dai = { + .name = "VT1602", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = VT1602_RATES, + .formats = VT1602_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = VT1602_RATES, + .formats = VT1602_FORMATS,}, + .ops = &vt1602_dai_ops, +}; + +static int vt1602_suspend(struct snd_soc_codec *codec) +{ + DBG_DETAIL(); + + vt1602_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int vt1602_resume(struct snd_soc_codec *codec) +{ + DBG_DETAIL(); + + snd_soc_cache_sync(codec); + vt1602_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* charge vt1602 caps */ + if (codec->dapm.suspend_bias_level == SND_SOC_BIAS_ON) { + vt1602_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->dapm.bias_level = SND_SOC_BIAS_ON; + schedule_delayed_work(&codec->dapm.delayed_work, + msecs_to_jiffies(1000)); + } + + return 0; +} + +static int wmt_vt1602_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret, reg; + + DBG_DETAIL(); + + /* charge output caps */ + vt1602_set_bias_level(codec, SND_SOC_BIAS_PREPARE); + codec->dapm.bias_level = SND_SOC_BIAS_STANDBY; + schedule_delayed_work(&codec->dapm.delayed_work, msecs_to_jiffies(1000)); + + /* set the update bits */ + reg = snd_soc_read(codec, VT1602_LDAC); + snd_soc_write(codec, VT1602_LDAC, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_RDAC); + snd_soc_write(codec, VT1602_RDAC, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_LOUT1V); + snd_soc_write(codec, VT1602_LOUT1V, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_ROUT1V); + snd_soc_write(codec, VT1602_ROUT1V, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_LOUT2V); + snd_soc_write(codec, VT1602_LOUT2V, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_ROUT2V); + snd_soc_write(codec, VT1602_ROUT2V, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_LINVOL); + snd_soc_write(codec, VT1602_LINVOL, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_RINVOL); + snd_soc_write(codec, VT1602_RINVOL, reg | 0x0100); + reg = snd_soc_read(codec, VT1602_LOUTM1); + snd_soc_write(codec, VT1602_LOUTM1, reg); + reg = snd_soc_read(codec, VT1602_LOUTM2); + snd_soc_write(codec, VT1602_LOUTM2, reg); + reg = snd_soc_read(codec, VT1602_ROUTM1); + snd_soc_write(codec, VT1602_ROUTM1, reg); + reg = snd_soc_read(codec, VT1602_ROUTM2); + snd_soc_write(codec, VT1602_ROUTM2, reg); + snd_soc_write(codec, VT1602_PWR1, 0x1FE); + snd_soc_write(codec, VT1602_PWR2, 0x1E0); + snd_soc_write(codec, VT1602_LADCIN, 0x020); + snd_soc_write(codec, VT1602_RADCIN, 0x020); + snd_soc_write(codec, VT1602_ADCTL3, 0x040); + + ret = snd_soc_add_codec_controls(codec, vt1602_snd_controls, + ARRAY_SIZE(vt1602_snd_controls)); + if (ret) { + info("snd_soc_add_codec_controls fail"); + return ret; + } + + ret = snd_soc_dapm_new_controls(dapm, vt1602_dapm_widgets, + ARRAY_SIZE(vt1602_dapm_widgets)); + if (ret) { + info("snd_soc_dapm_new_controls fail"); + return ret; + } + + ret = snd_soc_dapm_add_routes(dapm, audio_map, + ARRAY_SIZE(audio_map)); + if (ret) { + info("snd_soc_dapm_add_routes fail"); + return 0; + } + + return ret; +} + +static int vt1602_probe(struct snd_soc_codec *codec) +{ + struct vt1602_priv *vt1602 = snd_soc_codec_get_drvdata(codec); + int ret; + + pr_info("VT1602 Audio Codec %s\n", VT1602_VERSION); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, vt1602->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + ret = vt1602_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); + return ret; + } + + INIT_DELAYED_WORK(&codec->dapm.delayed_work, vt1602_work); + + ret = wmt_vt1602_init(codec); + if (ret) + info("vt1602_probe fail"); + return ret; +} + +/* + * This function forces any delayed work to be queued and run. + */ +static int run_delayed_work(struct delayed_work *dwork) +{ + int ret; + + DBG_DETAIL(); + + /* cancel any work waiting to be queued. */ + ret = cancel_delayed_work(dwork); + + /* if there was any work waiting then we run it now and + * wait for it's completion */ + if (ret) { + schedule_delayed_work(dwork, 0); + flush_scheduled_work(); + } + return ret; +} + +/* remove everything here */ +static int vt1602_remove(struct snd_soc_codec *codec) +{ + DBG_DETAIL(); + + vt1602_set_bias_level(codec, SND_SOC_BIAS_OFF); + run_delayed_work(&codec->dapm.delayed_work); + + return 0; +} + +static struct snd_soc_codec_driver soc_codec_dev_vt1602 = { + .probe = vt1602_probe, + .remove = vt1602_remove, + .suspend = vt1602_suspend, + .resume = vt1602_resume, + .set_bias_level = vt1602_set_bias_level, + .reg_cache_size = ARRAY_SIZE(vt1602_reg), + .reg_word_size = sizeof(u16), + .reg_cache_default = vt1602_reg, + + /*.controls = vt1602_snd_controls, + .num_controls = ARRAY_SIZE(vt1602_snd_controls), + .dapm_widgets = vt1602_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(vt1602_dapm_widgets), + .dapm_routes = audio_map, + .num_dapm_routes = ARRAY_SIZE(audio_map),*/ +}; + +#if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) +static int __devinit vt1602_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct vt1602_priv *vt1602; + int ret; + + DBG_DETAIL(); + + vt1602 = devm_kzalloc(&i2c->dev, sizeof(struct vt1602_priv), GFP_KERNEL); + if (vt1602 == NULL) { + info("vt1602_i2c_probe kzalloc fail"); + return -ENOMEM; + } + + i2c_set_clientdata(i2c, vt1602); + vt1602->control_type = SND_SOC_I2C; + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_vt1602, &vt1602_dai, 1); + + return ret; +} + +static int __devexit vt1602_i2c_remove(struct i2c_client *client) +{ + DBG_DETAIL(); + + snd_soc_unregister_codec(&client->dev); + + return 0; +} + +static const struct i2c_device_id vt1602_i2c_id[] = { + { "vt1602", 1}, + { } +}; +MODULE_DEVICE_TABLE(i2c, vt1602_i2c_id); + +/* corgi i2c codec control layer */ +static struct i2c_driver vt1602_i2c_driver = { + .driver = { + .name = "vt1602", + .owner = THIS_MODULE, + }, + .probe = vt1602_i2c_probe, + .remove = __devexit_p(vt1602_i2c_remove), + .id_table = vt1602_i2c_id, +}; +#endif + +static struct i2c_board_info __initdata wmt_vt1602_board_info[] = { + { + I2C_BOARD_INFO("vt1602", 0x1a), + }, +}; + +static int __init vt1602_modinit(void) +{ + char buf[80]; + int varlen = 80; + int ret = 0; + char codec_name[6]; + struct i2c_adapter *adapter = NULL; + + DBG_DETAIL(); + + ret = wmt_getsyspara("wmt.audio.i2s", buf, &varlen); + + if (ret == 0) { + sscanf(buf, "%6s", codec_name); + + if (strcmp(codec_name, "vt1602")) { + info("vt1602 string not found"); + return -EINVAL; + } + } + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + /*i2c_register_board_info(1, wmt_vt1602_board_info, + ARRAY_SIZE(wmt_vt1602_board_info));*/ + + adapter = i2c_get_adapter(1); + if (adapter == NULL) { + err("can not get i2c adapter, client address error"); + return -ENODEV; + } + + if (i2c_new_device(adapter, wmt_vt1602_board_info) == NULL) { + err("allocate i2c client failed"); + return -ENOMEM; + } + + i2c_put_adapter(adapter); + + ret = i2c_add_driver(&vt1602_i2c_driver); + if (ret) { + info("i2c_add_driver fail"); + } +#endif + + return ret; +} +module_init(vt1602_modinit); + +static void __exit vt1602_exit(void) +{ + DBG_DETAIL(); + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&vt1602_i2c_driver); +#endif +} +module_exit(vt1602_exit); + +MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.h b/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.h new file mode 100755 index 00000000..718c822a --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/codecs/wmt_vt1602.h @@ -0,0 +1,67 @@ +/*++ + * linux/sound/soc/codecs/wmt_vt1602.h + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef _VT1602_H +#define _VT1602_H + +/* VT1602 register space */ + +#define VT1602_LINVOL 0x00 +#define VT1602_RINVOL 0x01 +#define VT1602_LOUT1V 0x02 +#define VT1602_ROUT1V 0x03 +#define VT1602_ADCDAC 0x05 +#define VT1602_IFACE 0x07 +#define VT1602_SRATE 0x08 +#define VT1602_LDAC 0x0a +#define VT1602_RDAC 0x0b +#define VT1602_BASS 0x0c +#define VT1602_TREBLE 0x0d +#define VT1602_RESET 0x0f +#define VT1602_3D 0x10 +#define VT1602_ALC1 0x11 +#define VT1602_ALC2 0x12 +#define VT1602_ALC3 0x13 +#define VT1602_NGATE 0x14 +#define VT1602_LADC 0x15 +#define VT1602_RADC 0x16 +#define VT1602_ADCTL1 0x17 +#define VT1602_ADCTL2 0x18 +#define VT1602_PWR1 0x19 +#define VT1602_PWR2 0x1a +#define VT1602_ADCTL3 0x1b +#define VT1602_ADCIN 0x1f +#define VT1602_LADCIN 0x20 +#define VT1602_RADCIN 0x21 +#define VT1602_LOUTM1 0x22 +#define VT1602_LOUTM2 0x23 +#define VT1602_ROUTM1 0x24 +#define VT1602_ROUTM2 0x25 +#define VT1602_MOUTM1 0x26 +#define VT1602_MOUTM2 0x27 +#define VT1602_LOUT2V 0x28 +#define VT1602_ROUT2V 0x29 +#define VT1602_MOUTV 0x2a + +#endif diff --git a/ANDROID_3.4.5/sound/soc/soc-core.c b/ANDROID_3.4.5/sound/soc/soc-core.c index 18711a50..7e286549 100644 --- a/ANDROID_3.4.5/sound/soc/soc-core.c +++ b/ANDROID_3.4.5/sound/soc/soc-core.c @@ -639,7 +639,8 @@ static void soc_resume_deferred(struct work_struct *work) container_of(work, struct snd_soc_card, deferred_resume_work); struct snd_soc_codec *codec; int i; - + + int dbg_resume = 0; /* our power state is still SNDRV_CTL_POWER_D3hot from suspend time, * so userspace apps are blocked from touching us */ @@ -674,12 +675,23 @@ static void soc_resume_deferred(struct work_struct *work) case SND_SOC_BIAS_OFF: codec->driver->resume(codec); codec->suspended = 0; + dbg_resume = 1; break; default: + dev_dbg(codec->dev, "CODEC was on over suspend\n"); break; } } + #if 1 + if (!dbg_resume && codec->driver->resume) //add for temp std 2014-3-24 + { + dbg_resume = 1; + codec->driver->resume(codec); + codec->suspended = 0; + + } + #endif } for (i = 0; i < card->num_rtd; i++) { @@ -1718,24 +1730,247 @@ int snd_soc_poweroff(struct device *dev) EXPORT_SYMBOL_GPL(snd_soc_poweroff); const struct dev_pm_ops snd_soc_pm_ops = { - .suspend = snd_soc_suspend, - .resume = snd_soc_resume, - .freeze = snd_soc_suspend, - .thaw = snd_soc_resume, - .poweroff = snd_soc_poweroff, - .restore = snd_soc_resume, + .suspend = snd_soc_suspend, //PMSG_SUSPEND + .resume = snd_soc_resume, // + .freeze = snd_soc_suspend, //PMSG_FREEZE + .thaw = snd_soc_resume, // + .poweroff = snd_soc_poweroff, //PMSG_HIBERNATE + .restore = snd_soc_resume, // }; EXPORT_SYMBOL_GPL(snd_soc_pm_ops); +/*****add for std 2014-3-24*************/ + +/* powers down audio subsystem for suspend */ +static int soc_suspend(struct platform_device *dev, pm_message_t state) +{ + struct snd_soc_card *card = platform_get_drvdata(dev); + struct snd_soc_codec *codec; + int i; + printk("<<<<<<<<%s, pm state: 0x%x\n", __func__, state.event); + + /* If the initialization of this soc device failed, there is no codec + * associated with it. Just bail out in this case. + */ + if (list_empty(&card->codec_dev_list)) + return 0; + +#if 0 + mutex_lock(&std_pm_mutex); + if (std_dev_suspend) + { + mutex_unlock(&std_pm_mutex); + return 0; + } + std_pdev_suspend = 1; + mutex_unlock(&std_pm_mutex); + + if (std_pdev_suspend) + return 0; + + std_pdev_suspend = 1; +#endif + /* Due to the resume being scheduled into a workqueue we could + * suspend before that's finished - wait for it to complete. + */ + + if (state.event==PM_EVENT_SUSPEND/*0x2*/ || state.event==PM_EVENT_FREEZE/*0x1*/){ //move from device_driver pm + snd_power_lock(card->snd_card); + snd_power_wait(card->snd_card, SNDRV_CTL_POWER_D0); + snd_power_unlock(card->snd_card); + + /* we're going to block userspace touching us until resume completes */ + snd_power_change_state(card->snd_card, SNDRV_CTL_POWER_D3hot); + + /* mute any active DACs */ + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai *dai = card->rtd[i].codec_dai; + struct snd_soc_dai_driver *drv = dai->driver; + + if (card->rtd[i].dai_link->ignore_suspend) + continue; + + if (drv->ops->digital_mute && dai->playback_active) + drv->ops->digital_mute(dai, 1); + } + + /* suspend all pcms */ + for (i = 0; i < card->num_rtd; i++) { + if (card->rtd[i].dai_link->ignore_suspend) + continue; + + snd_pcm_suspend_all(card->rtd[i].pcm); + } + + if (card->suspend_pre) + card->suspend_pre(card); + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; + struct snd_soc_platform *platform = card->rtd[i].platform; + + if (card->rtd[i].dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->suspend && !cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); + if (platform->driver->suspend && !platform->suspended) { + platform->driver->suspend(cpu_dai); + platform->suspended = 1; + } + } + + /* close any waiting streams and save state */ + for (i = 0; i < card->num_rtd; i++) { + flush_delayed_work_sync(&card->rtd[i].delayed_work); + card->rtd[i].codec->dapm.suspend_bias_level = card->rtd[i].codec->dapm.bias_level; + } + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai *codec_dai = card->rtd[i].codec_dai; + + if (card->rtd[i].dai_link->ignore_suspend) + continue; + + snd_soc_dapm_stream_event(&card->rtd[i], + SNDRV_PCM_STREAM_PLAYBACK, + codec_dai, + SND_SOC_DAPM_STREAM_SUSPEND); + + snd_soc_dapm_stream_event(&card->rtd[i], + SNDRV_PCM_STREAM_CAPTURE, + codec_dai, + SND_SOC_DAPM_STREAM_SUSPEND); + } + + /* suspend all CODECs */ + list_for_each_entry(codec, &card->codec_dev_list, card_list) { + /* If there are paths active then the CODEC will be held with + * bias _ON and should not be suspended. */ + if (!codec->suspended && codec->driver->suspend) { + switch (codec->dapm.bias_level) { + case SND_SOC_BIAS_STANDBY: + /* + * If the CODEC is capable of idle + * bias off then being in STANDBY + * means it's doing something, + * otherwise fall through. + */ + if (codec->dapm.idle_bias_off) { + dev_dbg(codec->dev, + "idle_bias_off CODEC on over suspend\n"); + break; + } + case SND_SOC_BIAS_OFF: + codec->driver->suspend(codec); + codec->suspended = 1; + codec->cache_sync = 1; + break; + default: + dev_dbg(codec->dev, "CODEC is on over suspend\n"); + break; + } + } + } + + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; + + if (card->rtd[i].dai_link->ignore_suspend) + continue; + + if (cpu_dai->driver->suspend && cpu_dai->driver->ac97_control) + cpu_dai->driver->suspend(cpu_dai); + } + + if (card->suspend_post) + card->suspend_post(card); + + return 0; + } + + if (state.event == PM_EVENT_HIBERNATE) { // 0x4 + + /* Flush out pmdown_time work - we actually do want to run it + * now, we're shutting down so no imminent restart. */ + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + flush_delayed_work_sync(&rtd->delayed_work); + } + + snd_soc_dapm_shutdown(card); + + return 0; + + } + + return 0; +} + +/* powers up audio subsystem after a suspend */ +int soc_resume(struct platform_device *dev) +{ + struct snd_soc_card *card = platform_get_drvdata(dev); + int i, ac97_control = 0; + + printk("<<<<<%s\n", __func__); + +#if 0 + if (std_pdev_suspend) + std_pdev_suspend = 0; + else + return 0; +#endif + /* If the initialization of this soc device failed, there is no codec + * associated with it. Just bail out in this case. + */ + if (list_empty(&card->codec_dev_list)) + return 0; +#if 0 + mutex_lock(&std_pm_mutex); + if (std_dev_suspend) + { + mutex_unlock(&std_pm_mutex); + return 0; + } + std_pdev_suspend = 0; + mutex_unlock(&std_pm_mutex); +#endif + + /* AC97 devices might have other drivers hanging off them so + * need to resume immediately. Other drivers don't have that + * problem and may take a substantial amount of time to resume + * due to I/O costs and anti-pop so handle them out of line. + */ + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_dai *cpu_dai = card->rtd[i].cpu_dai; + ac97_control |= cpu_dai->driver->ac97_control; + } + if (ac97_control) { + dev_dbg(dev, "Resuming AC97 immediately\n"); + soc_resume_deferred(&card->deferred_resume_work); + } else { + dev_dbg(dev, "Scheduling resume work\n"); + if (!schedule_work(&card->deferred_resume_work)) + dev_err(dev, "resume work item may be lost\n"); + } + + return 0; +} + +/*********************************************/ + /* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, - .pm = &snd_soc_pm_ops, + //.pm = &snd_soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, + .suspend = soc_suspend, + .resume = soc_resume, }; /** diff --git a/ANDROID_3.4.5/sound/soc/soc-dapm.c b/ANDROID_3.4.5/sound/soc/soc-dapm.c index 64bcd4e2..6201fc54 100644 --- a/ANDROID_3.4.5/sound/soc/soc-dapm.c +++ b/ANDROID_3.4.5/sound/soc/soc-dapm.c @@ -1442,7 +1442,15 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } list_for_each_entry(w, &card->widgets, list) { - list_del_init(&w->dirty); + switch (w->id) { + case snd_soc_dapm_pre: + case snd_soc_dapm_post: + /* These widgets always need to be powered */ + break; + default: + list_del_init(&w->dirty); + break; + } if (w->power) { d = w->dapm; @@ -3266,7 +3274,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card) { struct snd_soc_codec *codec; - list_for_each_entry(codec, &card->codec_dev_list, list) { + list_for_each_entry(codec, &card->codec_dev_list, card_list) { soc_dapm_shutdown_codec(&codec->dapm); if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) snd_soc_dapm_set_bias_level(&codec->dapm, diff --git a/ANDROID_3.4.5/sound/soc/wmt/Kconfig b/ANDROID_3.4.5/sound/soc/wmt/Kconfig new file mode 100755 index 00000000..344ec73d --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/Kconfig @@ -0,0 +1,65 @@ +config SND_WMT_SOC + bool "SoC Audio for the WMT chip" + depends on ARCH_WMT && SND && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + the WMT AC97, I2S interface. You will also need + to select the audio interfaces to support below. + +# +# WMT ALSA I2S driver +# +config SND_WMT_SOC_I2S + bool "SoC I2S Audio support for WMT" + depends on SND_WMT_SOC + ---help--- + Say Y or M if you want to add support for codecs attached to + the WMT I2S interface. + +config SND_WMT_SOC_PDM + bool "SoC PDM Audio Interface support for WMT" + depends on SND_WMT_SOC + ---help--- + Say Y or M if you want to add support for codecs attached to + the WMT PDM interface. + +config I2S_HW_DAC + bool "HW_DAC" + depends on SND_WMT_SOC_I2S + select SND_SOC_HWDAC + ---help--- + Say Y here if you want to use HW DAC. + +config I2S_CODEC_VT1602 + bool "VT1602" + depends on SND_WMT_SOC_I2S + select SND_SOC_VT1602 + ---help--- + Say Y here if you want to use VT1602 as the I2S Codec. + +config I2S_CODEC_WM8900 + bool "WM8900" + depends on SND_WMT_SOC_I2S + select SND_SOC_WM8900 + ---help--- + Say Y here if you want to use WM8900 as the I2S Codec. + +config I2S_CODEC_WM8994 + bool "WM8994" + depends on SND_WMT_SOC_I2S + select SND_SOC_WM8994 + ---help--- + Say Y here if you want to use WM8994 as the I2S Codec. + +config I2S_CODEC_VT1603 + bool "VT1603" + depends on SND_WMT_SOC_I2S + select SND_SOC_VT1603 + ---help--- + Say Y here if you want to use VT1603 as the I2S Codec. + +config ECHO_CANCELLATION_FM34 + tristate "FM34" + select SND_SOC_WMT_FM34 + ---help--- + Say Y here if you want to use fm34 to echo cancallation. diff --git a/ANDROID_3.4.5/sound/soc/wmt/Makefile b/ANDROID_3.4.5/sound/soc/wmt/Makefile new file mode 100755 index 00000000..bf4a07ef --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/Makefile @@ -0,0 +1,12 @@ +# +## Makefile for ALSA WMT +# +# +# WMT SOC Support +snd-soc-wmt-i2s-objs := wmt-i2s.o wmt_swmixer.o +snd-soc-wmt-objs := wmt-soc.o wmt-pcm.o wmt_wm8994.o wmt_hwdep.o + +snd-soc-wmt-pdm-objs := wmt-pcm-controller.o wmt-pcm-dma.o +obj-$(CONFIG_SND_WMT_SOC_I2S) += snd-soc-wmt-i2s.o +obj-$(CONFIG_SND_WMT_SOC) += snd-soc-wmt.o +obj-$(CONFIG_SND_WMT_SOC_PDM) += snd-soc-wmt-pdm.o diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c new file mode 100755 index 00000000..643d25e2 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-i2s.c @@ -0,0 +1,1105 @@ +/*++ + * linux/sound/soc/wmt/wmt-i2s.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <linux/gpio.h> +#include <mach/wmt_iomux.h> +#include <linux/suspend.h> + +#include <mach/hardware.h> +#include <asm/dma.h> +#include "wmt-soc.h" +#include "wmt-pcm.h" + +#define NULL_DMA ((dmach_t)(-1)) +#define AUD_SPDIF_ENABLE 1 + +static int wmt_i2s_output = 0; //hdmi enable or not 2013-9-3 //maybe can remove it 2013-10-24 +static int wmt_codec_wm8994 = 0; //env set 2013-9-3 + +static int gpio_pa = -1; +static int gpio_active = 0; +extern int wmt_gpio_setpull(unsigned int gpio, enum wmt_gpio_pulltype pull); +/* + * Debug + */ +#define AUDIO_NAME "WMT_I2S" +//#define WMT_I2S_DEBUG 1 +//#define WMT_I2S_DEBUG_DETAIL 1 + +#ifdef WMT_I2S_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_I2S_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +struct wmt_i2s_asoc_data { + int stream_id; + unsigned int bus_id; + struct i2s_s i2s; + /* + * Flags indicating is the bus already activated and configured by + * another substream + */ + unsigned char HDMI_and_DAC0; //new env set to determine + unsigned char HDMI_aud_enable; //same with global wmt_i2s_output + + int active; + int configured; + unsigned char CH_SEL_NUM; + unsigned char HDMI_SPDIF_STATE; + /*struct timer_list delay_timer;*/ + struct audio_stream_a s[2]; +}; +static struct timer_list pa_timer; + +static int audio_interface_mode = 0;//add 2014-6-24 i2s left right + +static int wmt_pdm_module_enable = 0; +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +#define to_i2s_data(priv) container_of((priv), struct wmt_i2s_asoc_data, bus_id) +#define SNDRV_PCM_STREAM_ALL 2 + +static void i2s_init(int mode); +static void i2s_exit(void); + +#ifdef CONFIG_FB_WMT +extern int vpp_set_audio(int format, int sample_rate, int channel); +#endif + + +static struct wmt_i2s_asoc_data i2s_data[NUM_LINKS] = { + { + .bus_id = 0, //?? 2 SNDRV_PCM_STREAM_ALL + .i2s = { + /* interrupt counters */ + {0, 0, 0, 0, 0, 0, 0, 0}, + /* irq number*/ + 0, + /* reference counter */ + 0, + /* channels */ + 0, + /* format */ + 0, + /* fragment size */ + 0, + /* sample rate */ + 0, + i2s_init, + i2s_exit, + }, + .s = { + { + .id = "WMT I2S out", + .stream_id = SNDRV_PCM_STREAM_PLAYBACK, + .dmach = NULL_DMA, + .dma_dev = AHB1_AUD_DMA_REQ_1, + /*.dma_cfg = dma_device_cfg_table[I2S_TX_DMA_REQ],*/ + }, + { + .id = "WMT I2S in", + .stream_id = SNDRV_PCM_STREAM_CAPTURE, + .dmach = NULL_DMA, + .dma_dev = AHB1_AUD_DMA_REQ_0, + /*.dma_cfg = dma_device_cfg_table[I2S_RX_DMA_REQ],*/ + }, + }, + .HDMI_aud_enable = 0, + .HDMI_and_DAC0 = 0, + .CH_SEL_NUM = 2, + .HDMI_SPDIF_STATE = 6,//hdmi spdif play at sametime + }, +}; + + +/* for HDMI Audio status */ +int hd_audio_flag = 0; + + + +#ifdef CONFIG_WMT_I2S_INT +/* wmt_i2s_interrupt() + * + * It's only interrupt counter now, might be useful to + * debug or benchmark. + */ +static irqreturn_t +wmt_i2s_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + DBG_DETAIL(); + + return IRQ_HANDLED; +} +#endif + +#if 0 +static void delay_timer_handler(unsigned long data) +{ + DBG_DETAIL(); +} +#endif + +static void pa_timer_handler(unsigned long data) +{ + printk("%s\n", __func__); + if (gpio_pa >= 0) + { + + gpio_direction_output(gpio_pa, gpio_active); + } +} + +void wmt_i2s_ch_config(void) +{ + /* CH_SEL_NUM = 0, mesns output L to SPDIF&DAC's L and R + 1, mesns output R to SPDIF&DAC's L and R + 2, means normal stereo output to SPDIF&DAC */ + switch (i2s_data->CH_SEL_NUM) { + case 0: + ASMPFCHCFG0_VAL = 0x10100000; + ASMPF2HDACHCFG_VAL = 0x76543200; + break; + case 1: + ASMPFCHCFG0_VAL = 0x10101111; + ASMPF2HDACHCFG_VAL = 0x76543211; + break; + case 2: + ASMPFCHCFG0_VAL = 0x10101010; + ASMPF2HDACHCFG_VAL = 0x76543210; + break; + } + + if (i2s_data->i2s.channels == 0x01) { + ASMPFCHCFG0_VAL = 0x10100000; + ASMPF2HDACHCFG_VAL = 0x76543200; + } + + if (i2s_data->HDMI_aud_enable) { + /* disable DAC#0, if HDMI Audio is enabled */ + ASMPFCHCFG0_VAL &= 0xFFFF00FF; + ASMPFCHCFG0_VAL |= 0x00009800; + } + + /* HDMI_SPDIF_STATE = 3, means disable HDMI&SPDIF + 4, means enable HDMI only + 5, means enable SPDIF only + 6, means enable HDMI&SPDIF as normal */ + switch (i2s_data->HDMI_SPDIF_STATE) { + case 3: + ASMPFCHCFG0_VAL &= 0xFFFFFF00; + ASMPFCHCFG0_VAL |= 0x00000098; + ASMPF2HDACHCFG_VAL &= 0xFFFFFF00; + ASMPF2HDACHCFG_VAL |= 0x00000076; + break; + case 4: + ASMPFCHCFG0_VAL &= 0xFFFFFF00; + ASMPFCHCFG0_VAL |= 0x00000098; + break; + case 5: + ASMPF2HDACHCFG_VAL &= 0xFFFFFF00; + ASMPF2HDACHCFG_VAL |= 0x00000076; + break; + } +} + + +void wmt_i2s_dac0_ctrl(int HDMI_audio_enable) +{ + /* check if output to HDMI audio and DAC0 at same time */ + if (i2s_data->HDMI_and_DAC0) + return; + + if (HDMI_audio_enable) { + i2s_data->HDMI_aud_enable = 1; + info("HDMI Audio is enabled, disable dac0 of I2S"); + } + else { + i2s_data->HDMI_aud_enable = 0; + info("HDMI Audio is disabled, enable dac0 of I2S"); + } + + wmt_i2s_ch_config(); + + info("CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); +} +EXPORT_SYMBOL(wmt_i2s_dac0_ctrl); +void wmt_i2s_ch_sel(int ch_sel_num) +{ + if (ch_sel_num < 3) + i2s_data->CH_SEL_NUM = ch_sel_num; + else + i2s_data->HDMI_SPDIF_STATE = ch_sel_num; + + wmt_i2s_ch_config(); + + + info("SEL: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); +} +EXPORT_SYMBOL(wmt_i2s_ch_sel); + +static void i2s_disable(void) +{ + DBG_DETAIL(); + + DACCFG_VAL &= ~DACITF_ENABLE; + ASMPFCFG_VAL &=~ASMPF_ENABLE; + HDACKGEN_VAL &= ~HDACKGEN_ENABLE; + +#ifdef AUD_SPDIF_ENABLE + DGOCFG_VAL &= ~DGOITF_ENABLE; +#endif + + AADCF0CFG_VAL &= ~(AADCITF_ENABLE | AADCF_ENABLE); +} + +static void i2s_enable(void) +{ + DBG_DETAIL(); + + AADCF0CFG_VAL |= (AADCITF_ENABLE | AADCF_ENABLE); + ASMPFCFG_VAL |= (ASMPF_ENABLE); + +#ifdef AUD_SPDIF_ENABLE + DGOCFG_VAL |= DGOITF_ENABLE; +#endif + + DACCFG_VAL |= DACITF_ENABLE; + + if (hd_audio_flag) + HDACKGEN_VAL |= HDACKGEN_ENABLE; +} + +static void aud_audprf_setting(unsigned char smp_rate_index) +{ + unsigned long cfg_tbl[] = {0x00002001, 0x00002001, 0x00002001, + 0x00002001, 0x00002001, 0x00002001, 0x00002001, 0x00002001, + 0x00002001, 0x00002001, 0x00002001, 0x00002001, + 0x00000001, 0x00000001, 0x00000001 + }; + unsigned int dgo_tbl[] = {0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00}; + unsigned int sysclk_tbl[] = {4096, 5632, 6144, 8192, 11264, 12288, 16384, + 22579, 24576, 32768, 45056, 49152, 16384, 22528, 24576}; +#ifdef AUD_SPDIF_ENABLE + unsigned int dgocs_tbl[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x02, 0x00, 0x08, 0x0A, 0x00, 0x0C, 0x0E}; +#endif + //unsigned int clock = 0; + unsigned long aud_reg_val; + + DBG_DETAIL(); + + ADCCFG_VAL = cfg_tbl[smp_rate_index]; + + aud_reg_val = dgo_tbl[smp_rate_index]; + DGOCFG_VAL = aud_reg_val; + HDACKGEN_VAL = aud_reg_val; + +#ifdef AUD_SPDIF_ENABLE + /* set ADGO channel status */ + aud_reg_val = dgocs_tbl[smp_rate_index] << 24; + DGOCS0A_VAL = aud_reg_val; + DGOCS1A_VAL = aud_reg_val; + //info("%s : DGOCS01A_VAL=0x%x", __func__, (unsigned int)aud_reg_val); +#endif + + DACCFG_VAL = cfg_tbl[smp_rate_index]; + + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, sysclk_tbl[smp_rate_index]); + /*clock = auto_pll_divisor(DEV_I2S, GET_FREQ , 0, 0); + info("%s : clock=%d", __func__, clock);*/ +} + +static unsigned char aud_smp_rate_convert(unsigned int smp_rate) +{ + unsigned char i = 0; + unsigned int smp_rate_tbl[] = {8000, 11025, 12000, 16000, 22050, 24000, 32000, + 44100, 48000, 64000, 88200, 96000, 128000, 176000, 192000}; + + DBG_DETAIL(); + + /* boundary checking */ + if (smp_rate < 8000) { + i = 0x00; + } + else if (smp_rate > 192000) { + i = 0x0E; + } + else { + for (i = 0; i < 0x1F; i++) { + if (smp_rate == smp_rate_tbl[i]) { + break; + } + else if (smp_rate < smp_rate_tbl[i + 1]) { + if (smp_rate < (smp_rate_tbl[i] + ((smp_rate_tbl[i + 1] - smp_rate_tbl[i]) / 2))) { + break; + } + else { + i++; + break; + } + } + } + } + + return i; + +} + +static void i2s_sample_rate(unsigned int rate) +{ + unsigned char rate_index; + + DBG_DETAIL("rate=%d", rate); + + if (rate == i2s_data->i2s.rate) + return; + + i2s_data->i2s.rate = rate; + + rate_index = aud_smp_rate_convert(rate); + + aud_audprf_setting(rate_index); + +#ifdef CONFIG_FB_WMT + /* pass information of audio to HDMI Audio */ + hd_audio_flag = vpp_set_audio(16, rate, 2); +#endif +} + +static int i2sdacdat_gpio = -1;//means don't touch GP10 bit0-3 i2sDacDat0-3 + +void wmt_set_i2s_share_pin(void) +{ + int pwren_num,active_level,gpio_int_num; + char buf[256]; + int varlen = 256; + static int gpio26_mux = -1; + unsigned int gpval = 0x0; + + if(gpio26_mux == -1){ + if(wmt_getsyspara("wmt.bt.mtk6622",buf,&varlen) == 0) + { + sscanf(buf,"%d:%d:%d",&pwren_num,&active_level,&gpio_int_num); + printk("use customized value:p%d,a%d,i%d\n",pwren_num,active_level,gpio_int_num); + if(pwren_num == 62){ + gpio26_mux = 0x01; + }else{ + gpio26_mux = 0x00; + } + + }else{ + gpio26_mux = 0x00; + } + } + printk("%s gpio26_mux:%d\n",__func__,gpio26_mux); + if(!gpio26_mux){ + /* disable GPIO and enable Pull Down mode */ + gpval &= ~0xff; + //GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~0xFF; // 0xFF bit1 + }else{ + gpval &= ~0xF7; + //GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~0xF7; + } + + // configure I2SDACDAT1/2/3 as GPIO function but mmax-wm8994 that configured as I2SADCLRC/I2SADCBLCK + if (!wmt_codec_wm8994) + { + gpval |= (BIT1 | BIT2 | BIT3); + //GPIO_CTRL_GP10_I2S_BYTE_VAL |= (BIT1 | BIT2 | BIT3); + + } + + //add for mainly i2sdacdat1 work as gpio to control led 2014-8-15 so here just keep it specific func + //tp driver will use it as gpio + switch (i2sdacdat_gpio) + { + case 1: + gpval |= BIT1; + //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT1; // i2sdacdat1 work as gpio + break; + case 2: + gpval |= BIT2; + //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT2; + break; + case 3: + gpval |= BIT3; + //GPIO_CTRL_GP10_I2S_BYTE_VAL |= BIT3; + break; + + } + GPIO_CTRL_GP10_I2S_BYTE_VAL = gpval; + + GPIO_CTRL_GP11_I2S_BYTE_VAL &= ~(BIT0 | BIT1 | BIT2); + + if(!gpio26_mux){ + PULL_EN_GP10_I2S_BYTE_VAL |= 0xFF; + }else{ + PULL_EN_GP10_I2S_BYTE_VAL |= 0xF7; + } + PULL_EN_GP11_I2S_BYTE_VAL |= (BIT0 | BIT1 | BIT2); + + if(!gpio26_mux){ + /* set to 2ch input, 2ch output */ + PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT15 | BIT17 | BIT19 | BIT20); + PIN_SHARING_SEL_4BYTE_VAL |= (BIT1 | BIT16 | BIT18); + }else{ + PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT15 | BIT17 | BIT20); + PIN_SHARING_SEL_4BYTE_VAL |= (BIT1 | BIT16); + } +} +EXPORT_SYMBOL(wmt_set_i2s_share_pin); + +static void i2s_init(int mode) +{ +#ifdef DEBUG + int ret; +#endif + int temp ; + //unsigned int clock = 0; + + DPRINTK("i2s_ref = %d ", i2s_data->i2s.ref); + + if (++i2s_data->i2s.ref > 1) + return; + + DBG_DETAIL(); + + if (!mode) { + /* set to 24.576MHz */ + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); + /*clock = auto_pll_divisor(DEV_I2S, GET_FREQ , 0, 0); + info("%s : clock=%d \n" , __func__, clock);*/ + + /* Enable BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL |= (BIT4 | BIT3); + + /* Enable BIT2:AUD clock */ + PMCE3_VAL |= BIT2; + + wmt_set_i2s_share_pin(); + } + + /* connect DZDRQ8 to ADC0 FIFO, DZDRQ9 to DAC FIFO and DZDRQA to ADC1 FIFO */ + DZDRQ8_CFG_VAL = 0x0; + DZDRQ9_CFG_VAL = 0x1; + DZDRQA_CFG_VAL = 0x2; + + DACCFG_VAL = 0x0; + ADCCFG_VAL = 0x0; + + /* little endian, signed format, enable sample FIFO, 16bit sample, 2 channel */ + ASMPFCFG_VAL = 0x52; + + /* assign ch0 to DAC#0_L, DAC#1_L, DAC#2_L, SPDIF_L, + ch1 to DAC#0_R, DAC#1_R, DAC#2_R, SPDIF_R */ + ASMPFCHCFG0_VAL = 0x10101010; + + /* assign ch0 to DAC#3_L, ch1 to DAC#3_R */ + ASMPFCHCFG1_VAL = 0x10; + + /* select ADCDAT0, 16 bits mode, enable AADCFIFO and AADCITF */ + AADCF0CFG_VAL = (AADCITF_ENABLE | AADCF16_ENABLE | AADCF_ENABLE); + + /* the sequence must be ARF_ADCCFG first then ARF_DGOCFG, finally ARF_DACCFG while slave mode, + otherwise will generate noise when record function is active */ + + /* ADC slave mode, 48K sample rate, I2S mode */ + ADCCFG_VAL = 0x2001; + + i2s_data->i2s.rate = 48000; + i2s_data->i2s.channels = 2; + i2s_data->i2s.format = SNDRV_PCM_FORMAT_S16_LE; + +#ifdef AUD_SPDIF_ENABLE + /* B1: 0 -> PCM, 1 -> Bitstream + B24 ~ B27: sample rate */ + /* set ADGO channel status for 48K sample rate */ + DGOCS0A_VAL = 0x02 << 24; + DGOCS1A_VAL = 0x02 << 24; + + /* enable ADGOITF and ADGOCKGEN for 48K sample rate */ + DGOCFG_VAL = 0x82; +#endif + + /* enable ADACITF and ADACCKGEN for 44.1K sample rate, I2S mode */ + DACCFG_VAL = 0x402001; + if (audio_interface_mode == 1) + { + printk("<<<<<%s left justified!\n", __func__); + //left justified bit20: 0->i2s, 1->l/r clock polarity + DACCFG_VAL |= 0x1 << 20; + //bit7:0 padding added to dac serial data output + //0 = LJ + //1 = I2S + //others = RJ (depends on Bit 11:8) + + DACCFG_VAL &= ~(0x1<<0); + + } + + /* enable HD Audio clock for 48K sample rate or not*/ + HDACKGEN_VAL = 0x12; + +#ifdef CONFIG_FB_WMT + /* pass information of audio to HDMI Audio */ + hd_audio_flag = vpp_set_audio(16, i2s_data->i2s.rate, i2s_data->i2s.channels); + ASMPF2HDACHCFG_VAL = 0x76543210; +#endif + + if (!hd_audio_flag) + HDACKGEN_VAL = 0; + + /* audio peri reset */ + temp = AUDPRFRST_VAL; + +#ifdef AUD_SPDIF_ENABLE + temp |= (ASMPF_RESET | DACITF_RESET | ADCITF_RESET | DGOITF_RESET); +#else + temp |= (ASMPF_RESET | DACITF_RESET | ADCITF_RESET); +#endif + + AUDPRFRST_VAL = temp; + + /* + request irq + */ +#ifdef CONFIG_WMT_I2S_INT + ret = request_irq(i2s_data->i2s.irq, + wmt_i2s_interrupt, + SA_INTERRUPT, + "wmt_alsa_vt1602", + NULL); + if (ret) + printk("%s : unable to request IRQ \n" , __func__); +#endif +} + +static void i2s_exit(void) +{ + DBG_DETAIL(); + + if (--i2s_data->i2s.ref) + return; + + DPRINTK("Do i2s_exit "); + +#ifdef CONFIG_WMT_I2S_INT + free_irq(i2s_data->i2s.irq, NULL); +#endif + + /* Reset counter.*/ + memset(&i2s_data->i2s.ints, 0, sizeof(struct i2s_ints_s)); + return; +} + +static int wmt_i2s_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &i2s_data->s[0]; + + DBG_DETAIL(); + + s[stream_id].stream = substream; + + runtime->private_data = s; + + return 0; +} + +static void wmt_i2s_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + DBG_DETAIL(); +} + +static int wmt_i2s_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int err = 0; + int stream_id = substream->pstr->stream; + + DBG_DETAIL(); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + ASMPFCFG_VAL |= ASMPF_ENABLE; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + AADCF0CFG_VAL |= AADCF_ENABLE; + } + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + ASMPFCFG_VAL &= ~ASMPF_ENABLE; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + AADCF0CFG_VAL &= ~AADCF_ENABLE; + } + + /* + mod_timer(&i2s_data->delay_timer, jiffies + HZ / 100); + */ + break; + default: + err = -EINVAL; + } + + return err; +} + +static int wmt_i2s_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + DBG_DETAIL(); + return 0; +} + +static int wmt_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int channel, byte; + int stream_id = substream->pstr->stream; + +#ifdef CONFIG_SND_OSSEMUL + //info("oss.rate=%d, oss.channels=%d", runtime->oss.rate, runtime->oss.channels); +#else + DBG_DETAIL(); +#endif + + byte = (runtime->sample_bits)/8; + channel = runtime->channels; + + DPRINTK("snd_wmt_alsa_prepare byte = %d, channels = %d", byte, runtime->channels); + + + if ((runtime->rate != i2s_data->i2s.rate) || (runtime->format != i2s_data->i2s.format) || + (runtime->channels != i2s_data->i2s.channels) || (stream_id != i2s_data->stream_id)) { + info("*** stream_id=%d, rate=%d, format=0x%x channels=%d ***", + stream_id, runtime->rate, runtime->format, runtime->channels); + i2s_data->i2s.format = runtime->format; + i2s_data->i2s.channels = runtime->channels; + i2s_data->stream_id = stream_id; + } + else { + return 0; + } + + i2s_disable(); + + /* format setting */ + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* little or big endian check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_BE: + case SNDRV_PCM_FORMAT_U16_BE: + case SNDRV_PCM_FORMAT_S24_BE: + case SNDRV_PCM_FORMAT_U24_BE: + case SNDRV_PCM_FORMAT_S32_BE: + case SNDRV_PCM_FORMAT_U32_BE: + ASMPFCFG_VAL |= ASMPF_EXCH_ENDIAN; + break; + default: + ASMPFCFG_VAL &= ~ASMPF_EXCH_ENDIAN; + break; + } + + /* unsigned or signed check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_U16_BE: + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_U24_BE: + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_U32_BE: + ASMPFCFG_VAL |= ASMPF_EXCH_FMT; + break; + default: + ASMPFCFG_VAL &= ~ASMPF_EXCH_FMT; + break; + } + + /* sample quantization check */ + ASMPFCFG_VAL &= ~(BIT4 | BIT5); + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S8: + case SNDRV_PCM_FORMAT_U8: + ASMPFCFG_VAL |= ASMPF_8BIT_SMP; + break; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_U16_BE: + ASMPFCFG_VAL |= ASMPF_16BIT_SMP; + break; + case SNDRV_PCM_FORMAT_S32_LE: + case SNDRV_PCM_FORMAT_S32_BE: + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_U32_BE: + ASMPFCFG_VAL |= ASMPF_32BIT_SMP; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_U24_BE: + info("*** Not Supported: fmt=24Bit ***"); + default: + break; + } + + /* channel number check */ + ASMPFCFG_VAL &= ~(BIT0 | BIT1 | BIT2 | BIT3); + ASMPFCFG_VAL |= runtime->channels; + + wmt_i2s_ch_config(); + info("Prepare: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); + } + + /* sample rate setting */ +#ifdef CONFIG_SND_OSSEMUL + if (runtime->oss.rate) { + i2s_sample_rate(runtime->oss.rate); + } + else { + i2s_sample_rate(runtime->rate); + } +#else + i2s_sample_rate(runtime->rate); +#endif + + i2s_enable(); + + /* + printk("avail_max=%d, rate=%d, channels=%d, period_size=%d, periods=%d, buffer_size=%d, tick_time=%d, \ + min_align=%d, byte_align=%d, frame_bits=%d, sample_bits=%d, sleep_min=%d, xfer_align=%d, boundary=%d\n", + runtime->avail_max, runtime->rate, runtime->channels, runtime->period_size, runtime->periods, + runtime->buffer_size, runtime->tick_time, runtime->min_align, runtime->byte_align, + runtime->frame_bits, runtime->sample_bits, + runtime->sleep_min, runtime->xfer_align, runtime->boundary); + */ + return 0; +} + +/* + * This must be called before _set_clkdiv and _set_sysclk since McBSP register + * cache is initialized here + */ +static int wmt_i2s_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + DBG_DETAIL(); + return 0; //add rambo 2013-3-13 + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + default: + /* Unsupported data format */ + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + /* Set bit clock (CLKX/CLKR) and FS polarities */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + /* + * Normal BCLK + FS. + * FS active low. TX data driven on falling edge of bit clock + * and RX data sampled on rising edge of bit clock. + */ + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + return 0; +} + +#ifdef CONFIG_PM +static int wmt_i2s_suspend(struct snd_soc_dai *cpu_dai) +{ + printk("%s!\n", __func__); + + DBG_DETAIL(); + + i2s_data->i2s.ref = 0; +#if 1 + if (gpio_pa >= 0) + { + gpio_direction_output(gpio_pa, !gpio_active); + } +#endif + return 0; +} + +static int wmt_i2s_resume(struct snd_soc_dai *cpu_dai) +{ + printk("%s!\n", __func__); + + DBG_DETAIL(); + + i2s_init(1); + + wmt_i2s_ch_config(); +#if 1 + if (gpio_pa >= 0) + { + gpio_direction_output(gpio_pa, gpio_active); + mdelay(50); + } +#endif + info("Resume: CHCFG0=0x%x, HDACHCFG=0x%x", ASMPFCHCFG0_VAL, ASMPF2HDACHCFG_VAL); + return 0; +} +#else +#define wmt_i2s_suspend NULL +#define wmt_i2s_resume NULL +#endif + +static struct snd_soc_dai_ops wmt_i2s_dai_ops = { + .startup = wmt_i2s_dai_startup, + .prepare = wmt_i2s_prepare, + .shutdown = wmt_i2s_dai_shutdown, + .trigger = wmt_i2s_dai_trigger, + .hw_params = wmt_i2s_dai_hw_params, + .set_fmt = wmt_i2s_dai_set_dai_fmt, +}; + +struct snd_soc_dai_driver wmt_i2s_dai = { + .suspend = wmt_i2s_suspend, + .resume = wmt_i2s_resume, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_FLOAT, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &wmt_i2s_dai_ops, +}; + +static int wmt_i2s_probe(struct platform_device *pdev) +{ + int ret; + char buf[64]; + int varlen = 64; + + DBG_DETAIL(); + + ret = wmt_getsyspara("wmt.audio.pcm", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &wmt_pdm_module_enable); + } + + ret = wmt_getsyspara("wmt.audio.i2s", buf, &varlen); + if (ret == 0) { + if (!strncmp(buf, "wm8994", strlen("wm8994"))) + wmt_codec_wm8994 = 1; + } + memset(buf, 0, sizeof(buf)); + ret = wmt_getsyspara("wmt.audio.codechdmi", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &i2s_data[0].HDMI_and_DAC0); + printk("<<<%s codec and hdmi:%d\n", __FUNCTION__, i2s_data[0].HDMI_and_DAC0); + } + + memset(buf, 0, sizeof(buf)); + //0:i2sdacdat0(usually use i2s function!!) 1:i2sdacdat1 2:i2sdacdat2 3:i2sdacdat3 + ret = wmt_getsyspara("wmt.audio.dacdat.gpio", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &i2sdacdat_gpio); + printk("<<<%s i2sdacdat_gpio:%d\n", __FUNCTION__, i2sdacdat_gpio); + } + + + memset(buf, 0, sizeof(buf)); + ret = wmt_getsyspara("wmt.audio.interface.mode", buf, &varlen); + if (ret == 0) { // 0:i2s 1:left justified 2:right justified + sscanf(buf, "%d", &audio_interface_mode); + printk("<<<%s audio_interface_mode:%d\n", __FUNCTION__, audio_interface_mode); + } + + memset(buf, 0, sizeof(buf)); + ret = wmt_getsyspara("wmt.audio.pa", buf, &varlen); + if (ret == 0) { // gpio nr:active---> 1:0 + sscanf(buf, "%d:%d", &gpio_pa, &gpio_active); + + } + printk("%s audio pa:%d:%d\n", __FUNCTION__, gpio_pa, gpio_active); + if (gpio_pa >= 0) + { + ret = gpio_request(gpio_pa, "audio pa"); + if (ret) + { + printk("%s gpio %d request error!\n", __func__, gpio_pa); + return ret; + } + #if 0 + if (gpio_active) + wmt_gpio_setpull(gpio_pa, WMT_GPIO_PULL_DOWN); + else + wmt_gpio_setpull(gpio_pa, WMT_GPIO_PULL_UP); + msleep(10); + gpio_direction_output(gpio_pa, !gpio_active); //???pop??? + printk("%s shutdown pa!\n", __func__); + msleep(100); + #endif + + + init_timer(&pa_timer); + pa_timer.function = pa_timer_handler; + } + i2s_data->s[0].dma_cfg = dma_device_cfg_table[AHB1_AUD_DMA_REQ_1]; + i2s_data->s[1].dma_cfg = dma_device_cfg_table[AHB1_AUD_DMA_REQ_0]; + /*init i2s controller*/ + i2s_data->i2s.init(0); + + spin_lock_init(&i2s_data->s[0].dma_lock); + spin_lock_init(&i2s_data->s[1].dma_lock); + /*init_timer(&i2s_data->delay_timer); + i2s_data->delay_timer.function = delay_timer_handler;*/ + + /* register with the ASoC layers */ + ret = snd_soc_register_dai(&pdev->dev, &wmt_i2s_dai); + if (ret) { + pr_err("Failed to register DAI: %d\n", ret); + return ret; + } + + if (gpio_pa >= 0) + { + mod_timer(&pa_timer, jiffies+msecs_to_jiffies(10000)); + } + return 0; +} + +static int __devexit wmt_i2s_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + + snd_soc_unregister_dai(&pdev->dev); + + return 0; +} + +static void wmt_i2s_plat_shutdown(struct platform_device *pdev) +{ + printk("%s!\n", __func__); + if (gpio_pa >= 0) + { + gpio_direction_output(gpio_pa, !gpio_active); + } +} + +static struct platform_driver wmt_i2s_driver = { + .probe = wmt_i2s_probe, + .remove = __devexit_p(wmt_i2s_remove), + //.suspend = wmt_i2s_plat_suspend, + //.resume = wmt_i2s_plat_resume, + .shutdown = wmt_i2s_plat_shutdown, + .driver = { + .name = "wmt-i2s", + .owner = THIS_MODULE, + }, +}; + + +static int __init wmt_i2s_init(void) +{ + DBG_DETAIL(); + + return platform_driver_register(&wmt_i2s_driver); +} + +static void __exit wmt_i2s_exit(void) +{ + DBG_DETAIL(); + + platform_driver_unregister(&wmt_i2s_driver); +} + +module_init(wmt_i2s_init); +module_exit(wmt_i2s_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); +MODULE_LICENSE("GPL"); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c new file mode 100755 index 00000000..dccb82c6 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.c @@ -0,0 +1,419 @@ +/*++ + * linux/sound/soc/wmt/wmt-pdm-if.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <asm/irq.h> +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include <mach/hardware.h> +#include <asm/dma.h> +#include "wmt-soc.h" +#include "wmt-pcm-controller.h" + +#define PCM_IS_MASTER_MODE +#define NULL_DMA ((dmach_t)(-1)) + +static int wmt_pcm_module_enable = 0; +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); + +static struct audio_stream_a wmt_pcm_controller_data[] = { + { + .id = "WMT PCM out", + .stream_id = SNDRV_PCM_STREAM_PLAYBACK, + .dmach = NULL_DMA, + .dma_dev = PCM_TX_DMA_REQ, + /*.dma_cfg = dma_device_cfg_table[I2S_TX_DMA_REQ],*/ + }, + { + .id = "WMT PCM in", + .stream_id = SNDRV_PCM_STREAM_CAPTURE, + .dmach = NULL_DMA, + .dma_dev = PCM_RX_DMA_REQ, + /*.dma_cfg = dma_device_cfg_table[I2S_RX_DMA_REQ],*/ + }, +}; + +struct wmt_pcm_controller +{ + int irq_no; + int pcm_clk_src; + int pcm_enable; +}; + +static struct wmt_pcm_controller wmt_pcm_controller; + +static irqreturn_t +wmt_pcm_controller_irq_handler(int irq, void *dev_id) +{ + if (PCMSR_VAL & PCMSR_TXUND) { + printk("-->PCMSR_TXUND\n"); + } + else if (PCMSR_VAL & PCMSR_RXOVR) { + printk("-->PCMSR_RXOVR\n"); + } + + PCMSR_VAL = 0x7F; //write clear all intr + return IRQ_HANDLED; +} + +static void wmt_pcm_controller_enable(void) +{ + PCMCR_VAL |= (PCMCR_PCM_ENABLE | PCMCR_DMA_EN); +} + +static void wmt_pcm_controller_disable(void) +{ + PCMCR_VAL &= ~(PCMCR_PCM_ENABLE | PCMCR_DMA_EN); +} + +static int wmt_pcm_controller_init(void) +{ + wmt_pcm_controller.irq_no = IRQ_PCM; + + // Before control pcm-module, enable pcm clock first + CLOCKEN(27); + + // set pcm_clk_source = 62464khz + //auto_pll_divisor(DEV_PCM0, CLK_ENABLE, 0, 0); + //auto_pll_divisor(DEV_PCM0, SET_PLLDIV, 1, 83333); + + wmt_pcm_controller.pcm_clk_src = auto_pll_divisor(DEV_PCM0, GET_FREQ, 0, 0); + wmt_pcm_controller.pcm_clk_src /= 1000; + wmt_pcm_controller.pcm_enable = 0; + printk("wmt_pcm_controller_init: pcm_clk_src=%d \n\r", wmt_pcm_controller.pcm_clk_src); + + // Note: you should config PIN_SHARING_SEL_4BYTE_VAL if using pcm function!!! Loon mark at 2013/4/10 + if (wmt_pcm_module_enable) { + printk("begin to configure pcm pin\n"); + /* disable GPIO and Pull Down mode */ + /* Bit1:I2SDACDAT1=PCMSYNC, Bit2:I2SDACDAT2=PCMCLK, Bit3:I2SDACDAT3=PCMIN, Bit4:I2SADCMCLK=PCMOUT */ + GPIO_CTRL_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); + GPIO_CTRL_GP11_I2S_BYTE_VAL &= ~(BIT1); + /*disable pull enable*/ + PULL_EN_GP10_I2S_BYTE_VAL &= ~(BIT1 | BIT2 | BIT3 | BIT4); + PULL_EN_GP11_I2S_BYTE_VAL &= ~(BIT1); + + /* set to pcm mode */ + // select PCMMCLK[bit0], PCMSYNC[bit17:16], PCMCLK[19:18], PCMIN[20], PCMOUT[22:21] + //GPIO_PIN_SHARING_SEL_4BYTE_VAL &= ~(BIT21); + GPIO_PIN_SHARING_SEL_4BYTE_VAL |= (BIT15 | BIT16 | BIT17 | BIT18 | BIT19 |BIT20); + } + + // set pcm control register + PCMCR_VAL |= (PCMCR_TXFF_RST | PCMCR_RXFF_RST); + //PCMCR_VAL |= 0x00880000; // TX/RX Fifo Threshold A + PCMCR_VAL &= ~(PCMCR_BCLK_SEL); + PCMCR_VAL &= ~PCMCR_SLAVE; // master mode + PCMCR_VAL |= PCMCR_SYNC_MODE;//short frame sync + + // set pcm format register + PCMDFCR_VAL = 0; + PCMDFCR_VAL |= (PCMDFCR_WR_AL | PCMDFCR_TX_AL | PCMDFCR_RX_AL | PCMDFCR_RD_AL); + PCMDFCR_VAL |= (PCMDFCR_TX_SZ_14 | PCMDFCR_RX_SZ_14); + //PCMDFCR_VAL |= (PCMDFCR_TX_SZ_08 | PCMDFCR_RX_SZ_08); + + // + // request irq + // + /*if (request_irq(wmt_pcm_controller.irq_no, &wmt_pcm_controller_irq_handler, IRQF_DISABLED, "wmt_pcm_controller", NULL)){ + printk(KERN_ERR "PCM_IRQ Request Failed!\n"); + } + PCMCR_VAL |= (PCMCR_IRQ_EN | PCMCR_TXUND_EN | PCMCR_RXOVR_EN); + */ + printk("fun:%s,line:%d\n",__func__,__LINE__); + return 0 ; +} + + +static int wmt_pcm_controller_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &wmt_pcm_controller_data[0]; + //dump_stack(); + s[stream_id].stream = substream; + runtime->private_data = s; + + return 0; +} + +static void wmt_pcm_controller_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + //dump_stack(); +} + +static int wmt_pcm_controller_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + int err = 0; + int stream_id = substream->pstr->stream; + //dump_stack(); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + PCMCR_VAL |= PCMCR_TXFF_RST; // reset txfifo + PCMDFCR_VAL |= PCMDFCR_TXFM_EN; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + PCMCR_VAL |= PCMCR_RXFF_RST; // reset rxfifo + PCMDFCR_VAL |= PCMDFCR_RXFM_EN; + } + //wmt_pcm_controller_enable(); + wmt_pcm_controller.pcm_enable++; + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + PCMDFCR_VAL &= ~PCMDFCR_TXFM_EN; + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + PCMDFCR_VAL &= ~PCMDFCR_RXFM_EN; + } + //wmt_pcm_controller_disable(); + wmt_pcm_controller.pcm_enable--; + break; + default: + err = -EINVAL; + break; + } + + if (wmt_pcm_controller.pcm_enable) + wmt_pcm_controller_enable(); + else + wmt_pcm_controller_disable(); + + return err; +} + +static int wmt_pcm_controller_dai_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + //dump_stack(); + return 0; +} + +static int wmt_pcm_controller_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int channel, byte; + int stream_id = substream->pstr->stream; + + byte = (runtime->sample_bits)/8; + channel = runtime->channels; + + printk(KERN_INFO "wmt_pcm_controller_dai_prepare byte = %d, channels = %d\n", byte, runtime->channels); + + /* format setting */ + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* little or big endian check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + break; + } + + /* channel number check */ + switch (runtime->channels) { + case 1: + break; + case 2: + break; + default: + break; + } + } + else if (stream_id == SNDRV_PCM_STREAM_CAPTURE) { + /* little or big endian check */ + switch (runtime->format) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + default: + break; + } + + /* channel number check */ + switch (runtime->channels) { + case 1: + break; + case 2: + break; + default: + break; + } + } + + switch (runtime->rate) { + case 16000: + PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; + PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_256K); + break; + case 8000: + PCMDIVR_VAL &= ~PCMCLK_DIV_MASK; + PCMDIVR_VAL |= (wmt_pcm_controller.pcm_clk_src / PCMCLK_128K); + break; + default : + printk(KERN_ERR "not supported fs: %d \n\r", runtime->rate); + break; + } + + return 0; +} + +/* + * This must be called before _set_clkdiv and _set_sysclk since McBSP register + * cache is initialized here + */ +static int wmt_pcm_controller_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + return 0; +} + +#ifdef CONFIG_PM +static int wmt_pcm_controller_suspend(struct snd_soc_dai *cpu_dai) +{ + return 0; +} + +static int wmt_pcm_controller_resume(struct snd_soc_dai *cpu_dai) +{ + int ret = wmt_pcm_controller_init(); + if (ret) { + pr_err("Failed to init pcm module: %d\n", ret); + return ret; + } + return 0; +} +#else +#define wmt_pcm_controller_suspend NULL +#define wmt_pcm_controller_resume NULL +#endif + +static struct snd_soc_dai_ops wmt_pcm_controller_dai_ops = { + .startup = wmt_pcm_controller_dai_startup, + .prepare = wmt_pcm_controller_dai_prepare, + .shutdown = wmt_pcm_controller_dai_shutdown, + .trigger = wmt_pcm_controller_dai_trigger, + .hw_params = wmt_pcm_controller_dai_hw_params, + .set_fmt = wmt_pcm_controller_dai_set_dai_fmt, +}; + +struct snd_soc_dai_driver wmt_pcm_controller_dai = { + .suspend = wmt_pcm_controller_suspend, + .resume = wmt_pcm_controller_resume, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &wmt_pcm_controller_dai_ops, +}; + +static int wmt_pcm_controller_probe(struct platform_device *pdev) +{ + int ret = 0; + char buf[64]; + int varlen = 64; + + ret = wmt_getsyspara("wmt.audio.pcm", buf, &varlen); + if (ret == 0) { + sscanf(buf, "%d", &wmt_pcm_module_enable); + } + + ret = wmt_pcm_controller_init(); + if (ret) { + pr_err("Failed to init pcm module: %d\n", ret); + return ret; + } + + /* register with the ASoC layers */ + ret = snd_soc_register_dai(&pdev->dev, &wmt_pcm_controller_dai); + if (ret) { + pr_err("Failed to register DAI: %d\n", ret); + return ret; + } + + wmt_pcm_controller_data[0].dma_cfg = dma_device_cfg_table[PCM_TX_DMA_REQ]; + wmt_pcm_controller_data[1].dma_cfg = dma_device_cfg_table[PCM_RX_DMA_REQ]; + + spin_lock_init(&wmt_pcm_controller_data[0].dma_lock); + spin_lock_init(&wmt_pcm_controller_data[1].dma_lock); + + return 0; +} + +static int __devexit wmt_pcm_controller_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_controller_driver = { + .probe = wmt_pcm_controller_probe, + .remove = __devexit_p(wmt_pcm_controller_remove), + .driver = { + .name = "wmt-pcm-controller", + .owner = THIS_MODULE, + }, +}; + + +static int __init wmt_pcm_controller_module_init(void) +{ + return platform_driver_register(&wmt_pcm_controller_driver); +} + +static void __exit wmt_pcm_controller_module_exit(void) +{ + platform_driver_unregister(&wmt_pcm_controller_driver); +} + +module_init(wmt_pcm_controller_module_init); +module_exit(wmt_pcm_controller_module_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC] driver"); +MODULE_LICENSE("GPL"); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h new file mode 100755 index 00000000..58e94f23 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-controller.h @@ -0,0 +1,122 @@ +/*++ +linux/include/asm-arm/arch-wmt/wmt_pcm.h + +Copyright (c) 2008 WonderMedia Technologies, Inc. + +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, see <http://www.gnu.org/licenses/>. + +WonderMedia Technologies, Inc. +10F, 529, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C. +--*/ + +/* Be sure that virtual mapping is defined right */ + +#ifndef __WMT_PCM_CONTROLLER_H +#define __WMT_PCM_CONTROLLER_H + +/* + * Address + */ +#define PCM_CR_ADDR (0x0000+PCM_BASE_ADDR) +#define PCM_SR_ADDR (0x0004+PCM_BASE_ADDR) +/* Reserved 0x0008 ~ 0x000F */ +#define PCM_DFCR_ADDR (0x0008+PCM_BASE_ADDR) +#define PCM_DIVR_ADDR (0x000C+PCM_BASE_ADDR) +/* Reserved 0x0020 ~ 0x007F */ +#define PCM_TFIFO_ADDR (0x0010+PCM_BASE_ADDR) +#define PCM_TFIFO_1_ADDR (0x0014+PCM_BASE_ADDR) + +#define PCM_RFIFO_ADDR (0x0030+PCM_BASE_ADDR) +#define PCM_RFIFO_1_ADDR (0x0034+PCM_BASE_ADDR) +/* Reserved 0x0100 ~ 0xFFFF */ + +/* + * Value + */ +#define PCMCR_VAL (REG32_VAL(PCM_CR_ADDR)) +#define PCMSR_VAL (REG32_VAL(PCM_SR_ADDR)) +#define PCMDFCR_VAL (REG32_VAL(PCM_DFCR_ADDR)) +#define PCMDIVR_VAL (REG32_VAL(PCM_DIVR_ADDR)) +#define GPIO_PIN_SHARING_SEL_4BYTE_VAL (REG32_VAL(SHARE_PIN_SELEC)) + +#define PCMCLK_DIV_MASK 0x7FF +#define PLL_B_DIVF_MASK 0xFF0000 +#define PLL_B_DIVR_MASK 0x1F00 +#define PLL_B_DIVQ_MASK 0x07 +#define OSC_25M 25000 +#define PCMCLK_128K 128 +#define PCMCLK_256K 256 + + +//============================================================================= +// +// PCMCR PCM Control Register +// +//============================================================================= +#define PCMCR_PCM_ENABLE (BIT0) /* PCM Interface Enable */ +#define PCMCR_SLAVE (BIT1) /* Master/Slave Mode */ + +#define PCMCR_BCLK_SEL (BIT3) /* PCM_CLK Select */ +#define PCMCR_SYNC_MODE (BIT4) /* Frame Sync Mode: 0-long, 1-Short */ +#define PCMCR_DMA_EN (BIT5) /* DMA Enable */ +#define PCMCR_SYNC_OFF (BIT6) /* Sync Disable */ +#define PCMCR_MUTE (BIT7) /* Mute Enable */ +#define PCMCR_IRQ_EN (BIT8) /* Interrupt Enable */ +#define PCMCR_DMA_IRQ_SEL (BIT9) /* Threshold DMA/IRQ Select */ +#define PCMCR_RXFE_EN (BIT10) /* RX FIFO Empty Interrupt Enable */ +#define PCMCR_RXFF_EN (BIT11) /* RX FIFO Full Interrupt Enable */ +#define PCMCR_RXOVR_EN (BIT12) /* RX FIFO Overrun Interrupt Enable */ +#define PCMCR_TXFE_EN (BIT13) /* TX FIFO Empty Interrupt Enable */ +#define PCMCR_TXUND_EN (BIT14) /* TX FIFO Underrun Interrupt Enable */ + +#define PCMCR_TXFF_RST (BIT24) /* TX FIFO Reset */ +#define PCMCR_RXFF_RST (BIT25) /* RX FIFO Reset */ + + +//============================================================================= +// +// PCMSR PCM Status Register +// +//============================================================================= +#define PCMSR_RXFE (BIT0) /* RX FIFO Empty Status */ +#define PCMSR_RXFF (BIT1) /* RX FIFO Full Status */ +#define PCMSR_RXOVR (BIT2) /* RX FIFO Overrun Status */ +#define PCMSR_TXFE (BIT3) /* TX FIFO Empty Status */ +#define PCMSR_TXUND (BIT4) /* TX FIFO Underrun Status */ +#define PCMSR_TXFAE (BIT5) /* TX FIFO Almost Empty Status */ +#define PCMSR_RXFAF (BIT6) /* RX FIFO Almost Full Status */ + + +//============================================================================= +// +// PCMDFCR PCM Data Format Control Register +// +//============================================================================= +#define PCMDFCR_TXFM_EN (BIT0) /* TX Data Format Enable */ +#define PCMDFCR_TX_SZ_13 0x00 /* TX Input Data Size 13 bits wide */ +#define PCMDFCR_TX_SZ_14 0x02 /* TX Input Data Size 14 bits wide */ +#define PCMDFCR_TX_SZ_08 0x04 /* TX Input Data Size 8 bits wide */ +#define PCMDFCR_WR_AL (BIT3) /* TX Write Data Alignment Setup */ +#define PCMDFCR_TX_AL (BIT4) /* PCM_OUT Alignment Control */ +#define PCMDFCR_TX_PAD (BIT5) /* PCM_OUT Padding Control */ +#define PCMDFCR_TX_MSB (BIT6) /* PCM_OUT First Bit Select */ + +#define PCMDFCR_RXFM_EN (BIT8) /* RX Data Format Enable */ +#define PCMDFCR_RX_SZ_13 0x0000 /* RCM_IN Data Size 13 bits wide */ +#define PCMDFCR_RX_SZ_14 0x0200 /* RCM_IN Data Size 14 bits wide */ +#define PCMDFCR_RX_SZ_08 0x0400 /* RCM_IN Data Size 8 bits wide */ +#define PCMDFCR_RX_AL (BIT11) /* RCM_IN Data Alignment Setup */ +#define PCMDFCR_RD_AL (BIT12) /* PX FIFO Alignment Control */ +#define PCMDFCR_RX_PAD (BIT13) /* PX FIFO Padding Control */ +#define PCMDFCR_RX_MSB (BIT14) /* RX FIFO First Bit Select */ + + +#endif /* __WMT_PCM_CONTROLLER_H */ diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c new file mode 100755 index 00000000..27a66bea --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.c @@ -0,0 +1,597 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.c + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/delay.h> + +#include <asm/dma.h> +#include "wmt-pcm-dma.h" +#include "wmt-soc.h" + +#define NULL_DMA ((dmach_t)(-1)) + +/* + * Debug + */ +#define AUDIO_NAME "WMT_PCM_DMA" +//#define WMT_PCM_DEBUG 1 +//#define WMT_PCM_DEBUG_DETAIL 1 + +#ifdef WMT_PCM_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_PCM_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +static const struct snd_pcm_hardware wmt_pcm_dma_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 16000, + .period_bytes_min = 32, + .period_bytes_max = 4 * 1024, + .periods_min = 1, + .periods_max = 32, + .buffer_bytes_max = 16 * 1024, +}; + +static int audio_dma_free(struct audio_stream_a *s); + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream_a *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + dma_addr_t dma_base; + int ret = 0; + + DPRINTK("s: %d, dmach: %d. active: %d", (int)s, s->dmach, s->active); + + if (s->active) { + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + offset = dma_size * s->period; + + dma_base = __virt_to_phys((dma_addr_t)runtime->dma_area); + + if ((runtime->channels == 2 || runtime->channels == 1) && + (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + ret = wmt_start_dma(s->dmach, runtime->dma_addr + offset, 0, dma_size); + } + + if (ret) { + printk(KERN_ERR "audio_process_dma: cannot queue DMA buffer (%i) \n", ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +static void audio_dma_callback(void *data) +{ + struct audio_stream_a *s = data; + + //DBG_DETAIL(); + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +static int audio_dma_request(struct audio_stream_a *s, void (*callback) (void *)) +{ + int err; + err = 0; + + DBG_DETAIL(); + + //DPRINTK("s pointer: %d, dmach: %d, id: %s, dma_dev: %d", (int)s, s->dmach, s->id, s->dma_dev); + err = wmt_request_dma(&s->dmach, s->id, s->dma_dev, callback, s); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dmach); + + return err; +} + +static void audio_setup_dma(struct audio_stream_a *s, int stream_id) +{ + struct snd_pcm_runtime *runtime = s->stream->runtime; + + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* From memory to device */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_8BITS_CFG; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_16BITS_CFG; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = PCM_TX_DMA_32BITS_CFG; /* setup 4 byte*/ + break ; + } + } + else { + /* From device to memory */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_8BITS_CFG ; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_16BITS_CFG ; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = PCM_RX_DMA_32BITS_CFG ; /* setup 4 byte*/ + break ; + } + } + + s->dma_cfg.ChunkSize = 1; + + wmt_setup_dma(s->dmach, s->dma_cfg) ; +} + +static int audio_dma_free(struct audio_stream_a *s) +{ + int err = 0; + DBG_DETAIL(); + wmt_free_dma(s->dmach); + s->dmach = NULL_DMA; + return err; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream_a *s) +{ + //dump_stack(); + unsigned long flags; + DBG_DETAIL(); + local_irq_save(flags); + s->active = 0; + s->period = 0; + s->periods = 0; + s->offset = 0; + wmt_stop_dma(s->dmach); + wmt_clear_dma(s->dmach); + local_irq_restore(flags); +} + +/* this may get called several times by oss emulation */ +static int wmt_pcm_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + //dump_stack(); + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + return err; +} + +static int wmt_pcm_dma_hw_free(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int wmt_pcm_dma_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + //dump_stack(); + DBG_DETAIL(); + + s->period = 0; + s->periods = 0; + s->offset = 0; + audio_setup_dma(s, stream_id); + + return 0; +} + +static int wmt_pcm_dma_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + int ret = 0; + + DPRINTK("Enter, cmd=%d", cmd); + //dump_stack(); + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + audio_process_dma(s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + audio_stop_dma(s); + break; + default: + ret = -EINVAL; + } + spin_unlock(&s->dma_lock); + + return ret; +} + +static snd_pcm_uframes_t wmt_pcm_dma_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *prtd = runtime->private_data; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &prtd[stream_id]; + dma_addr_t ptr; + snd_pcm_uframes_t offset = 0; + //dump_stack(); + ptr = wmt_get_dma_pos(s->dmach); + + if ((runtime->channels == 1 || runtime->channels == 2) && (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } + + if (offset >= runtime->buffer_size) + offset = 0; + + spin_lock(&s->dma_lock); + + if (s->periods > 0 && s->periods < 2) { + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_pcm_playback_hw_avail(runtime) >= 2 * runtime->period_size) + audio_process_dma(s); + } + else { + if (snd_pcm_capture_hw_avail(runtime) >= 2* runtime->period_size) + audio_process_dma(s); + } + + } + spin_unlock(&s->dma_lock); + + return offset; +} + +static int wmt_pcm_dma_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + int ret; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_request(&s[0], audio_dma_callback); + audio_dma_request(&s[1], audio_dma_callback); + } + //dump_stack(); + snd_soc_set_runtime_hwparams(substream, &wmt_pcm_dma_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wmt_pcm_dma_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + + DBG_DETAIL(); + //dump_stack(); + if (!cpu_dai->active) { + audio_dma_free(&s[0]); + audio_dma_free(&s[1]); + } + + return 0; +} + +static int wmt_pcm_dma_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG_DETAIL(); + //dump_stack(); + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops wmt_pcm_dma_ops = { + .open = wmt_pcm_dma_open, + .close = wmt_pcm_dma_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = wmt_pcm_dma_hw_params, + .hw_free = wmt_pcm_dma_hw_free, + .prepare = wmt_pcm_dma_prepare, + .trigger = wmt_pcm_dma_trigger, + .pointer = wmt_pcm_dma_pointer, + .mmap = wmt_pcm_dma_mmap, +}; + +static u64 wmt_pcm_dma_dmamask = DMA_BIT_MASK(32); + +static int wmt_pcm_dma_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = wmt_pcm_dma_hardware.buffer_bytes_max; + + DBG_DETAIL(); + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + DPRINTK("buf_area = %x, buf_addr = %x", (unsigned int)buf->area, buf->addr); + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void wmt_pcm_dma_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG_DETAIL(); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, buf->addr); + + buf->area = NULL; + } +} + +static int wmt_pcm_dma_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + DBG_DETAIL(); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &wmt_pcm_dma_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = wmt_pcm_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = wmt_pcm_dma_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + wmt_pcm_dma_free_dma_buffers(pcm); + + return ret; +} + +#ifdef CONFIG_PM +static int wmt_pcm_dma_suspend(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + return 0; +} + +static int wmt_pcm_dma_resume(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + audio_setup_dma(s, SNDRV_PCM_STREAM_PLAYBACK); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + audio_setup_dma(s, SNDRV_PCM_STREAM_CAPTURE); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + return 0; +} +#else +#define wmt_pcm_dma_suspend NULL +#define wmt_pcm_dma_resume NULL +#endif + +static struct snd_soc_platform_driver wmt_soc_platform = { + .ops = &wmt_pcm_dma_ops, + .pcm_new = wmt_pcm_dma_new, + .pcm_free = wmt_pcm_dma_free_dma_buffers, + .suspend = wmt_pcm_dma_suspend, + .resume = wmt_pcm_dma_resume, +}; + +static int __devinit wmt_pcm_dma_platform_probe(struct platform_device *pdev) +{ + DBG_DETAIL(); + return snd_soc_register_platform(&pdev->dev, &wmt_soc_platform); +} + +static int __devexit wmt_pcm_dma_platform_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_dma_driver = { + .driver = { + .name = "wmt-pcm-dma", + .owner = THIS_MODULE, + }, + + .probe = wmt_pcm_dma_platform_probe, + .remove = __devexit_p(wmt_pcm_dma_platform_remove), +}; + +module_platform_driver(wmt_pcm_dma_driver); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/pcm dma] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h new file mode 100755 index 00000000..88f246b2 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm-dma.h @@ -0,0 +1,35 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.h + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_PCM_DMA_H__ +#define __WMT_PCM_DMA_H__ + +struct wmt_pcm_dma_data { + char *name; /* stream identifier */ + int dma_req; /* DMA request line */ + unsigned long port_addr; /* transmit/receive register */ + struct dma_device_cfg_s *dma_cfg; +}; + +#endif /* __WMT_PDM_PCM_H__ */ diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c new file mode 100755 index 00000000..848ea5a1 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.c @@ -0,0 +1,767 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.c + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> + +#include <linux/dma-mapping.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <linux/delay.h> + +#include <asm/dma.h> +#include "wmt-pcm.h" +#include "wmt-soc.h" + +#define NULL_DMA ((dmach_t)(-1)) + +#define AUDIO_NAME "WMT_PCM" +//#define WMT_PCM_DEBUG 1 +//#define WMT_PCM_DEBUG_DETAIL 1 + +#ifdef WMT_PCM_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_PCM_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +static const struct snd_pcm_hardware wmt_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 96000, + .period_bytes_min = 4*1024,//32, + .period_bytes_max = 8*1024,//4 * 1024, + .periods_min = 1,//2, + .periods_max = 16,//128, + .buffer_bytes_max = 64 * 1024, + .fifo_size = 32,// +}; + +struct wmt_runtime_data { + spinlock_t lock; + struct wmt_pcm_dma_data *dma_data; + int dma_ch; + int period_index; +}; + +struct snd_wfd_buffer { + struct device *dev; + unsigned char *area; /* virtual pointer */ + dma_addr_t addr; /* physical address */ + size_t bytes; /* buffer size in bytes */ + size_t valuable_sz; /* valuable size in wfd buffer */ + unsigned char *wr_ptr; + unsigned char *rd_ptr; + int enable; +}; +static struct snd_wfd_buffer wfd_audbuf; + +static int audio_dma_free(struct audio_stream_a *s); + +dmach_t pcm_out_dmach = 0xFF; +struct dma_device_cfg_s *pcm_out_dma_cfg = NULL; + +void wmt_pcm_wfd_start(void) +{ + /* allocate buffer for WFD support */ + wfd_audbuf.area = dma_alloc_writecombine(wfd_audbuf.dev, wfd_audbuf.bytes, + &wfd_audbuf.addr, GFP_KERNEL); + + if (!wfd_audbuf.area) { + err("WFD_Aud allocate buffer fail"); + } + else { + info("*WFD_Aud enable"); + wfd_audbuf.enable = 1; + } + + memset(wfd_audbuf.area, 0x0, wfd_audbuf.bytes); + //info("&wfd_audbuf.addr=0x%x wfd_audbuf.addr=0x%x", &wfd_audbuf.addr, (unsigned int)wfd_audbuf.addr); +} +EXPORT_SYMBOL(wmt_pcm_wfd_start); + +unsigned int wmt_pcm_wfd_get_buf(void) +{ + wfd_audbuf.valuable_sz = 0; + wfd_audbuf.wr_ptr = wfd_audbuf.rd_ptr = wfd_audbuf.area; + return (unsigned int)&wfd_audbuf.addr; +} +EXPORT_SYMBOL(wmt_pcm_wfd_get_buf); + +void wmt_pcm_wfd_stop(void) +{ + wfd_audbuf.wr_ptr = wfd_audbuf.area; + wfd_audbuf.enable = 0; + + dma_free_writecombine(wfd_audbuf.dev, wfd_audbuf.bytes, + wfd_audbuf.area, wfd_audbuf.addr); + info("*WFD_Aud disable"); + return; +} +EXPORT_SYMBOL(wmt_pcm_wfd_stop); + +int wmt_pcm_wfd_get_strm(WFDStrmInfo_t *info) +{ + if ((info->req_sz > wfd_audbuf.bytes) || (!wfd_audbuf.valuable_sz)) { + //info("WFD read size=%d, Too Large!", info->req_sz); + info->avail_sz = 0; + return (int)info; + } + else if (wfd_audbuf.valuable_sz > info->req_sz) { + info->avail_sz = info->req_sz; + wfd_audbuf.valuable_sz -= info->req_sz; + } + else { + info->avail_sz = wfd_audbuf.valuable_sz; + wfd_audbuf.valuable_sz = 0; + } + + info->buf_offset = wfd_audbuf.rd_ptr - wfd_audbuf.area; + wfd_audbuf.rd_ptr += info->avail_sz; + + if (wfd_audbuf.rd_ptr >= wfd_audbuf.area + wfd_audbuf.bytes) + wfd_audbuf.rd_ptr = wfd_audbuf.rd_ptr - wfd_audbuf.bytes; + + return (int)info; +} +EXPORT_SYMBOL(wmt_pcm_wfd_get_strm); + +void wmt_pcm_wfd_update(char *src_buf, unsigned int chunksize) +{ + //info("wmt_pcm_wfd_update, 0x%x", src_buf[1024]); + memcpy(wfd_audbuf.wr_ptr, src_buf, chunksize); + wfd_audbuf.wr_ptr += chunksize; + + if (wfd_audbuf.wr_ptr == wfd_audbuf.area + wfd_audbuf.bytes) + wfd_audbuf.wr_ptr = wfd_audbuf.area; + + wfd_audbuf.valuable_sz += chunksize; + if (wfd_audbuf.valuable_sz >= wfd_audbuf.bytes) { + wfd_audbuf.valuable_sz = wfd_audbuf.bytes; + wfd_audbuf.rd_ptr = wfd_audbuf.wr_ptr; + } +} + +/* + * Main dma routine, requests dma according where you are in main alsa buffer + */ +static void audio_process_dma(struct audio_stream_a *s) +{ + struct snd_pcm_substream *substream = s->stream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + dma_addr_t dma_base; + int ret = 0; + + //DBG_DETAIL(); + DPRINTK("s: %d, dmach: %d. active: %d", (int)s, s->dmach, s->active); + + if (s->active) { + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + /*DPRINTK("frame_bits=%d, period_size=%d, dma_size 1=%d", + runtime->frame_bits, (int)runtime->period_size, dma_size);*/ + if (dma_size > MAX_DMA_SIZE) + dma_size = CUT_DMA_SIZE; + offset = dma_size * s->period; + + /*DPRINTK("offset: 0x%x, ->dma_area: 0x%x, ->dma_addr: 0x%x, final addr: 0x%x", + offset, (unsigned int)runtime->dma_area, runtime->dma_addr, runtime->dma_addr+offset);*/ + + dma_base = __virt_to_phys((dma_addr_t)runtime->dma_area); + + //DPRINTK("dma address: 0x%x", dma_base+offset); + /*DPRINTK("hw_ptr_interrupt: 0x%x, state: %d, hwptr: %u, applptr: %u, avail_min: %u", + (unsigned int)runtime->hw_ptr_interrupt, runtime->status->state, (unsigned int)runtime->status->hw_ptr, + (unsigned int)runtime->control->appl_ptr, (unsigned int)runtime->control->avail_min);*/ + //DPRINTK("dmach: %u, dma_addr: %x, dma_size: %u", s->dmach, dma_base+offset, dma_size); + + if ((runtime->channels == 2 || runtime->channels == 1) && + (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + ret = wmt_start_dma(s->dmach, runtime->dma_addr + offset, 0, dma_size); + } + + if (ret) { + printk(KERN_ERR "audio_process_dma: cannot queue DMA buffer (%i) \n", ret); + return; + } + + s->period++; + s->period %= runtime->periods; + s->periods++; + s->offset = offset; + } +} + +/* + * This is called when dma IRQ occurs at the end of each transmited block + */ +static void audio_dma_callback(void *data) +{ + struct audio_stream_a *s = data; + struct snd_pcm_substream *substream; + struct snd_pcm_runtime *runtime; + unsigned int dma_size; + unsigned int offset; + int stream_id; + + //DBG_DETAIL(); + + substream = s->stream; + runtime = substream->runtime; + dma_size = frames_to_bytes(runtime, runtime->period_size); + stream_id = substream->pstr->stream; + + if (s->period > 0) + offset = dma_size * (s->period - 1); + else + offset = dma_size * (s->periods - 1); + + if ((stream_id == SNDRV_PCM_STREAM_PLAYBACK) && (wfd_audbuf.enable)) { + wmt_pcm_wfd_update(runtime->dma_area + offset, dma_size); + } + + /* + * If we are getting a callback for an active stream then we inform + * the PCM middle layer we've finished a period + */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + spin_lock(&s->dma_lock); + if (s->periods > 0) + s->periods--; + + audio_process_dma(s); + spin_unlock(&s->dma_lock); +} + +static int audio_dma_request(struct audio_stream_a *s, void (*callback) (void *)) +{ + int err; + err = 0; + + DBG_DETAIL(); + + //DPRINTK("s pointer: %d, dmach: %d, id: %s, dma_dev: %d", (int)s, s->dmach, s->id, s->dma_dev); + err = wmt_request_dma(&s->dmach, s->id, s->dma_dev, callback, s); + if (err < 0) + printk(KERN_ERR "Unable to grab audio dma 0x%x\n", s->dmach); + + if (!strcmp(s->id, "WMT I2S out")) { + pcm_out_dmach = s->dmach; + } + return err; +} + +static void audio_setup_dma(struct audio_stream_a *s, int stream_id) +{ + struct snd_pcm_runtime *runtime = s->stream->runtime; + + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + /* From memory to device */ + switch (runtime->channels * runtime->format) { + #if 0 //comment 2014-1-3 tinyplay mono pattern abnormal + case 1: + s->dma_cfg.DefaultCCR = I2S_TX_DMA_8BITS_CFG; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = I2S_TX_DMA_16BITS_CFG; /* setup 2 bytes*/ + break ; + case 4: + #endif + default : + s->dma_cfg.DefaultCCR = I2S_TX_DMA_32BITS_CFG; /* setup 4 byte*/ + break ; + } + } + else { + /* From device to memory */ + switch (runtime->channels * runtime->format) { + case 1: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_8BITS_CFG ; /* setup 1 bytes*/ + break ; + case 2: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_16BITS_CFG ; /* setup 2 bytes*/ + break ; + case 4: + s->dma_cfg.DefaultCCR = I2S_RX_DMA_32BITS_CFG ; /* setup 4 byte*/ + break ; + } + } + + s->dma_cfg.ChunkSize = 1; + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + pcm_out_dma_cfg = &s->dma_cfg; + } + + //DPRINTK("s pointer: %d. audio dma %d cfg.DefaultCCR 0x%x ", (int)s, s->dmach, (unsigned int)s->dma_cfg.DefaultCCR); + //DPRINTK("cfg.ChunkSize 0x%x ", s->dma_cfg.ChunkSize); + wmt_setup_dma(s->dmach, s->dma_cfg) ; +} + +static int audio_dma_free(struct audio_stream_a *s) +{ + int err = 0; + DBG_DETAIL(); + wmt_free_dma(s->dmach); + s->dmach = NULL_DMA; + pcm_out_dma_cfg = NULL; + return err; +} + +/* + * this stops the dma and clears the dma ptrs + */ +static void audio_stop_dma(struct audio_stream_a *s) +{ + unsigned long flags; + DBG_DETAIL(); + local_irq_save(flags); + s->active = 0; + s->period = 0; + s->periods = 0; + s->offset = 0; + s->last_offset = 0; + wmt_stop_dma(s->dmach); + wmt_clear_dma(s->dmach); + local_irq_restore(flags); +} + +/* this may get called several times by oss emulation */ +static int wmt_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int err = 0; + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + runtime->dma_bytes = params_buffer_bytes(params); + return err; +} + +static int wmt_pcm_hw_free(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +static int wmt_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + + DBG_DETAIL(); + + s->period = 0; + s->periods = 0; + s->offset = 0; + s->last_offset = 0; + audio_setup_dma(s, stream_id); + + return 0; +} + +static int wmt_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int stream_id = substream->pstr->stream; + struct audio_stream_a *prtd = runtime->private_data; + struct audio_stream_a *s = &prtd[stream_id]; + int ret = 0; + + DPRINTK("wmt_pcm_trigger Enter, cmd=%d", cmd); + + spin_lock(&s->dma_lock); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + s->active = 1; + audio_process_dma(s); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s->active = 0; + audio_stop_dma(s); + break; + default: + ret = -EINVAL; + } + spin_unlock(&s->dma_lock); + + return ret; +} + +static snd_pcm_uframes_t wmt_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *prtd = runtime->private_data; + int stream_id = substream->pstr->stream; + struct audio_stream_a *s = &prtd[stream_id]; + dma_addr_t ptr; + snd_pcm_uframes_t offset = 0; + + ptr = wmt_get_dma_pos(s->dmach); + + if ((runtime->channels == 1 || runtime->channels == 2) && (runtime->format == SNDRV_PCM_FORMAT_S16_LE)) { + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } + + if ((offset < s->last_offset) && ((s->last_offset - offset) < runtime->period_size) && + (s->last_offset != runtime->buffer_size)) { + snd_pcm_uframes_t old_offset = offset; + if (s->last_offset < runtime->period_size) + offset = runtime->period_size; + else { + offset = runtime->period_size * + ((s->last_offset / runtime->period_size) + 1); + } + printk("last_offset %d, old offset %d, new offset %d\n", (int)s->last_offset, (int)old_offset, (int)offset); + } + s->last_offset = offset; + + if (offset >= runtime->buffer_size) + offset = 0; + + spin_lock(&s->dma_lock); + + if (s->periods > 0 && s->periods < 3) { + if (stream_id == SNDRV_PCM_STREAM_PLAYBACK) { + if (snd_pcm_playback_hw_avail(runtime) >= 3 * runtime->period_size) + audio_process_dma(s); + } + else { + if (snd_pcm_capture_hw_avail(runtime) >= 3* runtime->period_size) + audio_process_dma(s); + } + + } + spin_unlock(&s->dma_lock); + + //DPRINTK("offset = %x", (unsigned int)offset); + return offset; +} + +static int wmt_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + int ret; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_request(&s[0], audio_dma_callback); + audio_dma_request(&s[1], audio_dma_callback); + } + + snd_soc_set_runtime_hwparams(substream, &wmt_pcm_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + +out: + return ret; +} + +static int wmt_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_pcm_runtime *runtime = substream->runtime; + struct audio_stream_a *s = runtime->private_data; + + DBG_DETAIL(); + + if (!cpu_dai->active) { + audio_dma_free(&s[0]); + audio_dma_free(&s[1]); + } + + return 0; +} + +static int wmt_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + DBG_DETAIL(); + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops wmt_pcm_ops = { + .open = wmt_pcm_open, + .close = wmt_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = wmt_pcm_hw_params, + .hw_free = wmt_pcm_hw_free, + .prepare = wmt_pcm_prepare, + .trigger = wmt_pcm_trigger, + .pointer = wmt_pcm_pointer, + .mmap = wmt_pcm_mmap, +}; + +static u64 wmt_pcm_dmamask = DMA_BIT_MASK(32); + +static int wmt_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, + int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = wmt_pcm_hardware.buffer_bytes_max; + + DBG_DETAIL(); + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + info("stream = %d, buf_addr = %x", stream, buf->addr); + + wfd_audbuf.bytes = size; + wfd_audbuf.dev = pcm->card->dev; + wfd_audbuf.enable = 0; + } + + if (!buf->area) + return -ENOMEM; + + buf->bytes = size; + + return 0; +} + +static void wmt_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + DBG_DETAIL(); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + + dma_free_writecombine(pcm->card->dev, buf->bytes, buf->area, buf->addr); + + buf->area = NULL; + } +} + +int wmt_pcm_new(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_card *card = rtd->card->snd_card; + struct snd_pcm *pcm = rtd->pcm; + int ret = 0; + + DBG_DETAIL(); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &wmt_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { + ret = wmt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { + ret = wmt_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } + +out: + /* free preallocated buffers in case of error */ + if (ret) + wmt_pcm_free_dma_buffers(pcm); + + return ret; +} + +#ifdef CONFIG_PM +static int wmt_pcm_suspend(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) + return 0; + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + + if (s->active) { + udelay(5); + wmt_stop_dma(s->dmach); + /* + wmt_clear_dma(s->dmach); + audio_stop_dma(s); + */ + } + + return 0; +} + +static int wmt_pcm_resume(struct snd_soc_dai *dai) +{ + struct snd_pcm_runtime *runtime = dai->runtime; + struct audio_stream_a *prtd; + struct audio_stream_a *s; + + DBG_DETAIL(); + + if (!runtime) { + if ((pcm_out_dmach != 0xFF) && (pcm_out_dma_cfg != NULL)) { + wmt_setup_dma(pcm_out_dmach, *pcm_out_dma_cfg); + } + return 0; + } + + prtd = runtime->private_data; + s = &prtd[SNDRV_PCM_STREAM_PLAYBACK]; + audio_setup_dma(s, SNDRV_PCM_STREAM_PLAYBACK); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + s = &prtd[SNDRV_PCM_STREAM_CAPTURE]; + audio_setup_dma(s, SNDRV_PCM_STREAM_CAPTURE); + + if (s->active) { + wmt_resume_dma(s->dmach) ; + } + + return 0; +} +#else +#define wmt_pcm_suspend NULL +#define wmt_pcm_resume NULL +#endif + +static struct snd_soc_platform_driver wmt_soc_platform = { + .ops = &wmt_pcm_ops, + .pcm_new = wmt_pcm_new, + .pcm_free = wmt_pcm_free_dma_buffers, + .suspend = wmt_pcm_suspend, + .resume = wmt_pcm_resume, +}; + +static int __devinit wmt_pcm_platform_probe(struct platform_device *pdev) +{ + DBG_DETAIL(); + + return snd_soc_register_platform(&pdev->dev, &wmt_soc_platform); +} + +static int __devexit wmt_pcm_platform_remove(struct platform_device *pdev) +{ + DBG_DETAIL(); + + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver wmt_pcm_driver = { + .driver = { + .name = "wmt-audio-pcm", + .owner = THIS_MODULE, + }, + + .probe = wmt_pcm_platform_probe, + .remove = __devexit_p(wmt_pcm_platform_remove), +}; + +module_platform_driver(wmt_pcm_driver); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/pcm] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h new file mode 100755 index 00000000..90ad7747 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-pcm.h @@ -0,0 +1,46 @@ +/*++ + * linux/sound/soc/wmt/wmt-pcm.h + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_PCM_H__ +#define __WMT_PCM_H__ + +struct wmt_pcm_dma_data { + char *name; /* stream identifier */ + int dma_req; /* DMA request line */ + unsigned long port_addr; /* transmit/receive register */ + struct dma_device_cfg_s *dma_cfg; +}; + +typedef struct WFDStrmInfo { + unsigned int req_sz; + unsigned int avail_sz; + unsigned int buf_offset; +} WFDStrmInfo_t; + +extern void wmt_pcm_wfd_start(void); +extern unsigned int wmt_pcm_wfd_get_buf(void); +extern void wmt_pcm_wfd_stop(void); +extern int wmt_pcm_wfd_get_strm(WFDStrmInfo_t *info); + +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c new file mode 100755 index 00000000..f62e993d --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.c @@ -0,0 +1,321 @@ +/*++ + * linux/sound/soc/wmt/wmt-soc.c + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt-soc.h" +#include "wmt-pcm.h" +#include "wmt_hwdep.h" +#include "../codecs/wmt_vt1602.h" +#include "../codecs/vt1603.h" + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); +extern void wmt_set_i2s_share_pin(); +char wmt_codec_name[80]; +char wmt_dai_name[80]; +char wmt_rate[10]; + +#define AUDIO_NAME "WMT_SOC" +//#define WMT_SOC_DEBUG 1 +//#define WMT_SOC_DEBUG_DETAIL 1 + +#ifdef WMT_SOC_DEBUG +#define DPRINTK(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#else +#define DPRINTK(format, arg...) do {} while (0) +#endif + +#ifdef WMT_SOC_DEBUG_DETAIL +#define DBG_DETAIL(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": [%s]" format "\n" , __FUNCTION__, ## arg) +#else +#define DBG_DETAIL(format, arg...) do {} while (0) +#endif + +#define err(format, arg...) \ + printk(KERN_ERR AUDIO_NAME ": " format "\n" , ## arg) +#define info(format, arg...) \ + printk(KERN_INFO AUDIO_NAME ": " format "\n" , ## arg) +#define warn(format, arg...) \ + printk(KERN_WARNING AUDIO_NAME ": " format "\n" , ## arg) + +#define WMT_I2S_RATES SNDRV_PCM_RATE_8000_96000 + +static struct snd_soc_card snd_soc_machine_wmt; + +static int wmt_soc_primary_startup(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); + return 0; +} + +static void wmt_soc_primary_shutdown(struct snd_pcm_substream *substream) +{ + DBG_DETAIL(); +} + +static int wmt_soc_primary_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + + DBG_DETAIL(); + + if (strcmp(wmt_codec_name, "hwdac")) { + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | + SND_SOC_DAIFMT_I2S |SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + } + + + /* Set cpu DAI configuration for I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + |SND_SOC_DAIFMT_MASTER_MASK | SND_SOC_DAIFMT_NB_NF); + if (ret < 0) + return ret; + + if ((!strcmp(wmt_codec_name, "vt1602")) || (!strcmp(wmt_codec_name, "vt1603"))) { + /* Set the codec system clock for DAC and ADC */ + if (!(params_rate(params) % 11025)) { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 11289600, + SND_SOC_CLOCK_IN); + } + else { + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 12288000, + SND_SOC_CLOCK_IN); + } + } + + return ret; +} + +static int wmt_soc_second_startup(struct snd_pcm_substream *substream) +{ + //dump_stack(); + DBG_DETAIL(); + return 0; +} + +static void wmt_soc_second_shutdown(struct snd_pcm_substream *substream) +{ + //dump_stack(); + DBG_DETAIL(); +} + +static int wmt_soc_second_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + //dump_stack(); + DBG_DETAIL(); + return 0; +} + +static int wmt_soc_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + DBG_DETAIL(); + wmt_soc_hwdep_new(rtd->codec); + return 0; +} + +static int wmt_suspend_pre(struct snd_soc_card *card) +{ + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Left HP"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Right HP"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Left SPK"); + snd_soc_dapm_disable_pin(&card->rtd->codec->dapm, "Right SPK"); +} + +static int wmt_suspend_post(struct snd_soc_card *card) +{ + DBG_DETAIL(); + + /* Disable BIT15:I2S clock, BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL &= ~(BIT15 | BIT4 | BIT3); + + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Left HP"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Right HP"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Left SPK"); + snd_soc_dapm_enable_pin(&card->rtd->codec->dapm, "Right SPK"); + + return 0; +} + +static int wmt_resume_pre(struct snd_soc_card *card) +{ + /* Enable MCLK before VT1602 codec enable, otherwise the codec will be disabled. */ + + /* set to 24.576MHz */ + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); + /* Enable BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL |= (BIT4 | BIT3); + /* Enable BIT2:AUD clock */ + PMCE3_VAL |= BIT2; + + wmt_set_i2s_share_pin(); + return 0; +} + +static struct snd_soc_ops wmt_soc_primary_ops = { + .startup = wmt_soc_primary_startup, + .hw_params = wmt_soc_primary_hw_params, + .shutdown = wmt_soc_primary_shutdown, +}; + +static struct snd_soc_ops wmt_soc_second_ops = { + .startup = wmt_soc_second_startup, + .hw_params = wmt_soc_second_hw_params, + .shutdown = wmt_soc_second_shutdown, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link wmt_dai[] = { + { + .name = "HiFi", + .stream_name = "HiFi", + .platform_name = "wmt-audio-pcm.0", + .init = wmt_soc_dai_init, + .ops = &wmt_soc_primary_ops, + }, + { + .name = "Voice", + .stream_name = "Voice", + .platform_name = "wmt-pcm-dma.0", + .cpu_dai_name = "wmt-pcm-controller.0", + .codec_dai_name = "HWDAC", + .codec_name = "wmt-i2s-hwdac.0", + .ops = &wmt_soc_second_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_machine_wmt = { + .name = "WMT_VT1609", + .dai_link = wmt_dai, + .num_links = ARRAY_SIZE(wmt_dai), + .suspend_pre = wmt_suspend_pre, + .suspend_post = wmt_suspend_post, + .resume_pre = wmt_resume_pre, +}; + +static struct platform_device *wmt_snd_device; + +static int __init wmt_soc_init(void) +{ + int ret, i; + char buf[64]; + int len = ARRAY_SIZE(buf); + + if (wmt_getsyspara("wmt.audio.i2s", buf, &len) != 0) { + strcpy(wmt_dai_name, "null"); + strcpy(wmt_codec_name, "null"); + } + else { + strcpy(wmt_dai_name, "i2s"); + } + + if (strcmp(wmt_dai_name, "null")) { + for (i = 0; i < 80; ++i) { + if (buf[i] == ':') + break; + else + wmt_codec_name[i] = buf[i]; + } + } + else { + return -EINVAL; + } + + // is wm8994, return and load wmt_wm8994 module + if (strcmp(wmt_codec_name, "wm8994") == 0) + return -ENODEV; + + info("dai_name=%s, codec_name=%s", wmt_dai_name, wmt_codec_name); + + + wmt_i2s_dai.playback.rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000); + wmt_i2s_dai.capture.rates = (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000); + wmt_dai[0].cpu_dai_name = "wmt-i2s.0"; + + if (!strcmp(wmt_codec_name, "vt1602")) { + wmt_dai[0].codec_dai_name = "VT1602"; + wmt_dai[0].codec_name = "vt1602.1-001a"; + } + else if (!strcmp(wmt_codec_name, "hwdac")) { + wmt_dai[0].codec_dai_name = "HWDAC"; + wmt_dai[0].codec_name = "wmt-i2s-hwdac.0"; + } + else if (!strcmp(wmt_codec_name, "vt1603")) { + wmt_dai[0].codec_dai_name = "VT1603"; + wmt_dai[0].codec_name = "vt1603-codec"; + } + + /* Doing register process after plug-in */ + wmt_snd_device = platform_device_alloc("soc-audio", -1); + if (!wmt_snd_device) + return -ENOMEM; + + platform_set_drvdata(wmt_snd_device, &snd_soc_machine_wmt); + + ret = platform_device_add(wmt_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + platform_device_put(wmt_snd_device); + return ret; + +} + +static void __exit wmt_soc_exit(void) +{ + DBG_DETAIL(); + + platform_device_unregister(wmt_snd_device); +} + +module_init(wmt_soc_init); +module_exit(wmt_soc_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT [ALSA SoC/Machine] driver"); +MODULE_LICENSE("GPL"); diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h new file mode 100755 index 00000000..f49a2764 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt-soc.h @@ -0,0 +1,53 @@ +/*++ + * linux/sound/soc/wmt/wmt-soc.h + * WonderMedia audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#ifndef __WMT_ASOC_H__ +#define __WMT_ASOC_H__ +#include <mach/dma.h> + +struct audio_stream_a { + char *id; /* identification string */ + int stream_id; /* numeric identification */ + dmach_t dmach; /* DMA channel number */ + struct dma_device_cfg_s dma_cfg; /* DMA device config */ + int dma_dev; /* dma number of that device */ + int dma_q_head; /* DMA Channel Q Head */ + int dma_q_tail; /* DMA Channel Q Tail */ + char dma_q_count; /* DMA Channel Q Count */ + int active:1; /* we are using this stream for transfer now */ + int period; /* current transfer period */ + int periods; /* current count of periods registerd in the DMA engine */ + spinlock_t dma_lock; /* for locking in DMA operations */ + struct snd_pcm_substream *stream; /* the pcm stream */ + unsigned linked:1; /* dma channels linked */ + int offset; /* store start position of the last period in the alsa buffer */ + snd_pcm_uframes_t last_offset; +}; + +#define NUM_LINKS 1 + +extern struct snd_soc_dai_driver wmt_i2s_dai; + + +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c new file mode 100755 index 00000000..8a6d374e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.c @@ -0,0 +1,259 @@ +/*++ + * linux/sound/soc/wmt/wmt_hwdep.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +#include <sound/soc.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> +#include <mach/gpio.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt_hwdep.h" +#include "wmt-pcm.h" + +int WFD_flag = 0; +static int gmode = 2; +static char gstring[3] = "LR"; + +static char gSpdHdm[6] = "BOTH"; +static int gSpHd = 6; + +extern void wmt_i2s_dac0_ctrl(int HDMI_audio_enable); + +static int wmt_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + if ((file->f_flags & O_RDWR) && (WFD_flag)) { + return -EBUSY; + } + else if (file->f_flags & O_SYNC) { + WFD_flag = 1; + } + return 0; +} + +static int wmt_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + WFD_flag = 0; + return 0; +} + +static int wmt_hwdep_mmap(struct snd_hwdep *hw, struct file *file, struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + if (remap_pfn_range(vma, vma->vm_start, (vma->vm_pgoff), + vma->vm_end - vma->vm_start, vma->vm_page_prot)) { + printk("*E* remap page range failed: vm_pgoff=0x%x ", (unsigned int)vma->vm_pgoff); + return -EAGAIN; + } + return 0; +} + +static int wmt_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, unsigned int cmd, unsigned long arg) +{ + int *value; + WFDStrmInfo_t *info; + struct wmt_soc_vt1603_info vt1603_info; + int ret = 0; + + switch (cmd) { + case WMT_SOC_IOCTL_HDMI: + value = (int __user *)arg; + + if (*value > 1) { + printk("Not supported status for HDMI Audio %d", *value); + return 0; + } + wmt_i2s_dac0_ctrl(*value); + return 0; + + case WMT_SOC_IOCTL_WFD_START: + wmt_pcm_wfd_start(); + return copy_to_user( (void *)arg, (const void __user *) wmt_pcm_wfd_get_buf(), sizeof(unsigned int)); + + case WMT_SOC_IOCTL_GET_STRM: + info = (WFDStrmInfo_t *)wmt_pcm_wfd_get_strm((WFDStrmInfo_t *)arg); + return __put_user((int)info, (unsigned int __user *) arg); + + case WMT_SOC_IOCTL_WFD_STOP: + wmt_pcm_wfd_stop(); + return 0; + + case WMT_SOC_IOCTL_VT1603_RD: + ret = copy_from_user(&vt1603_info, (void __user *)arg, sizeof(vt1603_info)); + + if (ret == 0) { + vt1603_info.reg_value = vt1603_hwdep_ioctl(0, vt1603_info.reg_offset, vt1603_info.reg_value); + printk("<<<%s read reg 0x%x val 0x%x\n", __FUNCTION__, vt1603_info.reg_offset, vt1603_info.reg_value); + ret = copy_to_user((void __user *)arg, &vt1603_info, sizeof(vt1603_info)); + } + return ret; + case WMT_SOC_IOCTL_VT1603_WR: + ret = copy_from_user(&vt1603_info, (void __user *)arg, sizeof(vt1603_info)); + printk("<<<%s write reg 0x%x val 0x%x\n", __FUNCTION__, vt1603_info.reg_offset, vt1603_info.reg_value); + if (ret == 0) + vt1603_hwdep_ioctl(1, vt1603_info.reg_offset, vt1603_info.reg_value); + return ret; + + case WMT_SOC_IOCTL_CH_SEL: + value = (int __user *)arg; + + if (*value > 2) { + printk("Not supported for CH select %d", *value); + return 0; + } + wmt_i2s_ch_sel(*value); + return 0; + + default: + break; + } + + printk("Not supported ioctl for WMT-HWDEP"); + return -ENOIOCTLCMD; +} + +static long wmt_hwdep_write(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + char string[3]; + //int mode; + memset(string, 0, sizeof(string)); + copy_from_user(&string, buf, sizeof(string)); + printk("<<<%s %s\n", __FUNCTION__, string); + if (!memcmp(string, "LL", 2)) { + gmode = 0; + } + else if (!memcmp(string, "RR", 2)) { + gmode = 1; + } + else if (!memcmp(string, "LR", 2)) { + gmode = 2; + } + else { + printk("Not supported for CH select"); + return count; + } + + memset(gstring, 0, sizeof(gstring)); + strncpy(gstring, string, sizeof(string)); + wmt_i2s_ch_sel(gmode); + return count; +} + +static long wmt_hwdep_read(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + int len = 0; + printk("%s string %s --> mode %d\n", __FUNCTION__, gstring, gmode); + len = copy_to_user(buf, gstring, sizeof(gstring)); + + return sizeof(gstring); +} + +static long wmt_hwdep_write_1(struct snd_hwdep *hw, const char __user *buf, + long count, loff_t *offset) +{ + char string[5]; + //int mode; + + copy_from_user(&string, buf, sizeof(string)); + printk("<<<%s %s\n", __FUNCTION__, string); + if (!memcmp(string, "NONE", 4)) { + gSpHd = 3; + } + else if (!memcmp(string, "HDMI", 4)) { + gSpHd = 4; + } + else if (!memcmp(string, "SPDIF", 5)) { + gSpHd = 5; + } + else if (!memcmp(string, "BOTH", 4)) { + gSpHd = 6; + } + else { + printk("Not supported for SPDIF/HDMI switch"); + return count; + } + + memset(gSpdHdm, 0, sizeof(gSpdHdm)); + strncpy(gSpdHdm, string, sizeof(string)); + + wmt_i2s_ch_sel(gSpHd); + return count; +} + + +static long wmt_hwdep_read_1(struct snd_hwdep *hw, char __user *buf, + long count, loff_t *offset) +{ + int len = 0; + printk("%s string %s --> mode %d\n", __FUNCTION__, gSpdHdm, gSpHd); + len = copy_to_user(buf, gSpdHdm, sizeof(gSpdHdm)); + + return sizeof(gstring); +} + +void wmt_soc_hwdep_new(struct snd_soc_codec *codec) +{ + struct snd_hwdep *hwdep; + struct snd_hwdep *hwdep_1; + if (snd_hwdep_new(codec->card->snd_card, "WMT-HWDEP", 0, &hwdep) < 0) { + printk("create WMT-HWDEP_0 fail"); + return; + } + + sprintf(hwdep->name, "WMT-HWDEP %d", 0); + + hwdep->iface = SNDRV_HWDEP_IFACE_WMT; + hwdep->ops.open = wmt_hwdep_open; + hwdep->ops.ioctl = wmt_hwdep_ioctl; + hwdep->ops.release = wmt_hwdep_release; + hwdep->ops.mmap = wmt_hwdep_mmap; + hwdep->ops.write = wmt_hwdep_write; + hwdep->ops.read = wmt_hwdep_read; + + if (snd_hwdep_new(codec->card->snd_card, "WMT-HWDEP", 1, &hwdep_1) < 0) { + printk("create WMT-HWDEP_1 fail"); + return; + } + + sprintf(hwdep_1->name, "WMT-HWDEP %d", 1); + printk("create %s success", hwdep_1->name); + + hwdep_1->iface = SNDRV_HWDEP_IFACE_WMT; + hwdep_1->ops.write = wmt_hwdep_write_1; + hwdep_1->ops.read = wmt_hwdep_read_1; +} + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h new file mode 100755 index 00000000..c95d5e9e --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_hwdep.h @@ -0,0 +1,55 @@ +/*++ + * linux/sound/soc/wmt/wmt_hwdep.h + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + +/* + * ioctls for Hardware Dependant Interface + */ +#ifndef __WMT_HWDEP_H__ +#define __WMT_HWDEP_H__ + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +struct wmt_soc_vt1603_info { + u16 reg_offset; + u16 reg_value; +};//add 2013-9-2 for vt1603 eq apk + +#define WMT_SOC_IOCTL_HDMI _IOWR('H', 0x10, int) +#define WMT_SOC_IOCTL_WFD_START _IOWR('H', 0x20, int) +#define WMT_SOC_IOCTL_GET_STRM _IOWR('H', 0x30, int) +#define WMT_SOC_IOCTL_WFD_STOP _IOWR('H', 0x40, int) +#define WMT_SOC_IOCTL_VT1603_RD _IOWR('H', 0x50, int) +#define WMT_SOC_IOCTL_VT1603_WR _IOWR('H', 0x60, int) +#define WMT_SOC_IOCTL_CH_SEL _IOWR('H', 0x70, int) +void wmt_soc_hwdep_new(struct snd_soc_codec *codec); + +extern int vt1603_hwdep_ioctl(u8 rw_flag, u16 offset, u16 value); +extern void wmt_i2s_ch_sel(int ch_sel_num); +#endif diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c new file mode 100755 index 00000000..4d92a81f --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.c @@ -0,0 +1,101 @@ +/*++ + * linux/sound/soc/wmt/wmt_swmixer.c + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +#include "wmt_swmixer.h" +#include <sound/asound.h> + + + +void wmt_sw_u2s(int fmt, char *buffer, unsigned int chunksize) +{ + unsigned int index; + if (fmt == SNDRV_PCM_FORMAT_U8) { + for (index = 0; index < chunksize; ++index) + *(buffer + index) ^= 0x80; + } + +} + + +void wmt_pcm_fmt_trans(int fmt, int channel, char *src_buf, char *dst_buf, unsigned int chunksize) +{ + unsigned int index = 0; + float_data_t f_data; + unsigned short data; + + /* always convert to 2ch, s16le (4 bytes) */ + if ((fmt == SNDRV_PCM_FORMAT_S16_LE) && (channel == 1)) { + /* transfer from 1ch s16le(2 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 2); ++index) { + *((unsigned int *)dst_buf + index) = (*((unsigned short *)src_buf + index) << 16 | + *((unsigned short *)src_buf + index)); + } + } + else if ((fmt == SNDRV_PCM_FORMAT_U8) && (channel == 1)) { + /* transfer from 1ch U8(1 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < chunksize; ++index) { + /* padding zero to byte 0 & byte 2 */ + *((unsigned int *)dst_buf + index) = (*((unsigned char *)src_buf + index) << 24 | + *((unsigned char *)src_buf + index) << 8); + } + } + else if ((fmt == SNDRV_PCM_FORMAT_U8) && (channel == 2)) { + /* transfer from 2ch U8(2 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < chunksize; ++index) { + /* padding zero to byte 0 */ + *((unsigned short *)dst_buf + index) = *((unsigned char *)src_buf + index) << 8; + } + } + else if ((fmt == SNDRV_PCM_FORMAT_FLOAT) && (channel == 2)) { + /* transfer from 2ch float(8 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 4); ++index) { + f_data = *((float_data_t *)src_buf + index); + + if (!f_data.sign) { + data = (f_data.frac + 0x800000) >> (8 - (f_data.exp - 127)); + } + else { + data = ~((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))) + 1; + } + + *((unsigned short *)dst_buf + index) = data; + } + } + else if ((fmt == SNDRV_PCM_FORMAT_FLOAT) && (channel == 1)) { + /* transfer from 1ch float(4 bytes) to 2ch s16le(4 bytes) */ + for (index = 0; index < (chunksize / 4); ++index) { + f_data = *((float_data_t *)src_buf + index); + + if (!f_data.sign) { + data = (unsigned short)((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))); + } + else { + data = (unsigned short)(~((f_data.frac + 0x800000) >> (8 - (f_data.exp - 127))) + 1); + } + + *((unsigned int *)dst_buf + index) = (data << 16) | data; + } + } +} + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h new file mode 100755 index 00000000..3163e5af --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_swmixer.h @@ -0,0 +1,33 @@ +/*++ + * linux/sound/soc/wmt/wmt_swmixer.h + * WonderMedia I2S audio driver for ALSA + * + * Copyright c 2010 WonderMedia Technologies, Inc. + * + * 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, see <http://www.gnu.org/licenses/>. + * + * WonderMedia Technologies, Inc. + * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C +--*/ + + +typedef struct float_data { + unsigned long frac : 23; + unsigned long exp : 8; + unsigned long sign : 1; +} float_data_t; + +void wmt_sw_u2s(int fmt, char *buffer, unsigned int chunksize); +void wmt_pcm_fmt_trans(int fmt, int channel, char *src_buf, char *dst_buf, unsigned int chunksize); + diff --git a/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c b/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c new file mode 100755 index 00000000..8d4cebf9 --- /dev/null +++ b/ANDROID_3.4.5/sound/soc/wmt/wmt_wm8994.c @@ -0,0 +1,304 @@ +/* + * wmt_wm8994.c + * + * Copyright (C) 2010 Samsung Electronics Co.Ltd + * Author: Chanwoo Choi <cw00.choi@samsung.com> + * + * 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. + * + */ + +#include <sound/soc.h> +#include <sound/jack.h> + +#include <asm/mach-types.h> +#include <mach/gpio.h> + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/device.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc-dapm.h> +#include <sound/hwdep.h> + +#include <asm/mach-types.h> +#include <mach/hardware.h> + +#include "wmt-soc.h" +#include "wmt-pcm.h" +#include "wmt_hwdep.h" +#include "../codecs/wm8994.h" +#include <linux/mfd/wm8994/registers.h> +#include <linux/mfd/wm8994/core.h> + +#include <linux/i2c.h> + +static struct snd_soc_card wmt; +static struct platform_device *wmt_snd_device; +static int wmt_incall = 0; + +extern int wmt_getsyspara(char *varname, unsigned char *varval, int *varlen); +extern void wmt_set_i2s_share_pin(void); + +static const struct snd_pcm_hardware wmt_voice_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rate_min = 8000, + .rate_max = 8000, + .period_bytes_min = 16, + .period_bytes_max = 4 * 1024, + .periods_min = 2, + .periods_max = 16, + .buffer_bytes_max = 16 * 1024, +}; + +static int wmt_snd_suspend_post(struct snd_soc_card *card) +{ + /* Disable BIT15:I2S clock, BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL &= ~(BIT15 | BIT4 | BIT3); + return 0; +} + +static int wmt_snd_resume_pre(struct snd_soc_card *card) +{ + /* Enable MCLK before VT1602 codec enable, otherwise the codec will be disabled. */ + + /* set to 24.576MHz */ + auto_pll_divisor(DEV_I2S, CLK_ENABLE , 0, 0); + auto_pll_divisor(DEV_I2S, SET_PLLDIV, 1, 24576); + /* Enable BIT4:ARFP clock, BIT3:ARF clock */ + PMCEU_VAL |= (BIT4 | BIT3); + /* Enable BIT2:AUD clock */ + PMCE3_VAL |= BIT2; + + wmt_set_i2s_share_pin(); + + return 0; +} + +static int wmt_wm8994_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret = 0; + unsigned int pll_in = 48000; + unsigned int pll_out = 12000000; + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_LRCLK, + pll_in, pll_out); + if (ret < 0) + return ret; + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, pll_out, + SND_SOC_CLOCK_IN); + + wmt_soc_hwdep_new(rtd->codec); + + return 0; +} + +static int wmt_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int ret = 0; + unsigned int pll_in = 48000; + unsigned int pll_out = 12000000; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF); + if (ret< 0) { + printk("<<ret:%d snd_soc_dai_set_fmt(codec) hifi\n", ret); + return ret; + } + + /* Set cpu DAI configuration for I2S */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_fmt(cpu) hifi\n", ret); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL1, WM8994_FLL_SRC_LRCLK, + pll_in, pll_out); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_pll hifi\n", ret); + return ret; + } + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL1, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + printk("<<ret:%d snd_soc_dai_set_sysclk hifi\n", ret); + + if (!wmt_incall) + snd_soc_update_bits(codec_dai->codec, WM8994_CLOCKING_1, WM8994_SYSCLK_SRC, 0); + + return 0; +} + +static int wmt_voice_startup(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &wmt_voice_hardware); + + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime,SNDRV_PCM_HW_PARAM_PERIODS); + return ret; +} + +static int wmt_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret = 0; + unsigned int pll_in = 2048000; + unsigned int pll_out = 12288000; + + if (params_rate(params) != 8000) + return -EINVAL; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_fmt voice\n", ret); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_pll(codec_dai, WM8994_FLL2, WM8994_FLL_SRC_BCLK, + pll_in, pll_out); + if (ret < 0) { + printk("<<ret:%d snd_soc_dai_set_pll voice\n", ret); + return ret; + } + ret = snd_soc_dai_set_sysclk(codec_dai, WM8994_SYSCLK_FLL2, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + printk("<<ret:%d snd_soc_dai_set_sysclk voice\n", ret); + + wmt_incall = 1; + return ret; +} + +static int wmt_voice_hw_free(struct snd_pcm_substream *substream) +{ + wmt_incall = 0; + return 0; +} + +static struct snd_soc_ops wmt_hifi_ops = { + .hw_params = wmt_hifi_hw_params, +}; + +static struct snd_soc_ops wmt_voice_ops = { + .startup = wmt_voice_startup, + .hw_params = wmt_voice_hw_params, + .hw_free = wmt_voice_hw_free, +}; + +static struct snd_soc_dai_driver voice_dai[] = { + { + .name = "wmt-voice-dai", + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + }, +}; + +static struct snd_soc_dai_link wmt_dai[] = { + { + .name = "WM8994", + .stream_name = "WM8994 HiFi", + .cpu_dai_name = "wmt-i2s.0", + .codec_dai_name = "wm8994-aif1", + .platform_name = "wmt-audio-pcm.0", + .codec_name = "wm8994-codec", + .init = wmt_wm8994_init, + .ops = &wmt_hifi_ops, + }, + { + .name = "WM8994 Voice", + .stream_name = "Voice", + .cpu_dai_name = "wmt-voice-dai", + .codec_dai_name = "wm8994-aif2", + .codec_name = "wm8994-codec", + .ops = &wmt_voice_ops, + }, +}; + +static struct snd_soc_card wmt = { + .name = "WMT_WM8994", + .dai_link = wmt_dai, + .num_links = ARRAY_SIZE(wmt_dai), + .suspend_post = wmt_snd_suspend_post, + .resume_pre = wmt_snd_resume_pre, +}; + +static int __init wmt_init(void) +{ + int ret; + char buf[128]; + int len = ARRAY_SIZE(buf); + + if (wmt_getsyspara("wmt.audio.i2s", buf, &len) != 0) + return -EINVAL; + + if (strncmp(buf, "wm8994", strlen("wm8994"))) + return -ENODEV; + + wmt_snd_device = platform_device_alloc("soc-audio", -1); + if (!wmt_snd_device) + return -ENOMEM; + + /* register voice DAI here */ + ret = snd_soc_register_dais(&wmt_snd_device->dev, voice_dai, ARRAY_SIZE(voice_dai)); + if (ret) { + platform_device_put(wmt_snd_device); + return ret; + } + + platform_set_drvdata(wmt_snd_device, &wmt); + ret = platform_device_add(wmt_snd_device); + + if (ret) { + snd_soc_unregister_dai(&wmt_snd_device->dev); + platform_device_put(wmt_snd_device); + } + + return ret; +} + +static void __exit wmt_exit(void) +{ + snd_soc_unregister_dai(&wmt_snd_device->dev); + platform_device_unregister(wmt_snd_device); +} + +module_init(wmt_init); +module_exit(wmt_exit); + +MODULE_DESCRIPTION("ALSA SoC WM8994 WMT(wm8950)"); +MODULE_AUTHOR("Loonzhong <Loonzhong@wondermedia.com.cn>"); +MODULE_LICENSE("GPL"); |