📄 wm8753.c
字号:
/* * wm8753.c -- WM8753 ALSA Soc Audio driver * * Copyright 2003 Wolfson Microelectronics PLC. * Author: Liam Girdwood * liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.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. * * Notes: * The WM8753 is a low power, high quality stereo codec with integrated PCM * codec designed for portable digital telephony applications. * * Dual DAI:- * * This driver support 2 DAI PCM's. This makes the default PCM available for * HiFi audio (e.g. MP3, ogg) playback/capture and the other PCM available for * voice. * * Please note that the voice PCM can be connected directly to a Bluetooth * codec or GSM modem and thus cannot be read or written to, although it is * available to be configured with snd_hw_params(), etc and kcontrols in the * normal alsa manner. * * Fast DAI switching:- * * The driver can now fast switch between the DAI configurations via a * an alsa kcontrol. This allows the PCM to remain open. * */#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/version.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/delay.h>#include <linux/pm.h>#include <linux/i2c.h>#include <linux/platform_device.h>#include <sound/driver.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 <asm/div64.h>#include "wm8753.h"#define AUDIO_NAME "wm8753"#define WM8753_VERSION "0.16"/* * Debug */#define WM8753_DEBUG 0#ifdef WM8753_DEBUG#define dbg(format, arg...) \ printk(KERN_DEBUG AUDIO_NAME ": " format "\n" , ## arg)#else#define dbg(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 int caps_charge = 2000;module_param(caps_charge, int, 0);MODULE_PARM_DESC(caps_charge, "WM8753 cap charge time (msecs)");static void wm8753_set_dai_mode(struct snd_soc_codec *codec, unsigned int mode);/* codec private data */struct wm8753_priv { unsigned int sysclk; unsigned int pcmclk;};/* * wm8753 register cache * We can't read the WM8753 register space when we * are using 2 wire for device control, so we cache them instead. */static const u16 wm8753_reg[] = { 0x0008, 0x0000, 0x000a, 0x000a, 0x0033, 0x0000, 0x0007, 0x00ff, 0x00ff, 0x000f, 0x000f, 0x007b, 0x0000, 0x0032, 0x0000, 0x00c3, 0x00c3, 0x00c0, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0055, 0x0005, 0x0050, 0x0055, 0x0050, 0x0055, 0x0050, 0x0055, 0x0079, 0x0079, 0x0079, 0x0079, 0x0079, 0x0000, 0x0000, 0x0000, 0x0000, 0x0097, 0x0097, 0x0000, 0x0004, 0x0000, 0x0083, 0x0024, 0x01ba, 0x0000, 0x0083, 0x0024, 0x01ba, 0x0000, 0x0000};/* * read wm8753 register cache */static inline unsigned int wm8753_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg){ u16 *cache = codec->reg_cache; if (reg < 1 || reg > (ARRAY_SIZE(wm8753_reg) + 1)) return -1; return cache[reg - 1];}/* * write wm8753 register cache */static inline void wm8753_write_reg_cache(struct snd_soc_codec *codec, unsigned int reg, unsigned int value){ u16 *cache = codec->reg_cache; if (reg < 1 || reg > 0x3f) return; cache[reg - 1] = value;}/* * write to the WM8753 register space */static int wm8753_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value){ u8 data[2]; /* data is * D15..D9 WM8753 register offset * D8...D0 register data */ data[0] = (reg << 1) | ((value >> 8) & 0x0001); data[1] = value & 0x00ff; wm8753_write_reg_cache (codec, reg, value); if (codec->hw_write(codec->control_data, data, 2) == 2) return 0; else return -EIO;}#define wm8753_reset(c) wm8753_write(c, WM8753_RESET, 0)/* * WM8753 Controls */static const char *wm8753_base[] = {"Linear Control", "Adaptive Boost"};static const char *wm8753_base_filter[] = {"130Hz @ 48kHz", "200Hz @ 48kHz", "100Hz @ 16kHz", "400Hz @ 48kHz", "100Hz @ 8kHz", "200Hz @ 8kHz"};static const char *wm8753_treble[] = {"8kHz", "4kHz"};static const char *wm8753_alc_func[] = {"Off", "Right", "Left", "Stereo"};static const char *wm8753_ng_type[] = {"Constant PGA Gain", "Mute ADC Output"};static const char *wm8753_3d_func[] = {"Capture", "Playback"};static const char *wm8753_3d_uc[] = {"2.2kHz", "1.5kHz"};static const char *wm8753_3d_lc[] = {"200Hz", "500Hz"};static const char *wm8753_deemp[] = {"None", "32kHz", "44.1kHz", "48kHz"};static const char *wm8753_mono_mix[] = {"Stereo", "Left", "Right", "Mono"};static const char *wm8753_dac_phase[] = {"Non Inverted", "Inverted"};static const char *wm8753_line_mix[] = {"Line 1 + 2", "Line 1 - 2", "Line 1", "Line 2"};static const char *wm8753_mono_mux[] = {"Line Mix", "Rx Mix"};static const char *wm8753_right_mux[] = {"Line 2", "Rx Mix"};static const char *wm8753_left_mux[] = {"Line 1", "Rx Mix"};static const char *wm8753_rxmsel[] = {"RXP - RXN", "RXP + RXN", "RXP", "RXN"};static const char *wm8753_sidetone_mux[] = {"Left PGA", "Mic 1", "Mic 2", "Right PGA"};static const char *wm8753_mono2_src[] = {"Inverted Mono 1", "Left", "Right", "Left + Right"};static const char *wm8753_out3[] = {"VREF", "ROUT2", "Left + Right"};static const char *wm8753_out4[] = {"VREF", "Capture ST", "LOUT2"};static const char *wm8753_radcsel[] = {"PGA", "Line or RXP-RXN", "Sidetone"};static const char *wm8753_ladcsel[] = {"PGA", "Line or RXP-RXN", "Line"};static const char *wm8753_mono_adc[] = {"Stereo", "Analogue Mix Left", "Analogue Mix Right", "Digital Mono Mix"};static const char *wm8753_adc_hp[] = {"3.4Hz @ 48kHz", "82Hz @ 16k", "82Hz @ 8kHz", "170Hz @ 8kHz"};static const char *wm8753_adc_filter[] = {"HiFi", "Voice"};static const char *wm8753_mic_sel[] = {"Mic 1", "Mic 2", "Mic 3"};static const char *wm8753_dai_mode[] = {"DAI 0", "DAI 1", "DAI 2", "DAI 3"};static const char *wm8753_dat_sel[] = {"Stereo", "Left ADC", "Right ADC", "Channel Swap"};static const struct soc_enum wm8753_enum[] = {SOC_ENUM_SINGLE(WM8753_BASS, 7, 2, wm8753_base),SOC_ENUM_SINGLE(WM8753_BASS, 4, 6, wm8753_base_filter),SOC_ENUM_SINGLE(WM8753_TREBLE, 6, 2, wm8753_treble),SOC_ENUM_SINGLE(WM8753_ALC1, 7, 4, wm8753_alc_func),SOC_ENUM_SINGLE(WM8753_NGATE, 1, 2, wm8753_ng_type),SOC_ENUM_SINGLE(WM8753_3D, 7, 2, wm8753_3d_func),SOC_ENUM_SINGLE(WM8753_3D, 6, 2, wm8753_3d_uc),SOC_ENUM_SINGLE(WM8753_3D, 5, 2, wm8753_3d_lc),SOC_ENUM_SINGLE(WM8753_DAC, 1, 4, wm8753_deemp),SOC_ENUM_SINGLE(WM8753_DAC, 4, 4, wm8753_mono_mix),SOC_ENUM_SINGLE(WM8753_DAC, 6, 2, wm8753_dac_phase),SOC_ENUM_SINGLE(WM8753_INCTL1, 3, 4, wm8753_line_mix),SOC_ENUM_SINGLE(WM8753_INCTL1, 2, 2, wm8753_mono_mux),SOC_ENUM_SINGLE(WM8753_INCTL1, 1, 2, wm8753_right_mux),SOC_ENUM_SINGLE(WM8753_INCTL1, 0, 2, wm8753_left_mux),SOC_ENUM_SINGLE(WM8753_INCTL2, 6, 4, wm8753_rxmsel),SOC_ENUM_SINGLE(WM8753_INCTL2, 4, 4, wm8753_sidetone_mux),SOC_ENUM_SINGLE(WM8753_OUTCTL, 7, 4, wm8753_mono2_src),SOC_ENUM_SINGLE(WM8753_OUTCTL, 0, 3, wm8753_out3),SOC_ENUM_SINGLE(WM8753_ADCTL2, 7, 3, wm8753_out4),SOC_ENUM_SINGLE(WM8753_ADCIN, 2, 3, wm8753_radcsel),SOC_ENUM_SINGLE(WM8753_ADCIN, 0, 3, wm8753_ladcsel),SOC_ENUM_SINGLE(WM8753_ADCIN, 4, 4, wm8753_mono_adc),SOC_ENUM_SINGLE(WM8753_ADC, 2, 4, wm8753_adc_hp),SOC_ENUM_SINGLE(WM8753_ADC, 4, 2, wm8753_adc_filter),SOC_ENUM_SINGLE(WM8753_MICBIAS, 6, 3, wm8753_mic_sel),SOC_ENUM_SINGLE(WM8753_IOCTL, 2, 4, wm8753_dai_mode),SOC_ENUM_SINGLE(WM8753_ADC, 7, 4, wm8753_dat_sel),};static int wm8753_get_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); ucontrol->value.integer.value[0] = (mode & 0xc) >> 2; return 0;}static int wm8753_set_dai(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol){ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); int mode = wm8753_read_reg_cache(codec, WM8753_IOCTL); if (((mode &0xc) >> 2) == ucontrol->value.integer.value[0]) return 0; mode &= 0xfff3; mode |= (ucontrol->value.integer.value[0] << 2); wm8753_write(codec, WM8753_IOCTL, mode); wm8753_set_dai_mode(codec, ucontrol->value.integer.value[0]); return 1;}static const struct snd_kcontrol_new wm8753_snd_controls[] = {SOC_DOUBLE_R("PCM Volume", WM8753_LDAC, WM8753_RDAC, 0, 255, 0),SOC_DOUBLE_R("ADC Capture Volume", WM8753_LADC, WM8753_RADC, 0, 255, 0),SOC_DOUBLE_R("Headphone Playback Volume", WM8753_LOUT1V, WM8753_ROUT1V, 0, 127, 0),SOC_DOUBLE_R("Speaker Playback Volume", WM8753_LOUT2V, WM8753_ROUT2V, 0, 127, 0),SOC_SINGLE("Mono Playback Volume", WM8753_MOUTV, 0, 127, 0),SOC_DOUBLE_R("Bypass Playback Volume", WM8753_LOUTM1, WM8753_ROUTM1, 4, 7, 1),SOC_DOUBLE_R("Sidetone Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 4, 7, 1),SOC_DOUBLE_R("Voice Playback Volume", WM8753_LOUTM2, WM8753_ROUTM2, 0, 7, 1),SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8753_LOUT1V, WM8753_ROUT1V, 7, 1, 0),SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8753_LOUT2V, WM8753_ROUT2V, 7, 1, 0),SOC_SINGLE("Mono Bypass Playback Volume", WM8753_MOUTM1, 4, 7, 1),SOC_SINGLE("Mono Sidetone Playback Volume", WM8753_MOUTM2, 4, 7, 1),SOC_SINGLE("Mono Voice Playback Volume", WM8753_MOUTM2, 4, 7, 1),SOC_SINGLE("Mono Playback ZC Switch", WM8753_MOUTV, 7, 1, 0),SOC_ENUM("Bass Boost", wm8753_enum[0]),SOC_ENUM("Bass Filter", wm8753_enum[1]),SOC_SINGLE("Bass Volume", WM8753_BASS, 0, 15, 1),SOC_SINGLE("Treble Volume", WM8753_TREBLE, 0, 15, 1),SOC_ENUM("Treble Cut-off", wm8753_enum[2]),SOC_DOUBLE("Sidetone Capture Volume", WM8753_RECMIX1, 0, 4, 7, 1),SOC_SINGLE("Voice Sidetone Capture Volume", WM8753_RECMIX2, 0, 7, 1),SOC_DOUBLE_R("Capture Volume", WM8753_LINVOL, WM8753_RINVOL, 0, 63, 0),SOC_DOUBLE_R("Capture ZC Switch", WM8753_LINVOL, WM8753_RINVOL, 6, 1, 0),SOC_DOUBLE_R("Capture Switch", WM8753_LINVOL, WM8753_RINVOL, 7, 1, 1),SOC_ENUM("Capture Filter Select", wm8753_enum[23]),SOC_ENUM("Capture Filter Cut-off", wm8753_enum[24]),SOC_SINGLE("Capture Filter Switch", WM8753_ADC, 0, 1, 1),SOC_SINGLE("ALC Capture Target Volume", WM8753_ALC1, 0, 7, 0),SOC_SINGLE("ALC Capture Max Volume", WM8753_ALC1, 4, 7, 0),SOC_ENUM("ALC Capture Function", wm8753_enum[3]),SOC_SINGLE("ALC Capture ZC Switch", WM8753_ALC2, 8, 1, 0),SOC_SINGLE("ALC Capture Hold Time", WM8753_ALC2, 0, 15, 1),SOC_SINGLE("ALC Capture Decay Time", WM8753_ALC3, 4, 15, 1),SOC_SINGLE("ALC Capture Attack Time", WM8753_ALC3, 0, 15, 0),SOC_SINGLE("ALC Capture NG Threshold", WM8753_NGATE, 3, 31, 0),SOC_ENUM("ALC Capture NG Type", wm8753_enum[4]),SOC_SINGLE("ALC Capture NG Switch", WM8753_NGATE, 0, 1, 0),SOC_ENUM("3D Function", wm8753_enum[5]),SOC_ENUM("3D Upper Cut-off", wm8753_enum[6]),SOC_ENUM("3D Lower Cut-off", wm8753_enum[7]),SOC_SINGLE("3D Volume", WM8753_3D, 1, 15, 0),SOC_SINGLE("3D Switch", WM8753_3D, 0, 1, 0),SOC_SINGLE("Capture 6dB Attenuate", WM8753_ADCTL1, 2, 1, 0),SOC_SINGLE("Playback 6dB Attenuate", WM8753_ADCTL1, 1, 1, 0),SOC_ENUM("De-emphasis", wm8753_enum[8]),SOC_ENUM("Playback Mono Mix", wm8753_enum[9]),SOC_ENUM("Playback Phase", wm8753_enum[10]),SOC_SINGLE("Mic2 Capture Volume", WM8753_INCTL1, 7, 3, 0),SOC_SINGLE("Mic1 Capture Volume", WM8753_INCTL1, 5, 3, 0),SOC_ENUM_EXT("DAI Mode", wm8753_enum[26], wm8753_get_dai, wm8753_set_dai),SOC_ENUM("ADC Data Select", wm8753_enum[27]),};/* add non dapm controls */static int wm8753_add_controls(struct snd_soc_codec *codec){ int err, i; for (i = 0; i < ARRAY_SIZE(wm8753_snd_controls); i++) { err = snd_ctl_add(codec->card, snd_soc_cnew(&wm8753_snd_controls[i],codec, NULL)); if (err < 0) return err; } return 0;}/* * _DAPM_ Controls *//* Left Mixer */static const struct snd_kcontrol_new wm8753_left_mixer_controls[] = {SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_LOUTM2, 8, 1, 0),SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_LOUTM2, 7, 1, 0),SOC_DAPM_SINGLE("Left Playback Switch", WM8753_LOUTM1, 8, 1, 0),SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_LOUTM1, 7, 1, 0),};/* Right mixer */static const struct snd_kcontrol_new wm8753_right_mixer_controls[] = {SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_ROUTM2, 8, 1, 0),SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_ROUTM2, 7, 1, 0),SOC_DAPM_SINGLE("Right Playback Switch", WM8753_ROUTM1, 8, 1, 0),SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_ROUTM1, 7, 1, 0),};/* Mono mixer */static const struct snd_kcontrol_new wm8753_mono_mixer_controls[] = {SOC_DAPM_SINGLE("Left Playback Switch", WM8753_MOUTM1, 8, 1, 0),SOC_DAPM_SINGLE("Right Playback Switch", WM8753_MOUTM2, 8, 1, 0),SOC_DAPM_SINGLE("Voice Playback Switch", WM8753_MOUTM2, 3, 1, 0),SOC_DAPM_SINGLE("Sidetone Playback Switch", WM8753_MOUTM2, 7, 1, 0),SOC_DAPM_SINGLE("Bypass Playback Switch", WM8753_MOUTM1, 7, 1, 0),};/* Mono 2 Mux */static const struct snd_kcontrol_new wm8753_mono2_controls =SOC_DAPM_ENUM("Route", wm8753_enum[17]);/* Out 3 Mux */static const struct snd_kcontrol_new wm8753_out3_controls =SOC_DAPM_ENUM("Route", wm8753_enum[18]);/* Out 4 Mux */static const struct snd_kcontrol_new wm8753_out4_controls =SOC_DAPM_ENUM("Route", wm8753_enum[19]);/* ADC Mono Mix */static const struct snd_kcontrol_new wm8753_adc_mono_controls =SOC_DAPM_ENUM("Route", wm8753_enum[22]);/* Record mixer */static const struct snd_kcontrol_new wm8753_record_mixer_controls[] = {SOC_DAPM_SINGLE("Voice Capture Switch", WM8753_RECMIX2, 3, 1, 0),SOC_DAPM_SINGLE("Left Capture Switch", WM8753_RECMIX1, 3, 1, 0),SOC_DAPM_SINGLE("Right Capture Switch", WM8753_RECMIX1, 7, 1, 0),};/* Left ADC mux */static const struct snd_kcontrol_new wm8753_adc_left_controls =SOC_DAPM_ENUM("Route", wm8753_enum[21]);/* Right ADC mux */static const struct snd_kcontrol_new wm8753_adc_right_controls =SOC_DAPM_ENUM("Route", wm8753_enum[20]);/* MIC mux */static const struct snd_kcontrol_new wm8753_mic_mux_controls =SOC_DAPM_ENUM("Route", wm8753_enum[16]);/* ALC mixer */static const struct snd_kcontrol_new wm8753_alc_mixer_controls[] = {SOC_DAPM_SINGLE("Line Capture Switch", WM8753_INCTL2, 3, 1, 0),SOC_DAPM_SINGLE("Mic2 Capture Switch", WM8753_INCTL2, 2, 1, 0),SOC_DAPM_SINGLE("Mic1 Capture Switch", WM8753_INCTL2, 1, 1, 0),SOC_DAPM_SINGLE("Rx Capture Switch", WM8753_INCTL2, 0, 1, 0),};/* Left Line mux */static const struct snd_kcontrol_new wm8753_line_left_controls =SOC_DAPM_ENUM("Route", wm8753_enum[14]);/* Right Line mux */static const struct snd_kcontrol_new wm8753_line_right_controls =SOC_DAPM_ENUM("Route", wm8753_enum[13]);/* Mono Line mux */static const struct snd_kcontrol_new wm8753_line_mono_controls =SOC_DAPM_ENUM("Route", wm8753_enum[12]);/* Line mux and mixer */static const struct snd_kcontrol_new wm8753_line_mux_mix_controls =SOC_DAPM_ENUM("Route", wm8753_enum[11]);/* Rx mux and mixer */static const struct snd_kcontrol_new wm8753_rx_mux_mix_controls =SOC_DAPM_ENUM("Route", wm8753_enum[15]);/* Mic Selector Mux */static const struct snd_kcontrol_new wm8753_mic_sel_mux_controls =SOC_DAPM_ENUM("Route", wm8753_enum[25]);static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = {SND_SOC_DAPM_MICBIAS("Mic Bias", WM8753_PWR1, 5, 0),SND_SOC_DAPM_MIXER("Left Mixer", WM8753_PWR4, 0, 0, &wm8753_left_mixer_controls[0], ARRAY_SIZE(wm8753_left_mixer_controls)),SND_SOC_DAPM_PGA("Left Out 1", WM8753_PWR3, 8, 0, NULL, 0),SND_SOC_DAPM_PGA("Left Out 2", WM8753_PWR3, 6, 0, NULL, 0),SND_SOC_DAPM_DAC("Left DAC", "Left HiFi Playback", WM8753_PWR1, 3, 0),SND_SOC_DAPM_OUTPUT("LOUT1"),SND_SOC_DAPM_OUTPUT("LOUT2"),SND_SOC_DAPM_MIXER("Right Mixer", WM8753_PWR4, 1, 0, &wm8753_right_mixer_controls[0], ARRAY_SIZE(wm8753_right_mixer_controls)),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -