SND_SOC_DAPM_MIXER("Output Mixer", WM8731_PWR, 4, 1, wm8731_output_mixer_controls,
ARRAY_SIZE(wm8731_output_mixer_controls)),
+If you dont want the mixer elements prefixed with the name of the mixer widget,
+you can use SND_SOC_DAPM_MIXER_NAMED_CTL instead. the parameters are the same
+as for SND_SOC_DAPM_MIXER.
2.3 Platform/Machine domain Widgets
-----------------------------------
/* IrDA */
GPIO38_GPIO | MFP_LPM_DRIVE_HIGH,
+ /* Audio power control */
+ GPIO16_GPIO, /* AC97 codec AVDD2 supply (analogue power) */
+ GPIO40_GPIO, /* Mic amp power */
+ GPIO41_GPIO, /* Headphone amp power */
+
/* PC Card */
GPIO8_GPIO, /* CD0 */
GPIO44_GPIO, /* CD1 */
/* IrDA */
GPIO38_GPIO | MFP_LPM_DRIVE_HIGH,
+ /* Audio power control */
+ GPIO4_GPIO, /* Headphone amp power */
+ GPIO7_GPIO, /* Speaker amp power */
+ GPIO37_GPIO, /* Headphone detect */
+
/* PC Card */
GPIO8_GPIO, /* CD0 */
GPIO44_GPIO, /* CD1 */
GPIO23_SSP1_SCLK,
GPIO25_SSP1_TXD,
GPIO26_SSP1_RXD,
+
+ /* I2S */
+ GPIO28_I2S_BITCLK_OUT,
+ GPIO29_I2S_SDATA_IN,
+ GPIO30_I2S_SDATA_OUT,
+ GPIO31_I2S_SYNC,
+ GPIO32_I2S_SYSCLK,
};
/*
/* e7xx IrDA power control */
#define GPIO_E7XX_IR_OFF 38
+/* e740 audio control GPIOs */
+#define GPIO_E740_WM9705_nAVDD2 16
+#define GPIO_E740_MIC_ON 40
+#define GPIO_E740_AMP_ON 41
+
+/* e750 audio control GPIOs */
+#define GPIO_E750_HP_AMP_OFF 4
+#define GPIO_E750_SPK_AMP_OFF 7
+#define GPIO_E750_HP_DETECT 37
+
+/* e800 audio control GPIOs */
+#define GPIO_E800_HP_DETECT 81
+#define GPIO_E800_HP_AMP_OFF 82
+#define GPIO_E800_SPK_AMP_ON 83
+
/* ASIC related GPIOs */
#define GPIO_ESERIES_TMIO_IRQ 5
#define GPIO_ESERIES_TMIO_PCLR 19
#define SSCR0_TUM (1 << 23) /* Transmit FIFO underrun interrupt mask */
#define SSCR0_FRDC (0x07000000) /* Frame rate divider control (mask) */
#define SSCR0_SlotsPerFrm(x) (((x) - 1) << 24) /* Time slots per frame [1..8] */
-#define SSCR0_ADC (1 << 30) /* Audio clock select */
+#define SSCR0_ACS (1 << 30) /* Audio clock select */
#define SSCR0_MOD (1 << 31) /* Mode (normal or network) */
#endif
#define SSSR_TINT (1 << 19) /* Receiver Time-out Interrupt */
#define SSSR_PINT (1 << 18) /* Peripheral Trailing Byte Interrupt */
+#if defined(CONFIG_PXA3xx)
+#define SSPSP_EDMYSTOP(x) ((x) << 28) /* Extended Dummy Stop */
+#define SSPSP_EDMYSTRT(x) ((x) << 26) /* Extended Dummy Start */
+#endif
+
#define SSPSP_FSRT (1 << 25) /* Frame Sync Relative Timing */
#define SSPSP_DMYSTOP(x) ((x) << 23) /* Dummy Stop */
#define SSPSP_SFRMWDTH(x) ((x) << 16) /* Serial Frame Width */
GPIO57_nIOIS16,
GPIO104_PSKTSEL,
+ /* I2S */
+ GPIO28_I2S_BITCLK_OUT,
+ GPIO29_I2S_SDATA_IN,
+ GPIO30_I2S_SDATA_OUT,
+ GPIO31_I2S_SYNC,
+
/* MMC */
GPIO32_MMC_CLK,
GPIO112_MMC_CMD,
#include <mach/regs-mem.h>
#include <mach/regs-lcd.h>
#include <mach/regs-sdi.h>
-#include <asm/plat-s3c24xx/regs-iis.h>
+#include <plat/regs-iis.h>
#include <plat/regs-spi.h>
static struct s3c24xx_dma_map __initdata s3c2410_dma_mappings[] = {
#include <mach/regs-mem.h>
#include <mach/regs-lcd.h>
#include <mach/regs-sdi.h>
-#include <asm/plat-s3c24xx/regs-s3c2412-iis.h>
-#include <asm/plat-s3c24xx/regs-iis.h>
+#include <plat/regs-s3c2412-iis.h>
+#include <plat/regs-iis.h>
#include <plat/regs-spi.h>
#define MAP(x) { (x)| DMA_CH_VALID, (x)| DMA_CH_VALID, (x)| DMA_CH_VALID, (x)| DMA_CH_VALID }
#include <mach/regs-mem.h>
#include <mach/regs-lcd.h>
#include <mach/regs-sdi.h>
-#include <asm/plat-s3c24xx/regs-iis.h>
+#include <plat/regs-iis.h>
#include <plat/regs-spi.h>
static struct s3c24xx_dma_map __initdata s3c2440_dma_mappings[] = {
#include <mach/regs-mem.h>
#include <mach/regs-lcd.h>
#include <mach/regs-sdi.h>
-#include <asm/plat-s3c24xx/regs-iis.h>
+#include <plat/regs-iis.h>
#include <plat/regs-spi.h>
#define MAP(x) { \
#define S3C2412_IISCON_RXDMA_ACTIVE (1 << 1)
#define S3C2412_IISCON_IIS_ACTIVE (1 << 0)
+#define S3C64XX_IISMOD_IMS_PCLK (0 << 10)
+#define S3C64XX_IISMOD_IMS_SYSMUX (1 << 10)
+
#define S3C2412_IISMOD_MASTER_INTERNAL (0 << 10)
#define S3C2412_IISMOD_MASTER_EXTERNAL (1 << 10)
#define S3C2412_IISMOD_SLAVE (2 << 10)
#define S3C2412_IISMOD_LR_LLOW (0 << 7)
#define S3C2412_IISMOD_LR_RLOW (1 << 7)
#define S3C2412_IISMOD_SDF_IIS (0 << 5)
-#define S3C2412_IISMOD_SDF_MSB (0 << 5)
-#define S3C2412_IISMOD_SDF_LSB (0 << 5)
+#define S3C2412_IISMOD_SDF_MSB (1 << 5)
+#define S3C2412_IISMOD_SDF_LSB (2 << 5)
#define S3C2412_IISMOD_SDF_MASK (3 << 5)
#define S3C2412_IISMOD_RCLK_256FS (0 << 3)
#define S3C2412_IISMOD_RCLK_512FS (1 << 3)
/*
* R231 (0xE7) - Jack Status
*/
+#define WM8350_JACK_L_LVL 0x0800
#define WM8350_JACK_R_LVL 0x0400
/*
#define WM8400_FLL_OUTDIV_SHIFT 0 /* FLL_OUTDIV - [2:0] */
#define WM8400_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [2:0] */
+struct wm8400;
void wm8400_reset_codec_reg_cache(struct wm8400 *wm8400);
#endif
extern int pxa2xx_ac97_hw_probe(struct platform_device *dev);
extern void pxa2xx_ac97_hw_remove(struct platform_device *dev);
+/* AC97 platform_data */
+/**
+ * struct pxa2xx_ac97_platform_data - pxa ac97 platform data
+ * @reset_gpio: AC97 reset gpio (normally gpio113 or gpio95)
+ * a -1 value means no gpio will be used for reset
+ *
+ * Platform data should only be specified for pxa27x CPUs where a silicon bug
+ * prevents correct operation of the reset line. If not specified, the default
+ * behaviour is to consider gpio 113 as the AC97 reset line, which is the
+ * default on most boards.
+ */
+struct pxa2xx_ac97_platform_data {
+ int reset_gpio;
+};
+
#endif
int (*resume)(struct snd_soc_dai *dai);
/* ops */
- struct snd_soc_dai_ops ops;
+ struct snd_soc_dai_ops *ops;
/* DAI capabilities */
struct snd_soc_pcm_stream capture;
wcontrols, wncontrols)\
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols}
+#define SND_SOC_DAPM_MIXER_NAMED_CTL(wname, wreg, wshift, winvert, \
+ wcontrols, wncontrols)\
+{ .id = snd_soc_dapm_mixer_named_ctl, .name = wname, .reg = wreg, \
+ .shift = wshift, .invert = winvert, .kcontrols = wcontrols, \
+ .num_kcontrols = wncontrols}
#define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \
{ .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrols = NULL, .num_kcontrols = 0}
{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols, \
.event = wevent, .event_flags = wflags}
+#define SND_SOC_DAPM_MIXER_NAMED_CTL_E(wname, wreg, wshift, winvert, \
+ wcontrols, wncontrols, wevent, wflags) \
+{ .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \
+ .invert = winvert, .kcontrols = wcontrols, \
+ .num_kcontrols = wncontrols, .event = wevent, .event_flags = wflags}
#define SND_SOC_DAPM_MICBIAS_E(wname, wreg, wshift, winvert, wevent, wflags) \
{ .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \
.invert = winvert, .kcontrols = NULL, .num_kcontrols = 0, \
.get = snd_soc_dapm_get_value_enum_double, \
.put = snd_soc_dapm_put_value_enum_double, \
.private_value = (unsigned long)&xenum }
+#define SOC_DAPM_PIN_SWITCH(xname) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname " Switch", \
+ .info = snd_soc_dapm_info_pin_switch, \
+ .get = snd_soc_dapm_get_pin_switch, \
+ .put = snd_soc_dapm_put_pin_switch, \
+ .private_value = (unsigned long)xname }
/* dapm stream operations */
#define SND_SOC_DAPM_STREAM_NOP 0x0
struct snd_ctl_elem_value *ucontrol);
int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
+int snd_soc_dapm_info_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_info *uinfo);
+int snd_soc_dapm_get_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uncontrol);
+int snd_soc_dapm_put_pin_switch(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *uncontrol);
int snd_soc_dapm_new_control(struct snd_soc_codec *codec,
const struct snd_soc_dapm_widget *widget);
int snd_soc_dapm_new_controls(struct snd_soc_codec *codec,
int snd_soc_dapm_sys_add(struct device *dev);
/* dapm audio pin control and status */
-int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, char *pin);
-int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, char *pin);
-int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, char *pin);
-int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, char *pin);
+int snd_soc_dapm_enable_pin(struct snd_soc_codec *codec, const char *pin);
+int snd_soc_dapm_disable_pin(struct snd_soc_codec *codec, const char *pin);
+int snd_soc_dapm_nc_pin(struct snd_soc_codec *codec, const char *pin);
+int snd_soc_dapm_get_pin_status(struct snd_soc_codec *codec, const char *pin);
int snd_soc_dapm_sync(struct snd_soc_codec *codec);
/* dapm widget types */
snd_soc_dapm_mux, /* selects 1 analog signal from many inputs */
snd_soc_dapm_value_mux, /* selects 1 analog signal from many inputs */
snd_soc_dapm_mixer, /* mixes several analog signals together */
+ snd_soc_dapm_mixer_named_ctl, /* mixer with named controls */
snd_soc_dapm_pga, /* programmable gain/attenuation (volume) */
snd_soc_dapm_adc, /* analog to digital converter */
snd_soc_dapm_dac, /* digital to analog converter */
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/control.h>
SND_SOC_BIAS_OFF,
};
+struct snd_jack;
+struct snd_soc_card;
struct snd_soc_device;
struct snd_soc_pcm_stream;
struct snd_soc_ops;
struct snd_soc_codec;
struct soc_enum;
struct snd_soc_ac97_ops;
+struct snd_soc_jack;
+struct snd_soc_jack_pin;
+#ifdef CONFIG_GPIOLIB
+struct snd_soc_jack_gpio;
+#endif
typedef int (*hw_write_t)(void *,const char* ,int);
typedef int (*hw_read_t)(void *,char* ,int);
int snd_soc_set_runtime_hwparams(struct snd_pcm_substream *substream,
const struct snd_pcm_hardware *hw);
+/* Jack reporting */
+int snd_soc_jack_new(struct snd_soc_card *card, const char *id, int type,
+ struct snd_soc_jack *jack);
+void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask);
+int snd_soc_jack_add_pins(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_pin *pins);
+#ifdef CONFIG_GPIOLIB
+int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_gpio *gpios);
+void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count,
+ struct snd_soc_jack_gpio *gpios);
+#endif
+
/* codec IO */
#define snd_soc_read(codec, reg) codec->read(codec, reg)
#define snd_soc_write(codec, reg, value) codec->write(codec, reg, value)
*/
struct snd_kcontrol *snd_soc_cnew(const struct snd_kcontrol_new *_template,
void *data, char *long_name);
+int snd_soc_add_controls(struct snd_soc_codec *codec,
+ const struct snd_kcontrol_new *controls, int num_controls);
int snd_soc_info_enum_double(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo);
int snd_soc_info_enum_ext(struct snd_kcontrol *kcontrol,
int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol);
+/**
+ * struct snd_soc_jack_pin - Describes a pin to update based on jack detection
+ *
+ * @pin: name of the pin to update
+ * @mask: bits to check for in reported jack status
+ * @invert: if non-zero then pin is enabled when status is not reported
+ */
+struct snd_soc_jack_pin {
+ struct list_head list;
+ const char *pin;
+ int mask;
+ bool invert;
+};
+
+/**
+ * struct snd_soc_jack_gpio - Describes a gpio pin for jack detection
+ *
+ * @gpio: gpio number
+ * @name: gpio name
+ * @report: value to report when jack detected
+ * @invert: report presence in low state
+ * @debouce_time: debouce time in ms
+ */
+#ifdef CONFIG_GPIOLIB
+struct snd_soc_jack_gpio {
+ unsigned int gpio;
+ const char *name;
+ int report;
+ int invert;
+ int debounce_time;
+ struct snd_soc_jack *jack;
+ struct work_struct work;
+};
+#endif
+
+struct snd_soc_jack {
+ struct snd_jack *jack;
+ struct snd_soc_card *card;
+ struct list_head pins;
+ int status;
+};
+
/* SoC PCM stream information */
struct snd_soc_pcm_stream {
char *stream_name;
struct snd_soc_device *socdev;
+ struct snd_soc_codec *codec;
+
struct snd_soc_platform *platform;
struct delayed_work delayed_work;
struct work_struct deferred_resume_work;
struct snd_soc_device {
struct device *dev;
struct snd_soc_card *card;
- struct snd_soc_codec *codec;
struct snd_soc_codec_device *codec_dev;
void *codec_data;
};
static volatile long gsr_bits;
static struct clk *ac97_clk;
static struct clk *ac97conf_clk;
+static int reset_gpio;
/*
* Beware PXA27x bugs:
* 1 jiffy timeout if interrupt never comes).
*/
+enum {
+ RESETGPIO_FORCE_HIGH,
+ RESETGPIO_FORCE_LOW,
+ RESETGPIO_NORMAL_ALTFUNC
+};
+
+/**
+ * set_resetgpio_mode - computes and sets the AC97_RESET gpio mode on PXA
+ * @mode: chosen action
+ *
+ * As the PXA27x CPUs suffer from a AC97 bug, a manual control of the reset line
+ * must be done to insure proper work of AC97 reset line. This function
+ * computes the correct gpio_mode for further use by reset functions, and
+ * applied the change through pxa_gpio_mode.
+ */
+static void set_resetgpio_mode(int resetgpio_action)
+{
+ int mode = 0;
+
+ if (reset_gpio)
+ switch (resetgpio_action) {
+ case RESETGPIO_NORMAL_ALTFUNC:
+ if (reset_gpio == 113)
+ mode = 113 | GPIO_OUT | GPIO_DFLT_LOW;
+ if (reset_gpio == 95)
+ mode = 95 | GPIO_ALT_FN_1_OUT;
+ break;
+ case RESETGPIO_FORCE_LOW:
+ mode = reset_gpio | GPIO_OUT | GPIO_DFLT_LOW;
+ break;
+ case RESETGPIO_FORCE_HIGH:
+ mode = reset_gpio | GPIO_OUT | GPIO_DFLT_HIGH;
+ break;
+ };
+
+ if (mode)
+ pxa_gpio_mode(mode);
+}
+
unsigned short pxa2xx_ac97_read(struct snd_ac97 *ac97, unsigned short reg)
{
unsigned short val = -1;
/* warm reset broken on Bulverde,
so manually keep AC97 reset high */
- pxa_gpio_mode(113 | GPIO_OUT | GPIO_DFLT_HIGH);
+ set_resetgpio_mode(RESETGPIO_FORCE_HIGH);
udelay(10);
GCR |= GCR_WARM_RST;
- pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT);
+ set_resetgpio_mode(RESETGPIO_NORMAL_ALTFUNC);
udelay(500);
}
pxa_gpio_mode(GPIO29_SDATA_IN_AC97_MD);
}
if (cpu_is_pxa27x()) {
- /* Use GPIO 113 as AC97 Reset on Bulverde */
- pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT);
+ /* Use GPIO 113 or 95 as AC97 Reset on Bulverde */
+ set_resetgpio_mode(RESETGPIO_NORMAL_ALTFUNC);
}
clk_enable(ac97_clk);
return 0;
int __devinit pxa2xx_ac97_hw_probe(struct platform_device *dev)
{
int ret;
+ struct pxa2xx_ac97_platform_data *pdata = dev->dev.platform_data;
+
+ if (pdata) {
+ switch (pdata->reset_gpio) {
+ case 95:
+ case 113:
+ reset_gpio = pdata->reset_gpio;
+ break;
+ case 0:
+ reset_gpio = 113;
+ break;
+ case -1:
+ break;
+ default:
+ dev_err(&dev->dev, "Invalid reset GPIO %d\n",
+ pdata->reset_gpio);
+ }
+ } else {
+ if (cpu_is_pxa27x())
+ reset_gpio = 113;
+ }
if (cpu_is_pxa25x() || cpu_is_pxa27x()) {
pxa_gpio_mode(GPIO31_SYNC_AC97_MD);
if (cpu_is_pxa27x()) {
/* Use GPIO 113 as AC97 Reset on Bulverde */
- pxa_gpio_mode(113 | GPIO_ALT_FN_2_OUT);
+ set_resetgpio_mode(RESETGPIO_NORMAL_ALTFUNC);
ac97conf_clk = clk_get(&dev->dev, "AC97CONFCLK");
if (IS_ERR(ac97conf_clk)) {
ret = PTR_ERR(ac97conf_clk);
tristate "ALSA for SoC audio support"
select SND_PCM
select AC97_BUS if SND_SOC_AC97_BUS
+ select SND_JACK if INPUT=y || INPUT=SND
---help---
If you want ASoC support, you should say Y here and also to the
-snd-soc-core-objs := soc-core.o soc-dapm.o
+snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
obj-$(CONFIG_SND_SOC) += codecs/
vma->vm_end - vma->vm_start, vma->vm_page_prot);
}
-struct snd_pcm_ops atmel_pcm_ops = {
+static struct snd_pcm_ops atmel_pcm_ops = {
.open = atmel_pcm_open,
.close = atmel_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
#define ATMEL_SSC_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+static struct snd_soc_dai_ops atmel_ssc_dai_ops = {
+ .startup = atmel_ssc_startup,
+ .shutdown = atmel_ssc_shutdown,
+ .prepare = atmel_ssc_prepare,
+ .hw_params = atmel_ssc_hw_params,
+ .set_fmt = atmel_ssc_set_dai_fmt,
+ .set_clkdiv = atmel_ssc_set_dai_clkdiv,
+};
+
struct snd_soc_dai atmel_ssc_dai[NUM_SSC_DEVICES] = {
{ .name = "atmel-ssc0",
.id = 0,
.channels_max = 2,
.rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,},
- .ops = {
- .startup = atmel_ssc_startup,
- .shutdown = atmel_ssc_shutdown,
- .prepare = atmel_ssc_prepare,
- .hw_params = atmel_ssc_hw_params,
- .set_fmt = atmel_ssc_set_dai_fmt,
- .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
+ .ops = &atmel_ssc_dai_ops,
.private_data = &ssc_info[0],
},
#if NUM_SSC_DEVICES == 3
.channels_max = 2,
.rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,},
- .ops = {
- .startup = atmel_ssc_startup,
- .shutdown = atmel_ssc_shutdown,
- .prepare = atmel_ssc_prepare,
- .hw_params = atmel_ssc_hw_params,
- .set_fmt = atmel_ssc_set_dai_fmt,
- .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
+ .ops = &atmel_ssc_dai_ops,
.private_data = &ssc_info[1],
},
{ .name = "atmel-ssc2",
.channels_max = 2,
.rates = ATMEL_SSC_RATES,
.formats = ATMEL_SSC_FORMATS,},
- .ops = {
- .startup = atmel_ssc_startup,
- .shutdown = atmel_ssc_shutdown,
- .prepare = atmel_ssc_prepare,
- .hw_params = atmel_ssc_hw_params,
- .set_fmt = atmel_ssc_set_dai_fmt,
- .set_clkdiv = atmel_ssc_set_dai_clkdiv,},
+ .ops = &atmel_ssc_dai_ops,
.private_data = &ssc_info[2],
},
#endif
*/
switch (params_rate(params)) {
case 48000:
- pll_out = 12288000;
- mclk_div = WM8510_MCLKDIV_1;
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_2;
bclk = WM8510_BCLKDIV_8;
break;
case 44100:
- pll_out = 11289600;
- mclk_div = WM8510_MCLKDIV_1;
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_2;
bclk = WM8510_BCLKDIV_8;
break;
case 22050:
- pll_out = 11289600;
- mclk_div = WM8510_MCLKDIV_2;
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_4;
bclk = WM8510_BCLKDIV_8;
break;
case 16000:
- pll_out = 12288000;
- mclk_div = WM8510_MCLKDIV_3;
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_6;
bclk = WM8510_BCLKDIV_8;
break;
case 11025:
- pll_out = 11289600;
- mclk_div = WM8510_MCLKDIV_4;
+ pll_out = 22579200;
+ mclk_div = WM8510_MCLKDIV_8;
bclk = WM8510_BCLKDIV_8;
break;
case 8000:
- pll_out = 12288000;
- mclk_div = WM8510_MCLKDIV_6;
+ pll_out = 24576000;
+ mclk_div = WM8510_MCLKDIV_12;
bclk = WM8510_BCLKDIV_8;
break;
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
+#include <linux/i2c.h>
#include <linux/atmel-ssc.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
+#include <asm/mach-types.h>
#include <mach/hardware.h>
#include <mach/gpio.h>
#include "atmel-pcm.h"
#include "atmel_ssc_dai.h"
+#define MCLK_RATE 12000000
+
+static struct clk *mclk;
static int at91sam9g20ek_startup(struct snd_pcm_substream *substream)
{
struct snd_soc_dai *codec_dai = rtd->dai->codec_dai;
int ret;
- /* codec system clock is supplied by PCK0, set to 12MHz */
ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK,
- 12000000, SND_SOC_CLOCK_IN);
- if (ret < 0)
+ MCLK_RATE, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ clk_disable(mclk);
return ret;
+ }
return 0;
}
.shutdown = at91sam9g20ek_shutdown,
};
+static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card,
+ enum snd_soc_bias_level level)
+{
+ static int mclk_on;
+ int ret = 0;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ case SND_SOC_BIAS_PREPARE:
+ if (!mclk_on)
+ ret = clk_enable(mclk);
+ if (ret == 0)
+ mclk_on = 1;
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ case SND_SOC_BIAS_STANDBY:
+ if (mclk_on)
+ clk_disable(mclk);
+ mclk_on = 0;
+ break;
+ }
+
+ return ret;
+}
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets[] = {
SND_SOC_DAPM_MIC("Int Mic", NULL),
};
static struct snd_soc_card snd_soc_at91sam9g20ek = {
- .name = "WM8731",
+ .name = "AT91SAMG20-EK",
.platform = &atmel_soc_platform,
.dai_link = &at91sam9g20ek_dai,
.num_links = 1,
+ .set_bias_level = at91sam9g20ek_set_bias_level,
};
-static struct wm8731_setup_data at91sam9g20ek_wm8731_setup = {
- .i2c_bus = 0,
- .i2c_address = 0x1b,
-};
+/*
+ * FIXME: This is a temporary bodge to avoid cross-tree merge issues.
+ * New drivers should register the wm8731 I2C device in the machine
+ * setup code (under arch/arm for ARM systems).
+ */
+static int wm8731_i2c_register(void)
+{
+ struct i2c_board_info info;
+ struct i2c_adapter *adapter;
+ struct i2c_client *client;
+
+ memset(&info, 0, sizeof(struct i2c_board_info));
+ info.addr = 0x1b;
+ strlcpy(info.type, "wm8731", I2C_NAME_SIZE);
+
+ adapter = i2c_get_adapter(0);
+ if (!adapter) {
+ printk(KERN_ERR "can't get i2c adapter 0\n");
+ return -ENODEV;
+ }
+
+ client = i2c_new_device(adapter, &info);
+ i2c_put_adapter(adapter);
+ if (!client) {
+ printk(KERN_ERR "can't add i2c device at 0x%x\n",
+ (unsigned int)info.addr);
+ return -ENODEV;
+ }
+
+ return 0;
+}
static struct snd_soc_device at91sam9g20ek_snd_devdata = {
.card = &snd_soc_at91sam9g20ek,
.codec_dev = &soc_codec_dev_wm8731,
- .codec_data = &at91sam9g20ek_wm8731_setup,
};
static struct platform_device *at91sam9g20ek_snd_device;
{
struct atmel_ssc_info *ssc_p = at91sam9g20ek_dai.cpu_dai->private_data;
struct ssc_device *ssc = NULL;
+ struct clk *pllb;
int ret;
+ if (!machine_is_at91sam9g20ek())
+ return -ENODEV;
+
+ /*
+ * Codec MCLK is supplied by PCK0 - set it up.
+ */
+ mclk = clk_get(NULL, "pck0");
+ if (IS_ERR(mclk)) {
+ printk(KERN_ERR "ASoC: Failed to get MCLK\n");
+ ret = PTR_ERR(mclk);
+ goto err;
+ }
+
+ pllb = clk_get(NULL, "pllb");
+ if (IS_ERR(mclk)) {
+ printk(KERN_ERR "ASoC: Failed to get PLLB\n");
+ ret = PTR_ERR(mclk);
+ goto err_mclk;
+ }
+ ret = clk_set_parent(mclk, pllb);
+ clk_put(pllb);
+ if (ret != 0) {
+ printk(KERN_ERR "ASoC: Failed to set MCLK parent\n");
+ goto err_mclk;
+ }
+
+ clk_set_rate(mclk, MCLK_RATE);
+
/*
* Request SSC device
*/
ssc = ssc_request(0);
if (IS_ERR(ssc)) {
+ printk(KERN_ERR "ASoC: Failed to request SSC 0\n");
ret = PTR_ERR(ssc);
ssc = NULL;
goto err_ssc;
}
ssc_p->ssc = ssc;
+ ret = wm8731_i2c_register();
+ if (ret != 0)
+ goto err_ssc;
+
at91sam9g20ek_snd_device = platform_device_alloc("soc-audio", -1);
if (!at91sam9g20ek_snd_device) {
- printk(KERN_DEBUG
- "platform device allocation failed\n");
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
ret = -ENOMEM;
}
ret = platform_device_add(at91sam9g20ek_snd_device);
if (ret) {
- printk(KERN_DEBUG
- "platform device allocation failed\n");
+ printk(KERN_ERR "ASoC: Platform device allocation failed\n");
platform_device_put(at91sam9g20ek_snd_device);
}
return ret;
err_ssc:
+ ssc_free(ssc);
+ ssc_p->ssc = NULL;
+err_mclk:
+ clk_put(mclk);
+ mclk = NULL;
+err:
return ret;
}
platform_device_unregister(at91sam9g20ek_snd_device);
at91sam9g20ek_snd_device = NULL;
+ clk_put(mclk);
+ mclk = NULL;
}
module_init(at91sam9g20ek_init);
return 0;
}
-struct snd_pcm_ops au1xpsc_pcm_ops = {
+static struct snd_pcm_ops au1xpsc_pcm_ops = {
.open = au1xpsc_pcm_open,
.close = au1xpsc_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
return 0;
}
+static struct snd_soc_dai_ops au1xpsc_ac97_dai_ops = {
+ .trigger = au1xpsc_ac97_trigger,
+ .hw_params = au1xpsc_ac97_hw_params,
+};
+
struct snd_soc_dai au1xpsc_ac97_dai = {
.name = "au1xpsc_ac97",
.ac97_control = 1,
.channels_min = 2,
.channels_max = 2,
},
- .ops = {
- .trigger = au1xpsc_ac97_trigger,
- .hw_params = au1xpsc_ac97_hw_params,
- },
+ .ops = &au1xpsc_ac97_dai_ops,
};
EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai);
return 0;
}
+static struct snd_soc_dai_ops au1xpsc_i2s_dai_ops = {
+ .trigger = au1xpsc_i2s_trigger,
+ .hw_params = au1xpsc_i2s_hw_params,
+ .set_fmt = au1xpsc_i2s_set_fmt,
+};
+
struct snd_soc_dai au1xpsc_i2s_dai = {
.name = "au1xpsc_i2s",
.probe = au1xpsc_i2s_probe,
.channels_min = 2,
.channels_max = 8, /* 2 without external help */
},
- .ops = {
- .trigger = au1xpsc_i2s_trigger,
- .hw_params = au1xpsc_i2s_hw_params,
- .set_fmt = au1xpsc_i2s_set_fmt,
- },
+ .ops = &au1xpsc_i2s_dai_ops,
};
EXPORT_SYMBOL(au1xpsc_i2s_dai);
}
#endif
-struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
+static struct snd_pcm_ops bf5xx_pcm_ac97_ops = {
.open = bf5xx_pcm_open,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = bf5xx_pcm_hw_params,
#include "bf5xx-sport.h"
#include "bf5xx-ac97.h"
-#if defined(CONFIG_BF54x)
-#define PIN_REQ_SPORT_0 {P_SPORT0_TFS, P_SPORT0_DTPRI, P_SPORT0_TSCLK, \
- P_SPORT0_RFS, P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}
-
-#define PIN_REQ_SPORT_1 {P_SPORT1_TFS, P_SPORT1_DTPRI, P_SPORT1_TSCLK, \
- P_SPORT1_RFS, P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}
-
-#define PIN_REQ_SPORT_2 {P_SPORT2_TFS, P_SPORT2_DTPRI, P_SPORT2_TSCLK, \
- P_SPORT2_RFS, P_SPORT2_DRPRI, P_SPORT2_RSCLK, 0}
-
-#define PIN_REQ_SPORT_3 {P_SPORT3_TFS, P_SPORT3_DTPRI, P_SPORT3_TSCLK, \
- P_SPORT3_RFS, P_SPORT3_DRPRI, P_SPORT3_RSCLK, 0}
-#else
-#define PIN_REQ_SPORT_0 {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, \
- P_SPORT0_DRPRI, P_SPORT0_RSCLK, 0}
-
-#define PIN_REQ_SPORT_1 {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, \
- P_SPORT1_DRPRI, P_SPORT1_RSCLK, 0}
-#endif
-
static int *cmd_count;
static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM;
+#define SPORT_REQ(x) \
+ [x] = {P_SPORT##x##_TFS, P_SPORT##x##_DTPRI, P_SPORT##x##_TSCLK, \
+ P_SPORT##x##_RFS, P_SPORT##x##_DRPRI, P_SPORT##x##_RSCLK, 0}
static u16 sport_req[][7] = {
- PIN_REQ_SPORT_0,
-#ifdef PIN_REQ_SPORT_1
- PIN_REQ_SPORT_1,
+#ifdef SPORT0_TCR1
+ SPORT_REQ(0),
+#endif
+#ifdef SPORT1_TCR1
+ SPORT_REQ(1),
#endif
-#ifdef PIN_REQ_SPORT_2
- PIN_REQ_SPORT_2,
+#ifdef SPORT2_TCR1
+ SPORT_REQ(2),
#endif
-#ifdef PIN_REQ_SPORT_3
- PIN_REQ_SPORT_3,
+#ifdef SPORT3_TCR1
+ SPORT_REQ(3),
#endif
- };
+};
+#define SPORT_PARAMS(x) \
+ [x] = { \
+ .dma_rx_chan = CH_SPORT##x##_RX, \
+ .dma_tx_chan = CH_SPORT##x##_TX, \
+ .err_irq = IRQ_SPORT##x##_ERROR, \
+ .regs = (struct sport_register *)SPORT##x##_TCR1, \
+ }
static struct sport_param sport_params[4] = {
- {
- .dma_rx_chan = CH_SPORT0_RX,
- .dma_tx_chan = CH_SPORT0_TX,
- .err_irq = IRQ_SPORT0_ERROR,
- .regs = (struct sport_register *)SPORT0_TCR1,
- },
-#ifdef PIN_REQ_SPORT_1
- {
- .dma_rx_chan = CH_SPORT1_RX,
- .dma_tx_chan = CH_SPORT1_TX,
- .err_irq = IRQ_SPORT1_ERROR,
- .regs = (struct sport_register *)SPORT1_TCR1,
- },
+#ifdef SPORT0_TCR1
+ SPORT_PARAMS(0),
#endif
-#ifdef PIN_REQ_SPORT_2
- {
- .dma_rx_chan = CH_SPORT2_RX,
- .dma_tx_chan = CH_SPORT2_TX,
- .err_irq = IRQ_SPORT2_ERROR,
- .regs = (struct sport_register *)SPORT2_TCR1,
- },
+#ifdef SPORT1_TCR1
+ SPORT_PARAMS(1),
#endif
-#ifdef PIN_REQ_SPORT_3
- {
- .dma_rx_chan = CH_SPORT3_RX,
- .dma_tx_chan = CH_SPORT3_TX,
- .err_irq = IRQ_SPORT3_ERROR,
- .regs = (struct sport_register *)SPORT3_TCR1,
- }
+#ifdef SPORT2_TCR1
+ SPORT_PARAMS(2),
+#endif
+#ifdef SPORT3_TCR1
+ SPORT_PARAMS(3),
#endif
};
if (cmd_count == NULL)
return -ENOMEM;
- if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) {
+ if (peripheral_request_list(sport_req[sport_num], "soc-audio")) {
pr_err("Requesting Peripherals failed\n");
ret = -EFAULT;
goto peripheral_err;
- }
+ }
#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
/* Request PB3 as reset pin */
sport_err:
#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
-#endif
gpio_err:
- peripheral_free_list(&sport_req[sport_num][0]);
+#endif
+ peripheral_free_list(sport_req[sport_num]);
peripheral_err:
free_page((unsigned long)cmd_count);
cmd_count = NULL;
{
free_page((unsigned long)cmd_count);
cmd_count = NULL;
- peripheral_free_list(&sport_req[sport_num][0]);
+ peripheral_free_list(sport_req[sport_num]);
#ifdef CONFIG_SND_BF5XX_HAVE_COLD_RESET
gpio_free(CONFIG_SND_BF5XX_RESET_GPIO_NUM);
#endif
SSYNC();
/* When TUVF is set, the data is already send out */
- while (!(status & TUVF) && count++ < 10000) {
+ while (!(status & TUVF) && ++count < 10000) {
udelay(1);
status = bfin_read_SPORT_STAT();
SSYNC();
SSYNC();
local_irq_enable();
- if (count == 10000) {
+ if (count >= 10000) {
printk(KERN_ERR "ad73311: failed to configure codec\n");
return -1;
}
return 0 ;
}
-struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
+static struct snd_pcm_ops bf5xx_pcm_i2s_ops = {
.open = bf5xx_pcm_open,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = bf5xx_pcm_hw_params,
#define BF5XX_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE |\
SNDRV_PCM_FMTBIT_S32_LE)
+static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
+ .startup = bf5xx_i2s_startup,
+ .shutdown = bf5xx_i2s_shutdown,
+ .hw_params = bf5xx_i2s_hw_params,
+ .set_fmt = bf5xx_i2s_set_dai_fmt,
+};
+
struct snd_soc_dai bf5xx_i2s_dai = {
.name = "bf5xx-i2s",
.id = 0,
.channels_max = 2,
.rates = BF5XX_I2S_RATES,
.formats = BF5XX_I2S_FORMATS,},
- .ops = {
- .startup = bf5xx_i2s_startup,
- .shutdown = bf5xx_i2s_shutdown,
- .hw_params = bf5xx_i2s_hw_params,
- .set_fmt = bf5xx_i2s_set_dai_fmt,
- },
+ .ops = &bf5xx_i2s_dai_ops,
};
EXPORT_SYMBOL_GPL(bf5xx_i2s_dai);
int i;
for (i = 0; i < fragcount; ++i) {
- desc[i].next_desc_addr = (unsigned long)&(desc[i + 1]);
+ desc[i].next_desc_addr = &(desc[i + 1]);
desc[i].start_addr = (unsigned long)buf + i*fragsize;
desc[i].cfg = cfg;
desc[i].x_count = x_count;
}
/* make circular */
- desc[fragcount-1].next_desc_addr = (unsigned long)desc;
+ desc[fragcount-1].next_desc_addr = desc;
- pr_debug("setup desc: desc0=%p, next0=%lx, desc1=%p,"
- "next1=%lx\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
- &(desc[0]), desc[0].next_desc_addr,
- &(desc[1]), desc[1].next_desc_addr,
+ pr_debug("setup desc: desc0=%p, next0=%p, desc1=%p,"
+ "next1=%p\nx_count=%x,y_count=%x,addr=0x%lx,cfs=0x%x\n",
+ desc, desc[0].next_desc_addr,
+ desc+1, desc[1].next_desc_addr,
desc[0].x_count, desc[0].y_count,
desc[0].start_addr, desc[0].cfg);
}
BUG_ON(sport->curr_rx_desc == sport->dummy_rx_desc);
/* Maybe the dummy buffer descriptor ring is damaged */
- sport->dummy_rx_desc->next_desc_addr = \
- (unsigned long)(sport->dummy_rx_desc+1);
+ sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc + 1;
local_irq_save(flags);
- desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_rx_chan);
+ desc = get_dma_next_desc_ptr(sport->dma_rx_chan);
/* Copy the descriptor which will be damaged to backup */
temp_desc = *desc;
desc->x_count = 0xa;
desc->y_count = 0;
- desc->next_desc_addr = (unsigned long)(sport->dummy_rx_desc);
+ desc->next_desc_addr = sport->dummy_rx_desc;
local_irq_restore(flags);
/* Waiting for dummy buffer descriptor is already hooked*/
while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
- sizeof(struct dmasg)) !=
- (unsigned long)sport->dummy_rx_desc)
- ;
+ sizeof(struct dmasg)) != sport->dummy_rx_desc)
+ continue;
sport->curr_rx_desc = sport->dummy_rx_desc;
/* Restore the damaged descriptor */
*desc = temp_desc;
static inline int sport_rx_dma_start(struct sport_device *sport, int dummy)
{
if (dummy) {
- sport->dummy_rx_desc->next_desc_addr = \
- (unsigned long) sport->dummy_rx_desc;
+ sport->dummy_rx_desc->next_desc_addr = sport->dummy_rx_desc;
sport->curr_rx_desc = sport->dummy_rx_desc;
} else
sport->curr_rx_desc = sport->dma_rx_desc;
- set_dma_next_desc_addr(sport->dma_rx_chan, \
- (unsigned long)(sport->curr_rx_desc));
+ set_dma_next_desc_addr(sport->dma_rx_chan, sport->curr_rx_desc);
set_dma_x_count(sport->dma_rx_chan, 0);
set_dma_x_modify(sport->dma_rx_chan, 0);
set_dma_config(sport->dma_rx_chan, (DMAFLOW_LARGE | NDSIZE_9 | \
static inline int sport_tx_dma_start(struct sport_device *sport, int dummy)
{
if (dummy) {
- sport->dummy_tx_desc->next_desc_addr = \
- (unsigned long) sport->dummy_tx_desc;
+ sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc;
sport->curr_tx_desc = sport->dummy_tx_desc;
} else
sport->curr_tx_desc = sport->dma_tx_desc;
- set_dma_next_desc_addr(sport->dma_tx_chan, \
- (unsigned long)(sport->curr_tx_desc));
+ set_dma_next_desc_addr(sport->dma_tx_chan, sport->curr_tx_desc);
set_dma_x_count(sport->dma_tx_chan, 0);
set_dma_x_modify(sport->dma_tx_chan, 0);
set_dma_config(sport->dma_tx_chan,
BUG_ON(sport->curr_rx_desc != sport->dummy_rx_desc);
local_irq_save(flags);
while ((get_dma_curr_desc_ptr(sport->dma_rx_chan) -
- sizeof(struct dmasg)) !=
- (unsigned long)sport->dummy_rx_desc)
- ;
- sport->dummy_rx_desc->next_desc_addr =
- (unsigned long)(sport->dma_rx_desc);
+ sizeof(struct dmasg)) != sport->dummy_rx_desc)
+ continue;
+ sport->dummy_rx_desc->next_desc_addr = sport->dma_rx_desc;
local_irq_restore(flags);
sport->curr_rx_desc = sport->dma_rx_desc;
} else {
BUG_ON(sport->dummy_tx_desc == NULL);
BUG_ON(sport->curr_tx_desc == sport->dummy_tx_desc);
- sport->dummy_tx_desc->next_desc_addr = \
- (unsigned long)(sport->dummy_tx_desc+1);
+ sport->dummy_tx_desc->next_desc_addr = sport->dummy_tx_desc + 1;
/* Shorten the time on last normal descriptor */
local_irq_save(flags);
- desc = (struct dmasg *)get_dma_next_desc_ptr(sport->dma_tx_chan);
+ desc = get_dma_next_desc_ptr(sport->dma_tx_chan);
/* Store the descriptor which will be damaged */
temp_desc = *desc;
desc->x_count = 0xa;
desc->y_count = 0;
- desc->next_desc_addr = (unsigned long)(sport->dummy_tx_desc);
+ desc->next_desc_addr = sport->dummy_tx_desc;
local_irq_restore(flags);
/* Waiting for dummy buffer descriptor is already hooked*/
while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) - \
- sizeof(struct dmasg)) != \
- (unsigned long)sport->dummy_tx_desc)
- ;
+ sizeof(struct dmasg)) != sport->dummy_tx_desc)
+ continue;
sport->curr_tx_desc = sport->dummy_tx_desc;
/* Restore the damaged descriptor */
*desc = temp_desc;
/* Hook the normal buffer descriptor */
local_irq_save(flags);
while ((get_dma_curr_desc_ptr(sport->dma_tx_chan) -
- sizeof(struct dmasg)) !=
- (unsigned long)sport->dummy_tx_desc)
- ;
- sport->dummy_tx_desc->next_desc_addr =
- (unsigned long)(sport->dma_tx_desc);
+ sizeof(struct dmasg)) != sport->dummy_tx_desc)
+ continue;
+ sport->dummy_tx_desc->next_desc_addr = sport->dma_tx_desc;
local_irq_restore(flags);
sport->curr_tx_desc = sport->dma_tx_desc;
} else {
unsigned config;
pr_debug("%s entered\n", __func__);
-#if L1_DATA_A_LENGTH != 0
- desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc));
-#else
- {
+ if (L1_DATA_A_LENGTH)
+ desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+ else {
dma_addr_t addr;
desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ memset(desc, 0, 2 * sizeof(*desc));
}
-#endif
if (desc == NULL) {
pr_err("Failed to allocate memory for dummy rx desc\n");
return -ENOMEM;
}
- memset(desc, 0, 2 * sizeof(*desc));
sport->dummy_rx_desc = desc;
desc->start_addr = (unsigned long)sport->dummy_buf;
config = DMAFLOW_LARGE | NDSIZE_9 | compute_wdsize(sport->wdsize)
desc->y_count = 0;
desc->y_modify = 0;
memcpy(desc+1, desc, sizeof(*desc));
- desc->next_desc_addr = (unsigned long)(desc+1);
- desc[1].next_desc_addr = (unsigned long)desc;
+ desc->next_desc_addr = desc + 1;
+ desc[1].next_desc_addr = desc;
return 0;
}
pr_debug("%s entered\n", __func__);
-#if L1_DATA_A_LENGTH != 0
- desc = (struct dmasg *) l1_data_sram_alloc(2 * sizeof(*desc));
-#else
- {
+ if (L1_DATA_A_LENGTH)
+ desc = l1_data_sram_zalloc(2 * sizeof(*desc));
+ else {
dma_addr_t addr;
desc = dma_alloc_coherent(NULL, 2 * sizeof(*desc), &addr, 0);
+ memset(desc, 0, 2 * sizeof(*desc));
}
-#endif
if (!desc) {
pr_err("Failed to allocate memory for dummy tx desc\n");
return -ENOMEM;
}
- memset(desc, 0, 2 * sizeof(*desc));
sport->dummy_tx_desc = desc;
desc->start_addr = (unsigned long)sport->dummy_buf + \
sport->dummy_count;
desc->y_count = 0;
desc->y_modify = 0;
memcpy(desc+1, desc, sizeof(*desc));
- desc->next_desc_addr = (unsigned long)(desc+1);
- desc[1].next_desc_addr = (unsigned long)desc;
+ desc->next_desc_addr = desc + 1;
+ desc[1].next_desc_addr = desc;
return 0;
}
sport->wdsize = wdsize;
sport->dummy_count = dummy_count;
-#if L1_DATA_A_LENGTH != 0
- sport->dummy_buf = l1_data_sram_alloc(dummy_count * 2);
-#else
- sport->dummy_buf = kmalloc(dummy_count * 2, GFP_KERNEL);
-#endif
+ if (L1_DATA_A_LENGTH)
+ sport->dummy_buf = l1_data_sram_zalloc(dummy_count * 2);
+ else
+ sport->dummy_buf = kzalloc(dummy_count * 2, GFP_KERNEL);
if (sport->dummy_buf == NULL) {
pr_err("Failed to allocate dummy buffer\n");
goto __error;
}
- memset(sport->dummy_buf, 0, dummy_count * 2);
ret = sport_config_rx_dummy(sport);
if (ret) {
pr_err("Failed to config rx dummy ring\n");
sport = NULL;
}
EXPORT_SYMBOL(sport_done);
+
/*
* It is only used to send several bytes when dma is not enabled
* sport controller is configured but not enabled.
MODULE_AUTHOR("Roy Huang");
MODULE_DESCRIPTION("SPORT driver for ADI Blackfin");
MODULE_LICENSE("GPL");
-
config SND_SOC_ALL_CODECS
tristate "Build all ASoC CODEC drivers"
+ select SND_SOC_L3
select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS
select SND_SOC_AD1980 if SND_SOC_AC97_BUS
select SND_SOC_AD73311 if I2C
+ select SND_SOC_AK4104 if SPI_MASTER
select SND_SOC_AK4535 if I2C
select SND_SOC_CS4270 if I2C
select SND_SOC_PCM3008
select SND_SOC_UDA134X
select SND_SOC_UDA1380 if I2C
select SND_SOC_WM8350 if MFD_WM8350
+ select SND_SOC_WM8400 if MFD_WM8400
select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8580 if I2C
select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI
select SND_SOC_WM8903 if I2C
select SND_SOC_WM8971 if I2C
select SND_SOC_WM8990 if I2C
+ select SND_SOC_WM9705 if SND_SOC_AC97_BUS
select SND_SOC_WM9712 if SND_SOC_AC97_BUS
select SND_SOC_WM9713 if SND_SOC_AC97_BUS
help
config SND_SOC_AD73311
tristate
+config SND_SOC_AK4104
+ tristate
+
config SND_SOC_AK4535
tristate
config SND_SOC_CS4270
tristate
-# Cirrus Logic CS4270 Codec Hardware Mute Support
-# Select if you have external muting circuitry attached to your CS4270.
-config SND_SOC_CS4270_HWMUTE
- bool
- depends on SND_SOC_CS4270
-
# Cirrus Logic CS4270 Codec VD = 3.3V Errata
# Select if you are affected by the errata where the part will not function
# if MCLK divide-by-1.5 is selected and VD is set to 3.3V. The driver will
config SND_SOC_TLV320AIC23
tristate
- depends on I2C
config SND_SOC_TLV320AIC26
tristate "TI TLV320AIC26 Codec support" if SND_SOC_OF_SIMPLE
config SND_SOC_TLV320AIC3X
tristate
- depends on I2C
config SND_SOC_TWL4030
tristate
- depends on TWL4030_CORE
config SND_SOC_UDA134X
tristate
- select SND_SOC_L3
config SND_SOC_UDA1380
tristate
config SND_SOC_WM8350
tristate
+config SND_SOC_WM8400
+ tristate
+
config SND_SOC_WM8510
tristate
config SND_SOC_WM8990
tristate
+config SND_SOC_WM9705
+ tristate
+
config SND_SOC_WM9712
tristate
snd-soc-ac97-objs := ac97.o
snd-soc-ad1980-objs := ad1980.o
snd-soc-ad73311-objs := ad73311.o
+snd-soc-ak4104-objs := ak4104.o
snd-soc-ak4535-objs := ak4535.o
snd-soc-cs4270-objs := cs4270.o
snd-soc-l3-objs := l3.o
snd-soc-uda134x-objs := uda134x.o
snd-soc-uda1380-objs := uda1380.o
snd-soc-wm8350-objs := wm8350.o
+snd-soc-wm8400-objs := wm8400.o
snd-soc-wm8510-objs := wm8510.o
snd-soc-wm8580-objs := wm8580.o
snd-soc-wm8728-objs := wm8728.o
snd-soc-wm8903-objs := wm8903.o
snd-soc-wm8971-objs := wm8971.o
snd-soc-wm8990-objs := wm8990.o
+snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o
obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o
obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o
+obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o
obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o
obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o
obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o
obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o
+obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o
obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o
obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o
obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o
obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o
obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o
obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o
+obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o
+obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o
obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o
obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int reg = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ?
AC97_PCM_FRONT_DAC_RATE : AC97_PCM_LR_ADC_RATE;
SNDRV_PCM_RATE_22050 | SNDRV_PCM_RATE_44100 |\
SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops ac97_dai_ops = {
+ .prepare = ac97_prepare,
+};
+
struct snd_soc_dai ac97_dai = {
.name = "AC97 HiFi",
.ac97_control = 1,
.channels_max = 2,
.rates = STD_AC97_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = {
- .prepare = ac97_prepare,},
+ .ops = &ac97_dai_ops,
};
EXPORT_SYMBOL_GPL(ac97_dai);
printk(KERN_INFO "AC97 SoC Audio Codec %s\n", AC97_VERSION);
- socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (!socdev->codec)
+ socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (!socdev->card->codec)
return -ENOMEM;
- codec = socdev->codec;
+ codec = socdev->card->codec;
mutex_init(&codec->mutex);
codec->name = "AC97";
snd_soc_free_pcms(socdev);
err:
- kfree(socdev->codec->reg_cache);
- kfree(socdev->codec);
- socdev->codec = NULL;
+ kfree(socdev->card->codec);
+ socdev->card->codec = NULL;
return ret;
}
static int ac97_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (!codec)
return 0;
snd_soc_free_pcms(socdev);
- kfree(socdev->codec->reg_cache);
- kfree(socdev->codec);
+ kfree(socdev->card->codec);
return 0;
}
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- snd_ac97_suspend(socdev->codec->ac97);
+ snd_ac97_suspend(socdev->card->codec->ac97);
return 0;
}
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- snd_ac97_resume(socdev->codec->ac97);
+ snd_ac97_resume(socdev->card->codec->ac97);
return 0;
}
SOC_SINGLE("Mic Boost Switch", AC97_MIC, 6, 1, 0),
};
-/* add non dapm controls */
-static int ad1980_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(ad1980_snd_ac97_controls); i++) {
- err = snd_ctl_add(codec->card, snd_soc_cnew(
- &ad1980_snd_ac97_controls[i], codec, NULL));
- if (err < 0)
- return err;
- }
- return 0;
-}
-
static unsigned int ac97_read(struct snd_soc_codec *codec,
unsigned int reg)
{
default:
reg = reg >> 1;
- if (reg >= (ARRAY_SIZE(ad1980_reg)))
+ if (reg >= ARRAY_SIZE(ad1980_reg))
return -EINVAL;
return cache[reg];
soc_ac97_ops.write(codec->ac97, reg, val);
reg = reg >> 1;
- if (reg < (ARRAY_SIZE(ad1980_reg)))
+ if (reg < ARRAY_SIZE(ad1980_reg))
cache[reg] = val;
return 0;
printk(KERN_INFO "AD1980 SoC Audio Codec\n");
- socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (socdev->codec == NULL)
+ socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->card->codec == NULL)
return -ENOMEM;
- codec = socdev->codec;
+ codec = socdev->card->codec;
mutex_init(&codec->mutex);
codec->reg_cache =
ext_status = ac97_read(codec, AC97_EXTENDED_STATUS);
ac97_write(codec, AC97_EXTENDED_STATUS, ext_status&~0x3800);
- ad1980_add_controls(codec);
+ snd_soc_add_controls(codec, ad1980_snd_ac97_controls,
+ ARRAY_SIZE(ad1980_snd_ac97_controls));
ret = snd_soc_init_card(socdev);
if (ret < 0) {
printk(KERN_ERR "ad1980: failed to register card\n");
kfree(codec->reg_cache);
cache_err:
- kfree(socdev->codec);
- socdev->codec = NULL;
+ kfree(socdev->card->codec);
+ socdev->card->codec = NULL;
return ret;
}
static int ad1980_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (codec == NULL)
return 0;
codec->owner = THIS_MODULE;
codec->dai = &ad73311_dai;
codec->num_dai = 1;
- socdev->codec = codec;
+ socdev->card->codec = codec;
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
register_err:
snd_soc_free_pcms(socdev);
pcm_err:
- kfree(socdev->codec);
- socdev->codec = NULL;
+ kfree(socdev->card->codec);
+ socdev->card->codec = NULL;
return ret;
}
static int ad73311_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (codec == NULL)
return 0;
#define REGD_IGS(x) (x & 0x7)
#define REGD_RMOD (1 << 3)
#define REGD_OGS(x) ((x & 0x7) << 4)
-#define REGD_MUTE (x << 7)
+#define REGD_MUTE (1 << 7)
/* Control register E */
#define CTRL_REG_E (4 << 8)
--- /dev/null
+/*
+ * AK4104 ALSA SoC (ASoC) driver
+ *
+ * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
+ *
+ * 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 <linux/module.h>
+#include <sound/core.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+#include <linux/spi/spi.h>
+#include <sound/asoundef.h>
+
+#include "ak4104.h"
+
+/* AK4104 registers addresses */
+#define AK4104_REG_CONTROL1 0x00
+#define AK4104_REG_RESERVED 0x01
+#define AK4104_REG_CONTROL2 0x02
+#define AK4104_REG_TX 0x03
+#define AK4104_REG_CHN_STATUS(x) ((x) + 0x04)
+#define AK4104_NUM_REGS 10
+
+#define AK4104_REG_MASK 0x1f
+#define AK4104_READ 0xc0
+#define AK4104_WRITE 0xe0
+#define AK4104_RESERVED_VAL 0x5b
+
+/* Bit masks for AK4104 registers */
+#define AK4104_CONTROL1_RSTN (1 << 0)
+#define AK4104_CONTROL1_PW (1 << 1)
+#define AK4104_CONTROL1_DIF0 (1 << 2)
+#define AK4104_CONTROL1_DIF1 (1 << 3)
+
+#define AK4104_CONTROL2_SEL0 (1 << 0)
+#define AK4104_CONTROL2_SEL1 (1 << 1)
+#define AK4104_CONTROL2_MODE (1 << 2)
+
+#define AK4104_TX_TXE (1 << 0)
+#define AK4104_TX_V (1 << 1)
+
+#define DRV_NAME "ak4104"
+
+struct ak4104_private {
+ struct snd_soc_codec codec;
+ u8 reg_cache[AK4104_NUM_REGS];
+};
+
+static int ak4104_fill_cache(struct snd_soc_codec *codec)
+{
+ int i;
+ u8 *reg_cache = codec->reg_cache;
+ struct spi_device *spi = codec->control_data;
+
+ for (i = 0; i < codec->reg_cache_size; i++) {
+ int ret = spi_w8r8(spi, i | AK4104_READ);
+ if (ret < 0) {
+ dev_err(&spi->dev, "SPI write failure\n");
+ return ret;
+ }
+
+ reg_cache[i] = ret;
+ }
+
+ return 0;
+}
+
+static unsigned int ak4104_read_reg_cache(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ u8 *reg_cache = codec->reg_cache;
+
+ if (reg >= codec->reg_cache_size)
+ return -EINVAL;
+
+ return reg_cache[reg];
+}
+
+static int ak4104_spi_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ u8 *cache = codec->reg_cache;
+ struct spi_device *spi = codec->control_data;
+
+ if (reg >= codec->reg_cache_size)
+ return -EINVAL;
+
+ reg &= AK4104_REG_MASK;
+ reg |= AK4104_WRITE;
+
+ /* only write to the hardware if value has changed */
+ if (cache[reg] != value) {
+ u8 tmp[2] = { reg, value };
+ if (spi_write(spi, tmp, sizeof(tmp))) {
+ dev_err(&spi->dev, "SPI write failed\n");
+ return -EIO;
+ }
+
+ cache[reg] = value;
+ }
+
+ return 0;
+}
+
+static int ak4104_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int format)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int val = 0;
+
+ val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+ if (val < 0)
+ return val;
+
+ val &= ~(AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1);
+
+ /* set DAI format */
+ switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_RIGHT_J:
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ val |= AK4104_CONTROL1_DIF0;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ val |= AK4104_CONTROL1_DIF0 | AK4104_CONTROL1_DIF1;
+ break;
+ default:
+ dev_err(codec->dev, "invalid dai format\n");
+ return -EINVAL;
+ }
+
+ /* This device can only be slave */
+ if ((format & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ return ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+}
+
+static int ak4104_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_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int val = 0;
+
+ /* set the IEC958 bits: consumer mode, no copyright bit */
+ val |= IEC958_AES0_CON_NOT_COPYRIGHT;
+ ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(0), val);
+
+ val = 0;
+
+ switch (params_rate(params)) {
+ case 44100:
+ val |= IEC958_AES3_CON_FS_44100;
+ break;
+ case 48000:
+ val |= IEC958_AES3_CON_FS_48000;
+ break;
+ case 32000:
+ val |= IEC958_AES3_CON_FS_32000;
+ break;
+ default:
+ dev_err(codec->dev, "unsupported sampling rate\n");
+ return -EINVAL;
+ }
+
+ return ak4104_spi_write(codec, AK4104_REG_CHN_STATUS(3), val);
+}
+
+static struct snd_soc_dai_ops ak4101_dai_ops = {
+ .hw_params = ak4104_hw_params,
+ .set_fmt = ak4104_set_dai_fmt,
+};
+
+struct snd_soc_dai ak4104_dai = {
+ .name = DRV_NAME,
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_32000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S24_LE
+ },
+ .ops = &ak4101_dai_ops,
+};
+
+static struct snd_soc_codec *ak4104_codec;
+
+static int ak4104_spi_probe(struct spi_device *spi)
+{
+ struct snd_soc_codec *codec;
+ struct ak4104_private *ak4104;
+ int ret, val;
+
+ spi->bits_per_word = 8;
+ spi->mode = SPI_MODE_0;
+ ret = spi_setup(spi);
+ if (ret < 0)
+ return ret;
+
+ ak4104 = kzalloc(sizeof(struct ak4104_private), GFP_KERNEL);
+ if (!ak4104) {
+ dev_err(&spi->dev, "could not allocate codec\n");
+ return -ENOMEM;
+ }
+
+ codec = &ak4104->codec;
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ codec->dev = &spi->dev;
+ codec->name = DRV_NAME;
+ codec->owner = THIS_MODULE;
+ codec->dai = &ak4104_dai;
+ codec->num_dai = 1;
+ codec->private_data = ak4104;
+ codec->control_data = spi;
+ codec->reg_cache = ak4104->reg_cache;
+ codec->reg_cache_size = AK4104_NUM_REGS;
+
+ /* read all regs and fill the cache */
+ ret = ak4104_fill_cache(codec);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to fill register cache\n");
+ return ret;
+ }
+
+ /* read the 'reserved' register - according to the datasheet, it
+ * should contain 0x5b. Not a good way to verify the presence of
+ * the device, but there is no hardware ID register. */
+ if (ak4104_read_reg_cache(codec, AK4104_REG_RESERVED) !=
+ AK4104_RESERVED_VAL) {
+ ret = -ENODEV;
+ goto error_free_codec;
+ }
+
+ /* set power-up and non-reset bits */
+ val = ak4104_read_reg_cache(codec, AK4104_REG_CONTROL1);
+ val |= AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN;
+ ret = ak4104_spi_write(codec, AK4104_REG_CONTROL1, val);
+ if (ret < 0)
+ goto error_free_codec;
+
+ /* enable transmitter */
+ val = ak4104_read_reg_cache(codec, AK4104_REG_TX);
+ val |= AK4104_TX_TXE;
+ ret = ak4104_spi_write(codec, AK4104_REG_TX, val);
+ if (ret < 0)
+ goto error_free_codec;
+
+ ak4104_codec = codec;
+ ret = snd_soc_register_dai(&ak4104_dai);
+ if (ret < 0) {
+ dev_err(&spi->dev, "failed to register DAI\n");
+ goto error_free_codec;
+ }
+
+ spi_set_drvdata(spi, ak4104);
+ dev_info(&spi->dev, "SPI device initialized\n");
+ return 0;
+
+error_free_codec:
+ kfree(ak4104);
+ ak4104_dai.dev = NULL;
+ return ret;
+}
+
+static int __devexit ak4104_spi_remove(struct spi_device *spi)
+{
+ int ret, val;
+ struct ak4104_private *ak4104 = spi_get_drvdata(spi);
+
+ val = ak4104_read_reg_cache(&ak4104->codec, AK4104_REG_CONTROL1);
+ if (val < 0)
+ return val;
+
+ /* clear power-up and non-reset bits */
+ val &= ~(AK4104_CONTROL1_PW | AK4104_CONTROL1_RSTN);
+ ret = ak4104_spi_write(&ak4104->codec, AK4104_REG_CONTROL1, val);
+ if (ret < 0)
+ return ret;
+
+ ak4104_codec = NULL;
+ kfree(ak4104);
+ return 0;
+}
+
+static int ak4104_probe(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = ak4104_codec;
+ int ret;
+
+ /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
+ socdev->card->codec = codec;
+
+ /* Register PCMs */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
+ }
+
+ /* Register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ snd_soc_free_pcms(socdev);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ak4104_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ snd_soc_free_pcms(socdev);
+ return 0;
+};
+
+struct snd_soc_codec_device soc_codec_device_ak4104 = {
+ .probe = ak4104_probe,
+ .remove = ak4104_remove
+};
+EXPORT_SYMBOL_GPL(soc_codec_device_ak4104);
+
+static struct spi_driver ak4104_spi_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = ak4104_spi_probe,
+ .remove = __devexit_p(ak4104_spi_remove),
+};
+
+static int __init ak4104_init(void)
+{
+ pr_info("Asahi Kasei AK4104 ALSA SoC Codec Driver\n");
+ return spi_register_driver(&ak4104_spi_driver);
+}
+module_init(ak4104_init);
+
+static void __exit ak4104_exit(void)
+{
+ spi_unregister_driver(&ak4104_spi_driver);
+}
+module_exit(ak4104_exit);
+
+MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_DESCRIPTION("Asahi Kasei AK4104 ALSA SoC driver");
+MODULE_LICENSE("GPL");
+
--- /dev/null
+#ifndef _AK4104_H
+#define _AK4104_H
+
+extern struct snd_soc_dai ak4104_dai;
+extern struct snd_soc_codec_device soc_codec_device_ak4104;
+
+#endif
SOC_SINGLE("Mic Sidetone Volume", AK4535_VOL, 4, 7, 0),
};
-/* add non dapm controls */
-static int ak4535_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(ak4535_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&ak4535_snd_controls[i], codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
/* Mono 1 Mixer */
static const struct snd_kcontrol_new ak4535_mono1_mixer_controls[] = {
SOC_DAPM_SINGLE("Mic Sidetone Switch", AK4535_SIG1, 4, 1, 0),
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct ak4535_priv *ak4535 = codec->private_data;
u8 mode2 = ak4535_read_reg_cache(codec, AK4535_MODE2) & ~(0x3 << 5);
int rate = params_rate(params), fs = 256;
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops ak4535_dai_ops = {
+ .hw_params = ak4535_hw_params,
+ .set_fmt = ak4535_set_dai_fmt,
+ .digital_mute = ak4535_mute,
+ .set_sysclk = ak4535_set_dai_sysclk,
+};
+
struct snd_soc_dai ak4535_dai = {
.name = "AK4535",
.playback = {
.channels_max = 2,
.rates = AK4535_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = {
- .hw_params = ak4535_hw_params,
- .set_fmt = ak4535_set_dai_fmt,
- .digital_mute = ak4535_mute,
- .set_sysclk = ak4535_set_dai_sysclk,
- },
+ .ops = &ak4535_dai_ops,
};
EXPORT_SYMBOL_GPL(ak4535_dai);
static int ak4535_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
static int ak4535_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
ak4535_sync(codec);
ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
ak4535_set_bias_level(codec, codec->suspend_bias_level);
*/
static int ak4535_init(struct snd_soc_device *socdev)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret = 0;
codec->name = "AK4535";
/* power on device */
ak4535_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- ak4535_add_controls(codec);
+ snd_soc_add_controls(codec, ak4535_snd_controls,
+ ARRAY_SIZE(ak4535_snd_controls));
ak4535_add_widgets(codec);
ret = snd_soc_init_card(socdev);
if (ret < 0) {
const struct i2c_device_id *id)
{
struct snd_soc_device *socdev = ak4535_socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret;
i2c_set_clientdata(i2c, codec);
}
codec->private_data = ak4535;
- socdev->codec = codec;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int ak4535_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (codec->control_data)
ak4535_set_bias_level(codec, SND_SOC_BIAS_OFF);
*
* Author: Timur Tabi <timur@freescale.com>
*
- * Copyright 2007 Freescale Semiconductor, Inc. This file is licensed under
- * the terms of the GNU General Public License version 2. This program
- * is licensed "as is" without any warranty of any kind, whether express
- * or implied.
+ * Copyright 2007-2009 Freescale Semiconductor, Inc. This file is licensed
+ * under the terms of the GNU General Public License version 2. This
+ * program is licensed "as is" without any warranty of any kind, whether
+ * express or implied.
*
* This is an ASoC device driver for the Cirrus Logic CS4270 codec.
*
* Current features/limitations:
*
- * 1) Software mode is supported. Stand-alone mode is automatically
- * selected if I2C is disabled or if a CS4270 is not found on the I2C
- * bus. However, stand-alone mode is only partially implemented because
- * there is no mechanism yet for this driver and the machine driver to
- * communicate the values of the M0, M1, MCLK1, and MCLK2 pins.
- * 2) Only I2C is supported, not SPI
- * 3) Only Master mode is supported, not Slave.
- * 4) The machine driver's 'startup' function must call
- * cs4270_set_dai_sysclk() with the value of MCLK.
- * 5) Only I2S and left-justified modes are supported
- * 6) Power management is not supported
- * 7) The only supported control is volume and hardware mute (if enabled)
+ * - Software mode is supported. Stand-alone mode is not supported.
+ * - Only I2C is supported, not SPI
+ * - Support for master and slave mode
+ * - The machine driver's 'startup' function must call
+ * cs4270_set_dai_sysclk() with the value of MCLK.
+ * - Only I2S and left-justified modes are supported
+ * - Power management is not supported
*/
#include <linux/module.h>
#include "cs4270.h"
-/* If I2C is defined, then we support software mode. However, if we're
- not compiled as module but I2C is, then we can't use I2C calls. */
-#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE))
-#define USE_I2C
-#endif
-
-/* Private data for the CS4270 */
-struct cs4270_private {
- unsigned int mclk; /* Input frequency of the MCLK pin */
- unsigned int mode; /* The mode (I2S or left-justified) */
-};
-
/*
* The codec isn't really big-endian or little-endian, since the I2S
* interface requires data to be sent serially with the MSbit first.
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE)
-#ifdef USE_I2C
-
/* CS4270 registers addresses */
#define CS4270_CHIPID 0x01 /* Chip ID */
#define CS4270_PWRCTL 0x02 /* Power Control */
#define CS4270_MUTE_DAC_A 0x01
#define CS4270_MUTE_DAC_B 0x02
-/*
- * Clock Ratio Selection for Master Mode with I2C enabled
+/* Private data for the CS4270 */
+struct cs4270_private {
+ struct snd_soc_codec codec;
+ u8 reg_cache[CS4270_NUMREGS];
+ unsigned int mclk; /* Input frequency of the MCLK pin */
+ unsigned int mode; /* The mode (I2S or left-justified) */
+ unsigned int slave_mode;
+};
+
+/**
+ * struct cs4270_mode_ratios - clock ratio tables
+ * @ratio: the ratio of MCLK to the sample rate
+ * @speed_mode: the Speed Mode bits to set in the Mode Control register for
+ * this ratio
+ * @mclk: the Ratio Select bits to set in the Mode Control register for this
+ * ratio
*
* The data for this chart is taken from Table 5 of the CS4270 reference
* manual.
* It is also used by cs4270_set_dai_sysclk() to tell ALSA which sampling
* rates the CS4270 currently supports.
*
- * Each element in this array corresponds to the ratios in mclk_ratios[].
- * These two arrays need to be in sync.
- *
- * 'speed_mode' is the corresponding bit pattern to be written to the
+ * @speed_mode is the corresponding bit pattern to be written to the
* MODE bits of the Mode Control Register
*
- * 'mclk' is the corresponding bit pattern to be wirten to the MCLK bits of
+ * @mclk is the corresponding bit pattern to be wirten to the MCLK bits of
* the Mode Control Register.
*
* In situations where a single ratio is represented by multiple speed
* modes, we favor the slowest speed. E.g, for a ratio of 128, we pick
* double-speed instead of quad-speed. However, the CS4270 errata states
- * that Divide-By-1.5 can cause failures, so we avoid that mode where
+ * that divide-By-1.5 can cause failures, so we avoid that mode where
* possible.
*
- * ERRATA: There is an errata for the CS4270 where divide-by-1.5 does not
- * work if VD = 3.3V. If this effects you, select the
+ * Errata: There is an errata for the CS4270 where divide-by-1.5 does not
+ * work if Vd is 3.3V. If this effects you, select the
* CONFIG_SND_SOC_CS4270_VD33_ERRATA Kconfig option, and the driver will
* never select any sample rates that require divide-by-1.5.
*/
-static struct {
+struct cs4270_mode_ratios {
unsigned int ratio;
u8 speed_mode;
u8 mclk;
-} cs4270_mode_ratios[] = {
+};
+
+static struct cs4270_mode_ratios cs4270_mode_ratios[] = {
{64, CS4270_MODE_4X, CS4270_MODE_DIV1},
#ifndef CONFIG_SND_SOC_CS4270_VD33_ERRATA
{96, CS4270_MODE_4X, CS4270_MODE_DIV15},
/* The number of MCLK/LRCK ratios supported by the CS4270 */
#define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios)
-/*
- * Determine the CS4270 samples rates.
+/**
+ * cs4270_set_dai_sysclk - determine the CS4270 samples rates.
+ * @codec_dai: the codec DAI
+ * @clk_id: the clock ID (ignored)
+ * @freq: the MCLK input frequency
+ * @dir: the clock direction (ignored)
*
- * 'freq' is the input frequency to MCLK. The other parameters are ignored.
+ * This function is used to tell the codec driver what the input MCLK
+ * frequency is.
*
* The value of MCLK is used to determine which sample rates are supported
* by the CS4270. The ratio of MCLK / Fs must be equal to one of nine
- * support values: 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
+ * supported values - 64, 96, 128, 192, 256, 384, 512, 768, and 1024.
*
* This function calculates the nine ratios and determines which ones match
* a standard sample rate. If there's a match, then it is added to the list
- * of support sample rates.
+ * of supported sample rates.
*
* This function must be called by the machine driver's 'startup' function,
* otherwise the list of supported sample rates will not be available in
* time for ALSA.
- *
- * Note that in stand-alone mode, the sample rate is determined by input
- * pins M0, M1, MDIV1, and MDIV2. Also in stand-alone mode, divide-by-3
- * is not a programmable option. However, divide-by-3 is not an available
- * option in stand-alone mode. This cases two problems: a ratio of 768 is
- * not available (it requires divide-by-3) and B) ratios 192 and 384 can
- * only be selected with divide-by-1.5, but there is an errate that make
- * this selection difficult.
- *
- * In addition, there is no mechanism for communicating with the machine
- * driver what the input settings can be. This would need to be implemented
- * for stand-alone mode to work.
*/
static int cs4270_set_dai_sysclk(struct snd_soc_dai *codec_dai,
int clk_id, unsigned int freq, int dir)
rates &= ~SNDRV_PCM_RATE_KNOT;
if (!rates) {
- printk(KERN_ERR "cs4270: could not find a valid sample rate\n");
+ dev_err(codec->dev, "could not find a valid sample rate\n");
return -EINVAL;
}
return 0;
}
-/*
- * Configure the codec for the selected audio format
+/**
+ * cs4270_set_dai_fmt - configure the codec for the selected audio format
+ * @codec_dai: the codec DAI
+ * @format: a SND_SOC_DAIFMT_x value indicating the data format
*
* This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the
* codec accordingly.
struct cs4270_private *cs4270 = codec->private_data;
int ret = 0;
+ /* set DAI format */
switch (format & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
case SND_SOC_DAIFMT_LEFT_J:
cs4270->mode = format & SND_SOC_DAIFMT_FORMAT_MASK;
break;
default:
- printk(KERN_ERR "cs4270: invalid DAI format\n");
+ dev_err(codec->dev, "invalid dai format\n");
+ ret = -EINVAL;
+ }
+
+ /* set master/slave audio interface */
+ switch (format & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ cs4270->slave_mode = 1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ cs4270->slave_mode = 0;
+ break;
+ default:
+ /* all other modes are unsupported by the hardware */
ret = -EINVAL;
}
return ret;
}
-/*
- * A list of addresses on which this CS4270 could use. I2C addresses are
- * 7 bits. For the CS4270, the upper four bits are always 1001, and the
- * lower three bits are determined via the AD2, AD1, and AD0 pins
- * (respectively).
- */
-static const unsigned short normal_i2c[] = {
- 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, I2C_CLIENT_END
-};
-I2C_CLIENT_INSMOD;
-
-/*
- * Pre-fill the CS4270 register cache.
+/**
+ * cs4270_fill_cache - pre-fill the CS4270 register cache.
+ * @codec: the codec for this CS4270
+ *
+ * This function fills in the CS4270 register cache by reading the register
+ * values from the hardware.
+ *
+ * This CS4270 registers are cached to avoid excessive I2C I/O operations.
+ * After the initial read to pre-fill the cache, the CS4270 never updates
+ * the register values, so we won't have a cache coherency problem.
*
* We use the auto-increment feature of the CS4270 to read all registers in
* one shot.
CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache);
if (length != CS4270_NUMREGS) {
- printk(KERN_ERR "cs4270: I2C read failure, addr=0x%x\n",
+ dev_err(codec->dev, "i2c read failure, addr=0x%x\n",
i2c_client->addr);
return -EIO;
}
return 0;
}
-/*
- * Read from the CS4270 register cache.
+/**
+ * cs4270_read_reg_cache - read from the CS4270 register cache.
+ * @codec: the codec for this CS4270
+ * @reg: the register to read
+ *
+ * This function returns the value for a given register. It reads only from
+ * the register cache, not the hardware itself.
*
* This CS4270 registers are cached to avoid excessive I2C I/O operations.
* After the initial read to pre-fill the cache, the CS4270 never updates
- * the register values, so we won't have a cache coherncy problem.
+ * the register values, so we won't have a cache coherency problem.
*/
static unsigned int cs4270_read_reg_cache(struct snd_soc_codec *codec,
unsigned int reg)
return cache[reg - CS4270_FIRSTREG];
}
-/*
- * Write to a CS4270 register via the I2C bus.
+/**
+ * cs4270_i2c_write - write to a CS4270 register via the I2C bus.
+ * @codec: the codec for this CS4270
+ * @reg: the register to write
+ * @value: the value to write to the register
*
* This function writes the given value to the given CS4270 register, and
* also updates the register cache.
if (cache[reg - CS4270_FIRSTREG] != value) {
struct i2c_client *client = codec->control_data;
if (i2c_smbus_write_byte_data(client, reg, value)) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
+ dev_err(codec->dev, "i2c write failed\n");
return -EIO;
}
return 0;
}
-/*
- * Program the CS4270 with the given hardware parameters.
+/**
+ * cs4270_hw_params - program the CS4270 with the given hardware parameters.
+ * @substream: the audio stream
+ * @params: the hardware parameters to set
+ * @dai: the SOC DAI (ignored)
+ *
+ * This function programs the hardware with the values provided.
+ * Specifically, the sample rate and the data format.
*
- * The .ops functions are used to provide board-specific data, like
- * input frequencies, to this driver. This function takes that information,
+ * The .ops functions are used to provide board-specific data, like input
+ * frequencies, to this driver. This function takes that information,
* combines it with the hardware parameters provided, and programs the
* hardware accordingly.
*/
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct cs4270_private *cs4270 = codec->private_data;
int ret;
unsigned int i;
if (i == NUM_MCLK_RATIOS) {
/* We did not find a matching ratio */
- printk(KERN_ERR "cs4270: could not find matching ratio\n");
+ dev_err(codec->dev, "could not find matching ratio\n");
return -EINVAL;
}
- /* Freeze and power-down the codec */
-
- ret = snd_soc_write(codec, CS4270_PWRCTL, CS4270_PWRCTL_FREEZE |
- CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC |
- CS4270_PWRCTL_PDN);
- if (ret < 0) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
- return ret;
- }
-
- /* Program the mode control register */
+ /* Set the sample rate */
reg = snd_soc_read(codec, CS4270_MODE);
reg &= ~(CS4270_MODE_SPEED_MASK | CS4270_MODE_DIV_MASK);
- reg |= cs4270_mode_ratios[i].speed_mode | cs4270_mode_ratios[i].mclk;
+ reg |= cs4270_mode_ratios[i].mclk;
+
+ if (cs4270->slave_mode)
+ reg |= CS4270_MODE_SLAVE;
+ else
+ reg |= cs4270_mode_ratios[i].speed_mode;
ret = snd_soc_write(codec, CS4270_MODE, reg);
if (ret < 0) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
+ dev_err(codec->dev, "i2c write failed\n");
return ret;
}
- /* Program the format register */
+ /* Set the DAI format */
reg = snd_soc_read(codec, CS4270_FORMAT);
reg &= ~(CS4270_FORMAT_DAC_MASK | CS4270_FORMAT_ADC_MASK);
reg |= CS4270_FORMAT_DAC_LJ | CS4270_FORMAT_ADC_LJ;
break;
default:
- printk(KERN_ERR "cs4270: unknown format\n");
+ dev_err(codec->dev, "unknown dai format\n");
return -EINVAL;
}
ret = snd_soc_write(codec, CS4270_FORMAT, reg);
if (ret < 0) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
- return ret;
- }
-
- /* Disable auto-mute. This feature appears to be buggy, because in
- some situations, auto-mute will not deactivate when it should. */
-
- reg = snd_soc_read(codec, CS4270_MUTE);
- reg &= ~CS4270_MUTE_AUTO;
- ret = snd_soc_write(codec, CS4270_MUTE, reg);
- if (ret < 0) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
- return ret;
- }
-
- /* Disable automatic volume control. It's enabled by default, and
- * it causes volume change commands to be delayed, sometimes until
- * after playback has started.
- */
-
- reg = cs4270_read_reg_cache(codec, CS4270_TRANS);
- reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO);
- ret = cs4270_i2c_write(codec, CS4270_TRANS, reg);
- if (ret < 0) {
- printk(KERN_ERR "I2C write failed\n");
- return ret;
- }
-
- /* Thaw and power-up the codec */
-
- ret = snd_soc_write(codec, CS4270_PWRCTL, 0);
- if (ret < 0) {
- printk(KERN_ERR "cs4270: I2C write failed\n");
+ dev_err(codec->dev, "i2c write failed\n");
return ret;
}
return ret;
}
-#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
-
-/*
- * Set the CS4270 external mute
+/**
+ * cs4270_mute - enable/disable the CS4270 external mute
+ * @dai: the SOC DAI
+ * @mute: 0 = disable mute, 1 = enable mute
*
* This function toggles the mute bits in the MUTE register. The CS4270's
* mute capability is intended for external muting circuitry, so if the
reg6 = snd_soc_read(codec, CS4270_MUTE);
if (mute)
- reg6 |= CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
- CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
+ reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B;
else
- reg6 &= ~(CS4270_MUTE_ADC_A | CS4270_MUTE_ADC_B |
- CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
+ reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B);
return snd_soc_write(codec, CS4270_MUTE, reg6);
}
-#endif
-
-static int cs4270_i2c_probe(struct i2c_client *, const struct i2c_device_id *);
-
/* A list of non-DAPM controls that the CS4270 supports */
static const struct snd_kcontrol_new cs4270_snd_controls[] = {
SOC_DOUBLE_R("Master Playback Volume",
- CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1)
-};
-
-static const struct i2c_device_id cs4270_id[] = {
- {"cs4270", 0},
- {}
-};
-MODULE_DEVICE_TABLE(i2c, cs4270_id);
-
-static struct i2c_driver cs4270_i2c_driver = {
- .driver = {
- .name = "CS4270 I2C",
- .owner = THIS_MODULE,
- },
- .id_table = cs4270_id,
- .probe = cs4270_i2c_probe,
+ CS4270_VOLA, CS4270_VOLB, 0, 0xFF, 1),
+ SOC_SINGLE("Digital Sidetone Switch", CS4270_FORMAT, 5, 1, 0),
+ SOC_SINGLE("Soft Ramp Switch", CS4270_TRANS, 6, 1, 0),
+ SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0),
+ SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1),
+ SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0),
+ SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0)
};
/*
- * Global variable to store socdev for i2c probe function.
+ * cs4270_codec - global variable to store codec for the ASoC probe function
*
* If struct i2c_driver had a private_data field, we wouldn't need to use
- * cs4270_socdec. This is the only way to pass the socdev structure to
- * cs4270_i2c_probe().
- *
- * The real solution to cs4270_socdev is to create a mechanism
- * that maps I2C addresses to snd_soc_device structures. Perhaps the
- * creation of the snd_soc_device object should be moved out of
- * cs4270_probe() and into cs4270_i2c_probe(), but that would make this
- * driver dependent on I2C. The CS4270 supports "stand-alone" mode, whereby
- * the chip is *not* connected to the I2C bus, but is instead configured via
- * input pins.
+ * cs4270_codec. This is the only way to pass the codec structure from
+ * cs4270_i2c_probe() to cs4270_probe(). Unfortunately, there is no good
+ * way to synchronize these two functions. cs4270_i2c_probe() can be called
+ * multiple times before cs4270_probe() is called even once. So for now, we
+ * also only allow cs4270_i2c_probe() to be run once. That means that we do
+ * not support more than one cs4270 device in the system, at least for now.
*/
-static struct snd_soc_device *cs4270_socdev;
+static struct snd_soc_codec *cs4270_codec;
-/*
- * Initialize the I2C interface of the CS4270
- *
- * This function is called for whenever the I2C subsystem finds a device
- * at a particular address.
+static struct snd_soc_dai_ops cs4270_dai_ops = {
+ .hw_params = cs4270_hw_params,
+ .set_sysclk = cs4270_set_dai_sysclk,
+ .set_fmt = cs4270_set_dai_fmt,
+ .digital_mute = cs4270_mute,
+};
+
+struct snd_soc_dai cs4270_dai = {
+ .name = "cs4270",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = 0,
+ .formats = CS4270_FORMATS,
+ },
+ .ops = &cs4270_dai_ops,
+};
+EXPORT_SYMBOL_GPL(cs4270_dai);
+
+/**
+ * cs4270_probe - ASoC probe function
+ * @pdev: platform device
*
- * Note: snd_soc_new_pcms() must be called before this function can be called,
- * because of snd_ctl_add().
+ * This function is called when ASoC has all the pieces it needs to
+ * instantiate a sound driver.
*/
-static int cs4270_i2c_probe(struct i2c_client *i2c_client,
- const struct i2c_device_id *id)
+static int cs4270_probe(struct platform_device *pdev)
{
- struct snd_soc_device *socdev = cs4270_socdev;
- struct snd_soc_codec *codec = socdev->codec;
- int i;
- int ret = 0;
-
- /* Probing all possible addresses has one drawback: if there are
- multiple CS4270s on the bus, then you cannot specify which
- socdev is matched with which CS4270. For now, we just reject
- this I2C device if the socdev already has one attached. */
- if (codec->control_data)
- return -ENODEV;
-
- /* Note: codec_dai->codec is NULL here */
-
- codec->reg_cache = kzalloc(CS4270_NUMREGS, GFP_KERNEL);
- if (!codec->reg_cache) {
- printk(KERN_ERR "cs4270: could not allocate register cache\n");
- ret = -ENOMEM;
- goto error;
- }
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = cs4270_codec;
+ int ret;
- /* Verify that we have a CS4270 */
+ /* Connect the codec to the socdev. snd_soc_new_pcms() needs this. */
+ socdev->card->codec = codec;
- ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+ /* Register PCMs */
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
- printk(KERN_ERR "cs4270: failed to read I2C\n");
- goto error;
- }
- /* The top four bits of the chip ID should be 1100. */
- if ((ret & 0xF0) != 0xC0) {
- /* The device at this address is not a CS4270 codec */
- ret = -ENODEV;
- goto error;
+ dev_err(codec->dev, "failed to create pcms\n");
+ return ret;
}
- printk(KERN_INFO "cs4270: found device at I2C address %X\n",
- i2c_client->addr);
- printk(KERN_INFO "cs4270: hardware revision %X\n", ret & 0xF);
-
- codec->control_data = i2c_client;
- codec->read = cs4270_read_reg_cache;
- codec->write = cs4270_i2c_write;
- codec->reg_cache_size = CS4270_NUMREGS;
-
- /* The I2C interface is set up, so pre-fill our register cache */
-
- ret = cs4270_fill_cache(codec);
+ /* Add the non-DAPM controls */
+ ret = snd_soc_add_controls(codec, cs4270_snd_controls,
+ ARRAY_SIZE(cs4270_snd_controls));
if (ret < 0) {
- printk(KERN_ERR "cs4270: failed to fill register cache\n");
- goto error;
+ dev_err(codec->dev, "failed to add controls\n");
+ goto error_free_pcms;
}
- /* Add the non-DAPM controls */
-
- for (i = 0; i < ARRAY_SIZE(cs4270_snd_controls); i++) {
- struct snd_kcontrol *kctrl =
- snd_soc_cnew(&cs4270_snd_controls[i], codec, NULL);
-
- ret = snd_ctl_add(codec->card, kctrl);
- if (ret < 0)
- goto error;
+ /* And finally, register the socdev */
+ ret = snd_soc_init_card(socdev);
+ if (ret < 0) {
+ dev_err(codec->dev, "failed to register card\n");
+ goto error_free_pcms;
}
- i2c_set_clientdata(i2c_client, codec);
-
return 0;
-error:
- codec->control_data = NULL;
-
- kfree(codec->reg_cache);
- codec->reg_cache = NULL;
- codec->reg_cache_size = 0;
+error_free_pcms:
+ snd_soc_free_pcms(socdev);
return ret;
}
-#endif /* USE_I2C*/
+/**
+ * cs4270_remove - ASoC remove function
+ * @pdev: platform device
+ *
+ * This function is the counterpart to cs4270_probe().
+ */
+static int cs4270_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-struct snd_soc_dai cs4270_dai = {
- .name = "CS4270",
- .playback = {
- .stream_name = "Playback",
- .channels_min = 1,
- .channels_max = 2,
- .rates = 0,
- .formats = CS4270_FORMATS,
- },
- .capture = {
- .stream_name = "Capture",
- .channels_min = 1,
- .channels_max = 2,
- .rates = 0,
- .formats = CS4270_FORMATS,
- },
+ snd_soc_free_pcms(socdev);
+
+ return 0;
};
-EXPORT_SYMBOL_GPL(cs4270_dai);
-/*
- * ASoC probe function
+/**
+ * cs4270_i2c_probe - initialize the I2C interface of the CS4270
+ * @i2c_client: the I2C client object
+ * @id: the I2C device ID (ignored)
*
- * This function is called when the machine driver calls
- * platform_device_add().
+ * This function is called whenever the I2C subsystem finds a device that
+ * matches the device ID given via a prior call to i2c_add_driver().
*/
-static int cs4270_probe(struct platform_device *pdev)
+static int cs4270_i2c_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
- int ret = 0;
+ struct cs4270_private *cs4270;
+ unsigned int reg;
+ int ret;
- printk(KERN_INFO "CS4270 ALSA SoC Codec\n");
+ /* For now, we only support one cs4270 device in the system. See the
+ * comment for cs4270_codec.
+ */
+ if (cs4270_codec) {
+ dev_err(&i2c_client->dev, "ignoring CS4270 at addr %X\n",
+ i2c_client->addr);
+ dev_err(&i2c_client->dev, "only one per board allowed\n");
+ /* Should we return something other than ENODEV here? */
+ return -ENODEV;
+ }
+
+ /* Verify that we have a CS4270 */
+
+ ret = i2c_smbus_read_byte_data(i2c_client, CS4270_CHIPID);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to read i2c at addr %X\n",
+ i2c_client->addr);
+ return ret;
+ }
+ /* The top four bits of the chip ID should be 1100. */
+ if ((ret & 0xF0) != 0xC0) {
+ dev_err(&i2c_client->dev, "device at addr %X is not a CS4270\n",
+ i2c_client->addr);
+ return -ENODEV;
+ }
+
+ dev_info(&i2c_client->dev, "found device at i2c address %X\n",
+ i2c_client->addr);
+ dev_info(&i2c_client->dev, "hardware revision %X\n", ret & 0xF);
/* Allocate enough space for the snd_soc_codec structure
and our private data together. */
- codec = kzalloc(ALIGN(sizeof(struct snd_soc_codec), 4) +
- sizeof(struct cs4270_private), GFP_KERNEL);
- if (!codec) {
- printk(KERN_ERR "cs4270: Could not allocate codec structure\n");
+ cs4270 = kzalloc(sizeof(struct cs4270_private), GFP_KERNEL);
+ if (!cs4270) {
+ dev_err(&i2c_client->dev, "could not allocate codec\n");
return -ENOMEM;
}
+ codec = &cs4270->codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
+ codec->dev = &i2c_client->dev;
codec->name = "CS4270";
codec->owner = THIS_MODULE;
codec->dai = &cs4270_dai;
codec->num_dai = 1;
- codec->private_data = (void *) codec +
- ALIGN(sizeof(struct snd_soc_codec), 4);
-
- socdev->codec = codec;
+ codec->private_data = cs4270;
+ codec->control_data = i2c_client;
+ codec->read = cs4270_read_reg_cache;
+ codec->write = cs4270_i2c_write;
+ codec->reg_cache = cs4270->reg_cache;
+ codec->reg_cache_size = CS4270_NUMREGS;
- /* Register PCMs */
+ /* The I2C interface is set up, so pre-fill our register cache */
- ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ ret = cs4270_fill_cache(codec);
if (ret < 0) {
- printk(KERN_ERR "cs4270: failed to create PCMs\n");
+ dev_err(&i2c_client->dev, "failed to fill register cache\n");
goto error_free_codec;
}
-#ifdef USE_I2C
- cs4270_socdev = socdev;
+ /* Disable auto-mute. This feature appears to be buggy. In some
+ * situations, auto-mute will not deactivate when it should, so we want
+ * this feature disabled by default. An application (e.g. alsactl) can
+ * re-enabled it by using the controls.
+ */
- ret = i2c_add_driver(&cs4270_i2c_driver);
- if (ret) {
- printk(KERN_ERR "cs4270: failed to attach driver");
- goto error_free_pcms;
+ reg = cs4270_read_reg_cache(codec, CS4270_MUTE);
+ reg &= ~CS4270_MUTE_AUTO;
+ ret = cs4270_i2c_write(codec, CS4270_MUTE, reg);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "i2c write failed\n");
+ return ret;
}
- /* Did we find a CS4270 on the I2C bus? */
- if (codec->control_data) {
- /* Initialize codec ops */
- cs4270_dai.ops.hw_params = cs4270_hw_params;
- cs4270_dai.ops.set_sysclk = cs4270_set_dai_sysclk;
- cs4270_dai.ops.set_fmt = cs4270_set_dai_fmt;
-#ifdef CONFIG_SND_SOC_CS4270_HWMUTE
- cs4270_dai.ops.digital_mute = cs4270_mute;
-#endif
- } else
- printk(KERN_INFO "cs4270: no I2C device found, "
- "using stand-alone mode\n");
-#else
- printk(KERN_INFO "cs4270: I2C disabled, using stand-alone mode\n");
-#endif
+ /* Disable automatic volume control. The hardware enables, and it
+ * causes volume change commands to be delayed, sometimes until after
+ * playback has started. An application (e.g. alsactl) can
+ * re-enabled it by using the controls.
+ */
- ret = snd_soc_init_card(socdev);
+ reg = cs4270_read_reg_cache(codec, CS4270_TRANS);
+ reg &= ~(CS4270_TRANS_SOFT | CS4270_TRANS_ZERO);
+ ret = cs4270_i2c_write(codec, CS4270_TRANS, reg);
if (ret < 0) {
- printk(KERN_ERR "cs4270: failed to register card\n");
- goto error_del_driver;
+ dev_err(&i2c_client->dev, "i2c write failed\n");
+ return ret;
}
- return 0;
+ /* Initialize the DAI. Normally, we'd prefer to have a kmalloc'd DAI
+ * structure for each CS4270 device, but the machine driver needs to
+ * have a pointer to the DAI structure, so for now it must be a global
+ * variable.
+ */
+ cs4270_dai.dev = &i2c_client->dev;
-error_del_driver:
-#ifdef USE_I2C
- i2c_del_driver(&cs4270_i2c_driver);
+ /* Register the DAI. If all the other ASoC driver have already
+ * registered, then this will call our probe function, so
+ * cs4270_codec needs to be ready.
+ */
+ cs4270_codec = codec;
+ ret = snd_soc_register_dai(&cs4270_dai);
+ if (ret < 0) {
+ dev_err(&i2c_client->dev, "failed to register DAIe\n");
+ goto error_free_codec;
+ }
-error_free_pcms:
-#endif
- snd_soc_free_pcms(socdev);
+ i2c_set_clientdata(i2c_client, cs4270);
+
+ return 0;
error_free_codec:
- kfree(socdev->codec);
- socdev->codec = NULL;
+ kfree(cs4270);
+ cs4270_codec = NULL;
+ cs4270_dai.dev = NULL;
return ret;
}
-static int cs4270_remove(struct platform_device *pdev)
+/**
+ * cs4270_i2c_remove - remove an I2C device
+ * @i2c_client: the I2C client object
+ *
+ * This function is the counterpart to cs4270_i2c_probe().
+ */
+static int cs4270_i2c_remove(struct i2c_client *i2c_client)
{
- struct snd_soc_device *socdev = platform_get_drvdata(pdev);
-
- snd_soc_free_pcms(socdev);
-
-#ifdef USE_I2C
- i2c_del_driver(&cs4270_i2c_driver);
-#endif
+ struct cs4270_private *cs4270 = i2c_get_clientdata(i2c_client);
- kfree(socdev->codec);
- socdev->codec = NULL;
+ kfree(cs4270);
+ cs4270_codec = NULL;
+ cs4270_dai.dev = NULL;
return 0;
}
/*
+ * cs4270_id - I2C device IDs supported by this driver
+ */
+static struct i2c_device_id cs4270_id[] = {
+ {"cs4270", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, cs4270_id);
+
+/*
+ * cs4270_i2c_driver - I2C device identification
+ *
+ * This structure tells the I2C subsystem how to identify and support a
+ * given I2C device type.
+ */
+static struct i2c_driver cs4270_i2c_driver = {
+ .driver = {
+ .name = "cs4270",
+ .owner = THIS_MODULE,
+ },
+ .id_table = cs4270_id,
+ .probe = cs4270_i2c_probe,
+ .remove = cs4270_i2c_remove,
+};
+
+/*
* ASoC codec device structure
*
* Assign this variable to the codec_dev field of the machine driver's
static int __init cs4270_init(void)
{
- return snd_soc_register_dai(&cs4270_dai);
+ pr_info("Cirrus Logic CS4270 ALSA SoC Codec Driver\n");
+
+ return i2c_add_driver(&cs4270_i2c_driver);
}
module_init(cs4270_init);
static void __exit cs4270_exit(void)
{
- snd_soc_unregister_dai(&cs4270_dai);
+ i2c_del_driver(&cs4270_i2c_driver);
}
module_exit(cs4270_exit);
printk(KERN_INFO "PCM3008 SoC Audio Codec %s\n", PCM3008_VERSION);
- socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (!socdev->codec)
+ socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (!socdev->card->codec)
return -ENOMEM;
- codec = socdev->codec;
+ codec = socdev->card->codec;
mutex_init(&codec->mutex);
codec->name = "PCM3008";
card_err:
snd_soc_free_pcms(socdev);
pcm_err:
- kfree(socdev->codec);
+ kfree(socdev->card->codec);
return ret;
}
static int pcm3008_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct pcm3008_setup_data *setup = socdev->codec_data;
if (!codec)
pcm3008_gpio_free(setup);
snd_soc_free_pcms(socdev);
- kfree(socdev->codec);
+ kfree(socdev->card->codec);
return 0;
}
SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]),
};
-/* add non dapm controls */
-static int ssm2602_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(ssm2602_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&ssm2602_snd_controls[i], codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
/* Output Mixer */
static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = {
SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0),
u16 srate;
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct ssm2602_priv *ssm2602 = codec->private_data;
struct i2c_client *i2c = codec->control_data;
u16 iface = ssm2602_read_reg_cache(codec, SSM2602_IFACE) & 0xfff3;
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct ssm2602_priv *ssm2602 = codec->private_data;
struct i2c_client *i2c = codec->control_data;
struct snd_pcm_runtime *master_runtime;
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
/* set active */
ssm2602_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC);
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct ssm2602_priv *ssm2602 = codec->private_data;
/* deactivate */
if (!codec->active)
#define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
+static struct snd_soc_dai_ops ssm2602_dai_ops = {
+ .startup = ssm2602_startup,
+ .prepare = ssm2602_pcm_prepare,
+ .hw_params = ssm2602_hw_params,
+ .shutdown = ssm2602_shutdown,
+ .digital_mute = ssm2602_mute,
+ .set_sysclk = ssm2602_set_dai_sysclk,
+ .set_fmt = ssm2602_set_dai_fmt,
+};
+
struct snd_soc_dai ssm2602_dai = {
.name = "SSM2602",
.playback = {
.channels_max = 2,
.rates = SSM2602_RATES,
.formats = SSM2602_FORMATS,},
- .ops = {
- .startup = ssm2602_startup,
- .prepare = ssm2602_pcm_prepare,
- .hw_params = ssm2602_hw_params,
- .shutdown = ssm2602_shutdown,
- .digital_mute = ssm2602_mute,
- .set_sysclk = ssm2602_set_dai_sysclk,
- .set_fmt = ssm2602_set_dai_fmt,
- }
+ .ops = &ssm2602_dai_ops,
};
EXPORT_SYMBOL_GPL(ssm2602_dai);
static int ssm2602_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
static int ssm2602_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int i;
u8 data[2];
u16 *cache = codec->reg_cache;
*/
static int ssm2602_init(struct snd_soc_device *socdev)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int reg, ret = 0;
codec->name = "SSM2602";
APANA_ENABLE_MIC_BOOST);
ssm2602_write(codec, SSM2602_PWR, 0);
- ssm2602_add_controls(codec);
+ snd_soc_add_controls(codec, ssm2602_snd_controls,
+ ARRAY_SIZE(ssm2602_snd_controls));
ssm2602_add_widgets(codec);
ret = snd_soc_init_card(socdev);
if (ret < 0) {
const struct i2c_device_id *id)
{
struct snd_soc_device *socdev = ssm2602_socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret;
i2c_set_clientdata(i2c, codec);
}
codec->private_data = ssm2602;
- socdev->codec = codec;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int ssm2602_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (codec->control_data)
ssm2602_set_bias_level(codec, SND_SOC_BIAS_OFF);
SOC_ENUM("Playback De-emphasis", tlv320aic23_deemph),
};
-/* add non dapm controls */
-static int tlv320aic23_add_controls(struct snd_soc_codec *codec)
-{
-
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(tlv320aic23_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&tlv320aic23_snd_controls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-
-}
-
/* PGA Mixer controls for Line and Mic switch */
static const struct snd_kcontrol_new tlv320aic23_output_mixer_controls[] = {
SOC_DAPM_SINGLE("Line Bypass Switch", TLV320AIC23_ANLG, 3, 1, 0),
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
u16 iface_reg;
int ret;
struct aic23 *aic23 = container_of(codec, struct aic23, codec);
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
/* set active */
tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0001);
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct aic23 *aic23 = container_of(codec, struct aic23, codec);
/* deactivate */
#define AIC23_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+static struct snd_soc_dai_ops tlv320aic23_dai_ops = {
+ .prepare = tlv320aic23_pcm_prepare,
+ .hw_params = tlv320aic23_hw_params,
+ .shutdown = tlv320aic23_shutdown,
+ .digital_mute = tlv320aic23_mute,
+ .set_fmt = tlv320aic23_set_dai_fmt,
+ .set_sysclk = tlv320aic23_set_dai_sysclk,
+};
+
struct snd_soc_dai tlv320aic23_dai = {
.name = "tlv320aic23",
.playback = {
.channels_max = 2,
.rates = AIC23_RATES,
.formats = AIC23_FORMATS,},
- .ops = {
- .prepare = tlv320aic23_pcm_prepare,
- .hw_params = tlv320aic23_hw_params,
- .shutdown = tlv320aic23_shutdown,
- .digital_mute = tlv320aic23_mute,
- .set_fmt = tlv320aic23_set_dai_fmt,
- .set_sysclk = tlv320aic23_set_dai_sysclk,
- }
+ .ops = &tlv320aic23_dai_ops,
};
EXPORT_SYMBOL_GPL(tlv320aic23_dai);
pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x0);
tlv320aic23_set_bias_level(codec, SND_SOC_BIAS_OFF);
static int tlv320aic23_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int i;
u16 reg;
*/
static int tlv320aic23_init(struct snd_soc_device *socdev)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret = 0;
u16 reg;
tlv320aic23_write(codec, TLV320AIC23_ACTIVE, 0x1);
- tlv320aic23_add_controls(codec);
+ snd_soc_add_controls(codec, tlv320aic23_snd_controls,
+ ARRAY_SIZE(tlv320aic23_snd_controls));
tlv320aic23_add_widgets(codec);
ret = snd_soc_init_card(socdev);
if (ret < 0) {
const struct i2c_device_id *i2c_id)
{
struct snd_soc_device *socdev = tlv320aic23_socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret;
if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
if (aic23 == NULL)
return -ENOMEM;
codec = &aic23->codec;
- socdev->codec = codec;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int tlv320aic23_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct aic23 *aic23 = container_of(codec, struct aic23, codec);
if (codec->control_data)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct aic26 *aic26 = codec->private_data;
int fsref, divisor, wlen, pval, jval, dval, qval;
u16 reg;
#define AIC26_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE |\
SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE)
+static struct snd_soc_dai_ops aic26_dai_ops = {
+ .hw_params = aic26_hw_params,
+ .digital_mute = aic26_mute,
+ .set_sysclk = aic26_set_sysclk,
+ .set_fmt = aic26_set_fmt,
+};
+
struct snd_soc_dai aic26_dai = {
.name = "tlv320aic26",
.playback = {
.rates = AIC26_RATES,
.formats = AIC26_FORMATS,
},
- .ops = {
- .hw_params = aic26_hw_params,
- .digital_mute = aic26_mute,
- .set_sysclk = aic26_set_sysclk,
- .set_fmt = aic26_set_fmt,
- },
+ .ops = &aic26_dai_ops,
};
EXPORT_SYMBOL_GPL(aic26_dai);
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
- struct snd_kcontrol *kcontrol;
struct aic26 *aic26;
- int i, ret, err;
+ int ret, err;
dev_info(&pdev->dev, "Probing AIC26 SoC CODEC driver\n");
dev_dbg(&pdev->dev, "socdev=%p\n", socdev);
return -ENODEV;
}
codec = &aic26->codec;
- socdev->codec = codec;
+ socdev->card->codec = codec;
dev_dbg(&pdev->dev, "Registering PCMs, dev=%p, socdev->dev=%p\n",
&pdev->dev, socdev->dev);
/* register controls */
dev_dbg(&pdev->dev, "Registering controls\n");
- for (i = 0; i < ARRAY_SIZE(aic26_snd_controls); i++) {
- kcontrol = snd_soc_cnew(&aic26_snd_controls[i], codec, NULL);
- err = snd_ctl_add(codec->card, kcontrol);
- WARN_ON(err < 0);
- }
+ err = snd_soc_add_controls(codec, aic26_snd_controls,
+ ARRAY_SIZE(aic26_snd_controls));
+ WARN_ON(err < 0);
/* CODEC is setup, we can register the card now */
dev_dbg(&pdev->dev, "Registering card\n");
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/initval.h>
+#include <sound/tlv.h>
#include "tlv320aic3x.h"
SOC_ENUM_DOUBLE(AIC3X_CODEC_DFILT_CTRL, 6, 4, 4, aic3x_adc_hpf),
};
+/*
+ * DAC digital volumes. From -63.5 to 0 dB in 0.5 dB steps
+ */
+static DECLARE_TLV_DB_SCALE(dac_tlv, -6350, 50, 0);
+/* ADC PGA gain volumes. From 0 to 59.5 dB in 0.5 dB steps */
+static DECLARE_TLV_DB_SCALE(adc_tlv, 0, 50, 0);
+/*
+ * Output stage volumes. From -78.3 to 0 dB. Muted below -78.3 dB.
+ * Step size is approximately 0.5 dB over most of the scale but increasing
+ * near the very low levels.
+ * Define dB scale so that it is mostly correct for range about -55 to 0 dB
+ * but having increasing dB difference below that (and where it doesn't count
+ * so much). This setting shows -50 dB (actual is -50.3 dB) for register
+ * value 100 and -58.5 dB (actual is -78.3 dB) for register value 117.
+ */
+static DECLARE_TLV_DB_SCALE(output_stage_tlv, -5900, 50, 1);
+
static const struct snd_kcontrol_new aic3x_snd_controls[] = {
/* Output */
- SOC_DOUBLE_R("PCM Playback Volume", LDAC_VOL, RDAC_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R_TLV("PCM Playback Volume",
+ LDAC_VOL, RDAC_VOL, 0, 0x7f, 1, dac_tlv),
- SOC_DOUBLE_R("Line DAC Playback Volume", DACL1_2_LLOPM_VOL,
- DACR1_2_RLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R_TLV("Line DAC Playback Volume",
+ DACL1_2_LLOPM_VOL, DACR1_2_RLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
SOC_SINGLE("LineL Playback Switch", LLOPM_CTRL, 3, 0x01, 0),
SOC_SINGLE("LineR Playback Switch", RLOPM_CTRL, 3, 0x01, 0),
- SOC_DOUBLE_R("LineL DAC Playback Volume", DACL1_2_LLOPM_VOL,
- DACR1_2_LLOPM_VOL, 0, 0x7f, 1),
- SOC_SINGLE("LineL Left PGA Bypass Playback Volume", PGAL_2_LLOPM_VOL,
- 0, 0x7f, 1),
- SOC_SINGLE("LineR Right PGA Bypass Playback Volume", PGAR_2_RLOPM_VOL,
- 0, 0x7f, 1),
- SOC_DOUBLE_R("LineL Line2 Bypass Playback Volume", LINE2L_2_LLOPM_VOL,
- LINE2R_2_LLOPM_VOL, 0, 0x7f, 1),
- SOC_DOUBLE_R("LineR Line2 Bypass Playback Volume", LINE2L_2_RLOPM_VOL,
- LINE2R_2_RLOPM_VOL, 0, 0x7f, 1),
-
- SOC_DOUBLE_R("Mono DAC Playback Volume", DACL1_2_MONOLOPM_VOL,
- DACR1_2_MONOLOPM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R_TLV("LineL DAC Playback Volume",
+ DACL1_2_LLOPM_VOL, DACR1_2_LLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("LineL Left PGA Bypass Playback Volume",
+ PGAL_2_LLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("LineR Right PGA Bypass Playback Volume",
+ PGAR_2_RLOPM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("LineL Line2 Bypass Playback Volume",
+ LINE2L_2_LLOPM_VOL, LINE2R_2_LLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("LineR Line2 Bypass Playback Volume",
+ LINE2L_2_RLOPM_VOL, LINE2R_2_RLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("Mono DAC Playback Volume",
+ DACL1_2_MONOLOPM_VOL, DACR1_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
SOC_SINGLE("Mono DAC Playback Switch", MONOLOPM_CTRL, 3, 0x01, 0),
- SOC_DOUBLE_R("Mono PGA Bypass Playback Volume", PGAL_2_MONOLOPM_VOL,
- PGAR_2_MONOLOPM_VOL, 0, 0x7f, 1),
- SOC_DOUBLE_R("Mono Line2 Bypass Playback Volume", LINE2L_2_MONOLOPM_VOL,
- LINE2R_2_MONOLOPM_VOL, 0, 0x7f, 1),
-
- SOC_DOUBLE_R("HP DAC Playback Volume", DACL1_2_HPLOUT_VOL,
- DACR1_2_HPROUT_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R_TLV("Mono PGA Bypass Playback Volume",
+ PGAL_2_MONOLOPM_VOL, PGAR_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("Mono Line2 Bypass Playback Volume",
+ LINE2L_2_MONOLOPM_VOL, LINE2R_2_MONOLOPM_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("HP DAC Playback Volume",
+ DACL1_2_HPLOUT_VOL, DACR1_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
SOC_DOUBLE_R("HP DAC Playback Switch", HPLOUT_CTRL, HPROUT_CTRL, 3,
0x01, 0),
- SOC_DOUBLE_R("HP Right PGA Bypass Playback Volume", PGAR_2_HPLOUT_VOL,
- PGAR_2_HPROUT_VOL, 0, 0x7f, 1),
- SOC_SINGLE("HPL PGA Bypass Playback Volume", PGAL_2_HPLOUT_VOL,
- 0, 0x7f, 1),
- SOC_SINGLE("HPR PGA Bypass Playback Volume", PGAL_2_HPROUT_VOL,
- 0, 0x7f, 1),
- SOC_DOUBLE_R("HP Line2 Bypass Playback Volume", LINE2L_2_HPLOUT_VOL,
- LINE2R_2_HPROUT_VOL, 0, 0x7f, 1),
-
- SOC_DOUBLE_R("HPCOM DAC Playback Volume", DACL1_2_HPLCOM_VOL,
- DACR1_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_DOUBLE_R_TLV("HP Right PGA Bypass Playback Volume",
+ PGAR_2_HPLOUT_VOL, PGAR_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("HPL PGA Bypass Playback Volume",
+ PGAL_2_HPLOUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("HPR PGA Bypass Playback Volume",
+ PGAL_2_HPROUT_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HP Line2 Bypass Playback Volume",
+ LINE2L_2_HPLOUT_VOL, LINE2R_2_HPROUT_VOL,
+ 0, 118, 1, output_stage_tlv),
+
+ SOC_DOUBLE_R_TLV("HPCOM DAC Playback Volume",
+ DACL1_2_HPLCOM_VOL, DACR1_2_HPRCOM_VOL,
+ 0, 118, 1, output_stage_tlv),
SOC_DOUBLE_R("HPCOM DAC Playback Switch", HPLCOM_CTRL, HPRCOM_CTRL, 3,
0x01, 0),
- SOC_SINGLE("HPLCOM PGA Bypass Playback Volume", PGAL_2_HPLCOM_VOL,
- 0, 0x7f, 1),
- SOC_SINGLE("HPRCOM PGA Bypass Playback Volume", PGAL_2_HPRCOM_VOL,
- 0, 0x7f, 1),
- SOC_DOUBLE_R("HPCOM Line2 Bypass Playback Volume", LINE2L_2_HPLCOM_VOL,
- LINE2R_2_HPRCOM_VOL, 0, 0x7f, 1),
+ SOC_SINGLE_TLV("HPLCOM PGA Bypass Playback Volume",
+ PGAL_2_HPLCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_SINGLE_TLV("HPRCOM PGA Bypass Playback Volume",
+ PGAL_2_HPRCOM_VOL, 0, 118, 1, output_stage_tlv),
+ SOC_DOUBLE_R_TLV("HPCOM Line2 Bypass Playback Volume",
+ LINE2L_2_HPLCOM_VOL, LINE2R_2_HPRCOM_VOL,
+ 0, 118, 1, output_stage_tlv),
/*
* Note: enable Automatic input Gain Controller with care. It can
SOC_DOUBLE_R("AGC Switch", LAGC_CTRL_A, RAGC_CTRL_A, 7, 0x01, 0),
/* Input */
- SOC_DOUBLE_R("PGA Capture Volume", LADC_VOL, RADC_VOL, 0, 0x7f, 0),
+ SOC_DOUBLE_R_TLV("PGA Capture Volume", LADC_VOL, RADC_VOL,
+ 0, 119, 0, adc_tlv),
SOC_DOUBLE_R("PGA Capture Switch", LADC_VOL, RADC_VOL, 7, 0x01, 1),
SOC_ENUM("ADC HPF Cut-off", aic3x_enum[ADC_HPF_ENUM]),
};
-/* add non dapm controls */
-static int aic3x_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(aic3x_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&aic3x_snd_controls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
/* Left DAC Mux */
static const struct snd_kcontrol_new aic3x_left_dac_mux_controls =
SOC_DAPM_ENUM("Route", aic3x_enum[LDAC_ENUM]);
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct aic3x_priv *aic3x = codec->private_data;
int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0;
u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1;
#define AIC3X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \
SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S32_LE)
+static struct snd_soc_dai_ops aic3x_dai_ops = {
+ .hw_params = aic3x_hw_params,
+ .digital_mute = aic3x_mute,
+ .set_sysclk = aic3x_set_dai_sysclk,
+ .set_fmt = aic3x_set_dai_fmt,
+};
+
struct snd_soc_dai aic3x_dai = {
.name = "tlv320aic3x",
.playback = {
.channels_max = 2,
.rates = AIC3X_RATES,
.formats = AIC3X_FORMATS,},
- .ops = {
- .hw_params = aic3x_hw_params,
- .digital_mute = aic3x_mute,
- .set_sysclk = aic3x_set_dai_sysclk,
- .set_fmt = aic3x_set_dai_fmt,
- }
+ .ops = &aic3x_dai_ops,
};
EXPORT_SYMBOL_GPL(aic3x_dai);
static int aic3x_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
aic3x_set_bias_level(codec, SND_SOC_BIAS_OFF);
static int aic3x_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int i;
u8 data[2];
u8 *cache = codec->reg_cache;
*/
static int aic3x_init(struct snd_soc_device *socdev)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct aic3x_setup_data *setup = socdev->codec_data;
int reg, ret = 0;
aic3x_write(codec, AIC3X_GPIO1_REG, (setup->gpio_func[0] & 0xf) << 4);
aic3x_write(codec, AIC3X_GPIO2_REG, (setup->gpio_func[1] & 0xf) << 4);
- aic3x_add_controls(codec);
+ snd_soc_add_controls(codec, aic3x_snd_controls,
+ ARRAY_SIZE(aic3x_snd_controls));
aic3x_add_widgets(codec);
ret = snd_soc_init_card(socdev);
if (ret < 0) {
const struct i2c_device_id *id)
{
struct snd_soc_device *socdev = aic3x_socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret;
i2c_set_clientdata(i2c, codec);
}
codec->private_data = aic3x;
- socdev->codec = codec;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int aic3x_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
/* power down chip */
if (codec->control_data)
*/
static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = {
0x00, /* this register not used */
- 0x93, /* REG_CODEC_MODE (0x1) */
+ 0x91, /* REG_CODEC_MODE (0x1) */
0xc3, /* REG_OPTION (0x2) */
0x00, /* REG_UNKNOWN (0x3) */
0x00, /* REG_MICBIAS_CTL (0x4) */
0x00, /* REG_MISC_SET_2 (0x49) */
};
+/* codec private data */
+struct twl4030_priv {
+ unsigned int bypass_state;
+ unsigned int codec_powered;
+ unsigned int codec_muted;
+};
+
/*
* read twl4030 register cache
*/
{
u8 *cache = codec->reg_cache;
+ if (reg >= TWL4030_CACHEREGNUM)
+ return -EIO;
+
return cache[reg];
}
return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg);
}
-static void twl4030_clear_codecpdz(struct snd_soc_codec *codec)
+static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable)
{
+ struct twl4030_priv *twl4030 = codec->private_data;
u8 mode;
- mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
- twl4030_write(codec, TWL4030_REG_CODEC_MODE,
- mode & ~TWL4030_CODECPDZ);
-
- /* REVISIT: this delay is present in TI sample drivers */
- /* but there seems to be no TRM requirement for it */
- udelay(10);
-}
-
-static void twl4030_set_codecpdz(struct snd_soc_codec *codec)
-{
- u8 mode;
+ if (enable == twl4030->codec_powered)
+ return;
mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE);
- twl4030_write(codec, TWL4030_REG_CODEC_MODE,
- mode | TWL4030_CODECPDZ);
+ if (enable)
+ mode |= TWL4030_CODECPDZ;
+ else
+ mode &= ~TWL4030_CODECPDZ;
+
+ twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
+ twl4030->codec_powered = enable;
/* REVISIT: this delay is present in TI sample drivers */
/* but there seems to be no TRM requirement for it */
int i;
/* clear CODECPDZ prior to setting register defaults */
- twl4030_clear_codecpdz(codec);
+ twl4030_codec_enable(codec, 0);
/* set all audio section registers to reasonable defaults */
for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++)
}
+static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute)
+{
+ struct twl4030_priv *twl4030 = codec->private_data;
+ u8 reg_val;
+
+ if (mute == twl4030->codec_muted)
+ return;
+
+ if (mute) {
+ /* Bypass the reg_cache and mute the volumes
+ * Headset mute is done in it's own event handler
+ * Things to mute: Earpiece, PreDrivL/R, CarkitL/R
+ */
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL);
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg_val & (~TWL4030_EAR_GAIN),
+ TWL4030_REG_EAR_CTL);
+
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL);
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg_val & (~TWL4030_PREDL_GAIN),
+ TWL4030_REG_PREDL_CTL);
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL);
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg_val & (~TWL4030_PREDR_GAIN),
+ TWL4030_REG_PREDL_CTL);
+
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL);
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg_val & (~TWL4030_PRECKL_GAIN),
+ TWL4030_REG_PRECKL_CTL);
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL);
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ reg_val & (~TWL4030_PRECKL_GAIN),
+ TWL4030_REG_PRECKR_CTL);
+
+ /* Disable PLL */
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
+ reg_val &= ~TWL4030_APLL_EN;
+ twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val);
+ } else {
+ /* Restore the volumes
+ * Headset mute is done in it's own event handler
+ * Things to restore: Earpiece, PreDrivL/R, CarkitL/R
+ */
+ twl4030_write(codec, TWL4030_REG_EAR_CTL,
+ twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL));
+
+ twl4030_write(codec, TWL4030_REG_PREDL_CTL,
+ twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL));
+ twl4030_write(codec, TWL4030_REG_PREDR_CTL,
+ twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL));
+
+ twl4030_write(codec, TWL4030_REG_PRECKL_CTL,
+ twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL));
+ twl4030_write(codec, TWL4030_REG_PRECKR_CTL,
+ twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL));
+
+ /* Enable PLL */
+ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL);
+ reg_val |= TWL4030_APLL_EN;
+ twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val);
+ }
+
+ twl4030->codec_muted = mute;
+}
+
+static void twl4030_power_up(struct snd_soc_codec *codec)
+{
+ struct twl4030_priv *twl4030 = codec->private_data;
+ u8 anamicl, regmisc1, byte;
+ int i = 0;
+
+ if (twl4030->codec_powered)
+ return;
+
+ /* set CODECPDZ to turn on codec */
+ twl4030_codec_enable(codec, 1);
+
+ /* initiate offset cancellation */
+ anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
+ twl4030_write(codec, TWL4030_REG_ANAMICL,
+ anamicl | TWL4030_CNCL_OFFSET_START);
+
+ /* wait for offset cancellation to complete */
+ do {
+ /* this takes a little while, so don't slam i2c */
+ udelay(2000);
+ twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
+ TWL4030_REG_ANAMICL);
+ } while ((i++ < 100) &&
+ ((byte & TWL4030_CNCL_OFFSET_START) ==
+ TWL4030_CNCL_OFFSET_START));
+
+ /* Make sure that the reg_cache has the same value as the HW */
+ twl4030_write_reg_cache(codec, TWL4030_REG_ANAMICL, byte);
+
+ /* anti-pop when changing analog gain */
+ regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
+ twl4030_write(codec, TWL4030_REG_MISC_SET_1,
+ regmisc1 | TWL4030_SMOOTH_ANAVOL_EN);
+
+ /* toggle CODECPDZ as per TRM */
+ twl4030_codec_enable(codec, 0);
+ twl4030_codec_enable(codec, 1);
+}
+
+/*
+ * Unconditional power down
+ */
+static void twl4030_power_down(struct snd_soc_codec *codec)
+{
+ /* power down */
+ twl4030_codec_enable(codec, 0);
+}
+
/* Earpiece */
static const char *twl4030_earpiece_texts[] =
{"Off", "DACL1", "DACL2", "DACR1"};
static const struct snd_kcontrol_new twl4030_dapm_micpathtx2_control =
SOC_DAPM_ENUM("Route", twl4030_micpathtx2_enum);
+/* Analog bypass for AudioR1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr1_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL1 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl1_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL1_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioR2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXR2_APGA_CTL, 2, 1, 0);
+
+/* Analog bypass for AudioL2 */
+static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control =
+ SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0);
+
+/* Digital bypass gain, 0 mutes the bypass */
+static const unsigned int twl4030_dapm_dbypass_tlv[] = {
+ TLV_DB_RANGE_HEAD(2),
+ 0, 3, TLV_DB_SCALE_ITEM(-2400, 0, 1),
+ 4, 7, TLV_DB_SCALE_ITEM(-1800, 600, 0),
+};
+
+/* Digital bypass left (TX1L -> RX2L) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassl_control =
+ SOC_DAPM_SINGLE_TLV("Volume",
+ TWL4030_REG_ATX2ARXPGA, 3, 7, 0,
+ twl4030_dapm_dbypass_tlv);
+
+/* Digital bypass right (TX1R -> RX2R) */
+static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control =
+ SOC_DAPM_SINGLE_TLV("Volume",
+ TWL4030_REG_ATX2ARXPGA, 0, 7, 0,
+ twl4030_dapm_dbypass_tlv);
+
static int micpath_event(struct snd_soc_dapm_widget *w,
struct snd_kcontrol *kcontrol, int event)
{
return 0;
}
+static int headsetl_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ unsigned char hs_gain, hs_pop;
+
+ /* Save the current volume */
+ hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET);
+ hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET);
+
+ switch (event) {
+ case SND_SOC_DAPM_POST_PMU:
+ /* Do the anti-pop/bias ramp enable according to the TRM */
+ hs_pop |= TWL4030_VMID_EN;
+ twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ /* Is this needed? Can we just use whatever gain here? */
+ twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET,
+ (hs_gain & (~0x0f)) | 0x0a);
+ hs_pop |= TWL4030_RAMP_EN;
+ twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+
+ /* Restore the original volume */
+ twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain);
+ break;
+ case SND_SOC_DAPM_POST_PMD:
+ /* Do the anti-pop/bias ramp disable according to the TRM */
+ hs_pop &= ~TWL4030_RAMP_EN;
+ twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ /* Bypass the reg_cache to mute the headset */
+ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE,
+ hs_gain & (~0x0f),
+ TWL4030_REG_HS_GAIN_SET);
+ hs_pop &= ~TWL4030_VMID_EN;
+ twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop);
+ break;
+ }
+ return 0;
+}
+
+static int bypass_event(struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ struct soc_mixer_control *m =
+ (struct soc_mixer_control *)w->kcontrols->private_value;
+ struct twl4030_priv *twl4030 = w->codec->private_data;
+ unsigned char reg;
+
+ reg = twl4030_read_reg_cache(w->codec, m->reg);
+
+ if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) {
+ /* Analog bypass */
+ if (reg & (1 << m->shift))
+ twl4030->bypass_state |=
+ (1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL));
+ else
+ twl4030->bypass_state &=
+ ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL));
+ } else {
+ /* Digital bypass */
+ if (reg & (0x7 << m->shift))
+ twl4030->bypass_state |= (1 << (m->shift ? 5 : 4));
+ else
+ twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4));
+ }
+
+ if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) {
+ if (twl4030->bypass_state)
+ twl4030_codec_mute(w->codec, 0);
+ else
+ twl4030_codec_mute(w->codec, 1);
+ }
+ return 0;
+}
+
/*
* Some of the gain controls in TWL (mostly those which are associated with
* the outputs) are implemented in an interesting way:
*/
static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0);
+static const char *twl4030_rampdelay_texts[] = {
+ "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms",
+ "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms",
+ "3495/2581/1748 ms"
+};
+
+static const struct soc_enum twl4030_rampdelay_enum =
+ SOC_ENUM_SINGLE(TWL4030_REG_HS_POPN_SET, 2,
+ ARRAY_SIZE(twl4030_rampdelay_texts),
+ twl4030_rampdelay_texts);
+
static const struct snd_kcontrol_new twl4030_snd_controls[] = {
/* Common playback gain controls */
SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume",
SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN,
0, 3, 5, 0, input_gain_tlv),
-};
-
-/* add non dapm controls */
-static int twl4030_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(twl4030_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&twl4030_snd_controls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
- return 0;
-}
+ SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum),
+};
static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = {
/* Left channel inputs */
/* DACs */
SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback",
- TWL4030_REG_AVDAC_CTL, 0, 0),
+ SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback",
- TWL4030_REG_AVDAC_CTL, 1, 0),
+ SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback",
- TWL4030_REG_AVDAC_CTL, 2, 0),
+ SND_SOC_NOPM, 0, 0),
SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback",
- TWL4030_REG_AVDAC_CTL, 3, 0),
+ SND_SOC_NOPM, 0, 0),
/* Analog PGAs */
SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL,
SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL,
0, 0, NULL, 0),
+ /* Analog bypasses */
+ SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassr1_control, bypass_event,
+ SND_SOC_DAPM_POST_REG),
+ SND_SOC_DAPM_SWITCH_E("Left1 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassl1_control,
+ bypass_event, SND_SOC_DAPM_POST_REG),
+ SND_SOC_DAPM_SWITCH_E("Right2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassr2_control,
+ bypass_event, SND_SOC_DAPM_POST_REG),
+ SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_abypassl2_control,
+ bypass_event, SND_SOC_DAPM_POST_REG),
+
+ /* Digital bypasses */
+ SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_dbypassl_control, bypass_event,
+ SND_SOC_DAPM_POST_REG),
+ SND_SOC_DAPM_SWITCH_E("Right Digital Loopback", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_dbypassr_control, bypass_event,
+ SND_SOC_DAPM_POST_REG),
+
+ SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL,
+ 0, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", TWL4030_REG_AVDAC_CTL,
+ 1, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", TWL4030_REG_AVDAC_CTL,
+ 2, 0, NULL, 0),
+ SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL,
+ 3, 0, NULL, 0),
+
/* Output MUX controls */
/* Earpiece */
SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0,
SND_SOC_DAPM_VALUE_MUX("PredriveR Mux", SND_SOC_NOPM, 0, 0,
&twl4030_dapm_predriver_control),
/* HeadsetL/R */
- SND_SOC_DAPM_MUX("HeadsetL Mux", SND_SOC_NOPM, 0, 0,
- &twl4030_dapm_hsol_control),
+ SND_SOC_DAPM_MUX_E("HeadsetL Mux", SND_SOC_NOPM, 0, 0,
+ &twl4030_dapm_hsol_control, headsetl_event,
+ SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD),
SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0,
&twl4030_dapm_hsor_control),
/* CarkitL/R */
SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD|
SND_SOC_DAPM_POST_REG),
- /* Analog input muxes with power switch for the physical ADCL/R */
+ /* Analog input muxes with switch for the capture amplifiers */
SND_SOC_DAPM_VALUE_MUX("Analog Left Capture Route",
- TWL4030_REG_AVADC_CTL, 3, 0, &twl4030_dapm_analoglmic_control),
+ TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_control),
SND_SOC_DAPM_VALUE_MUX("Analog Right Capture Route",
- TWL4030_REG_AVADC_CTL, 1, 0, &twl4030_dapm_analogrmic_control),
+ TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_control),
- SND_SOC_DAPM_PGA("Analog Left Amplifier",
- TWL4030_REG_ANAMICL, 4, 0, NULL, 0),
- SND_SOC_DAPM_PGA("Analog Right Amplifier",
- TWL4030_REG_ANAMICR, 4, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC Physical Left",
+ TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0),
+ SND_SOC_DAPM_PGA("ADC Physical Right",
+ TWL4030_REG_AVADC_CTL, 1, 0, NULL, 0),
SND_SOC_DAPM_PGA("Digimic0 Enable",
TWL4030_REG_ADCMICSEL, 1, 0, NULL, 0),
SND_SOC_DAPM_MICBIAS("Mic Bias 1", TWL4030_REG_MICBIAS_CTL, 0, 0),
SND_SOC_DAPM_MICBIAS("Mic Bias 2", TWL4030_REG_MICBIAS_CTL, 1, 0),
SND_SOC_DAPM_MICBIAS("Headset Mic Bias", TWL4030_REG_MICBIAS_CTL, 2, 0),
+
};
static const struct snd_soc_dapm_route intercon[] = {
- {"ARXL1_APGA", NULL, "DAC Left1"},
- {"ARXR1_APGA", NULL, "DAC Right1"},
- {"ARXL2_APGA", NULL, "DAC Left2"},
- {"ARXR2_APGA", NULL, "DAC Right2"},
+ {"Analog L1 Playback Mixer", NULL, "DAC Left1"},
+ {"Analog R1 Playback Mixer", NULL, "DAC Right1"},
+ {"Analog L2 Playback Mixer", NULL, "DAC Left2"},
+ {"Analog R2 Playback Mixer", NULL, "DAC Right2"},
+
+ {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"},
+ {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"},
+ {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"},
+ {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"},
/* Internal playback routings */
/* Earpiece */
{"Analog Right Capture Route", "Sub mic", "SUBMIC"},
{"Analog Right Capture Route", "AUXR", "AUXR"},
- {"Analog Left Amplifier", NULL, "Analog Left Capture Route"},
- {"Analog Right Amplifier", NULL, "Analog Right Capture Route"},
+ {"ADC Physical Left", NULL, "Analog Left Capture Route"},
+ {"ADC Physical Right", NULL, "Analog Right Capture Route"},
{"Digimic0 Enable", NULL, "DIGIMIC0"},
{"Digimic1 Enable", NULL, "DIGIMIC1"},
/* TX1 Left capture path */
- {"TX1 Capture Route", "Analog", "Analog Left Amplifier"},
+ {"TX1 Capture Route", "Analog", "ADC Physical Left"},
{"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
/* TX1 Right capture path */
- {"TX1 Capture Route", "Analog", "Analog Right Amplifier"},
+ {"TX1 Capture Route", "Analog", "ADC Physical Right"},
{"TX1 Capture Route", "Digimic0", "Digimic0 Enable"},
/* TX2 Left capture path */
- {"TX2 Capture Route", "Analog", "Analog Left Amplifier"},
+ {"TX2 Capture Route", "Analog", "ADC Physical Left"},
{"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
/* TX2 Right capture path */
- {"TX2 Capture Route", "Analog", "Analog Right Amplifier"},
+ {"TX2 Capture Route", "Analog", "ADC Physical Right"},
{"TX2 Capture Route", "Digimic1", "Digimic1 Enable"},
{"ADC Virtual Left1", NULL, "TX1 Capture Route"},
{"ADC Virtual Left2", NULL, "TX2 Capture Route"},
{"ADC Virtual Right2", NULL, "TX2 Capture Route"},
+ /* Analog bypass routes */
+ {"Right1 Analog Loopback", "Switch", "Analog Right Capture Route"},
+ {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"},
+ {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"},
+ {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"},
+
+ {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"},
+ {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"},
+ {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"},
+ {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"},
+
+ /* Digital bypass routes */
+ {"Right Digital Loopback", "Volume", "TX1 Capture Route"},
+ {"Left Digital Loopback", "Volume", "TX1 Capture Route"},
+
+ {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"},
+ {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"},
+
};
static int twl4030_add_widgets(struct snd_soc_codec *codec)
return 0;
}
-static void twl4030_power_up(struct snd_soc_codec *codec)
-{
- u8 anamicl, regmisc1, byte, popn;
- int i = 0;
-
- /* set CODECPDZ to turn on codec */
- twl4030_set_codecpdz(codec);
-
- /* initiate offset cancellation */
- anamicl = twl4030_read_reg_cache(codec, TWL4030_REG_ANAMICL);
- twl4030_write(codec, TWL4030_REG_ANAMICL,
- anamicl | TWL4030_CNCL_OFFSET_START);
-
-
- /* wait for offset cancellation to complete */
- do {
- /* this takes a little while, so don't slam i2c */
- udelay(2000);
- twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &byte,
- TWL4030_REG_ANAMICL);
- } while ((i++ < 100) &&
- ((byte & TWL4030_CNCL_OFFSET_START) ==
- TWL4030_CNCL_OFFSET_START));
-
- /* anti-pop when changing analog gain */
- regmisc1 = twl4030_read_reg_cache(codec, TWL4030_REG_MISC_SET_1);
- twl4030_write(codec, TWL4030_REG_MISC_SET_1,
- regmisc1 | TWL4030_SMOOTH_ANAVOL_EN);
-
- /* toggle CODECPDZ as per TRM */
- twl4030_clear_codecpdz(codec);
- twl4030_set_codecpdz(codec);
-
- /* program anti-pop with bias ramp delay */
- popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
- popn &= TWL4030_RAMP_DELAY;
- popn |= TWL4030_RAMP_DELAY_645MS;
- twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
- popn |= TWL4030_VMID_EN;
- twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
-
- /* enable anti-pop ramp */
- popn |= TWL4030_RAMP_EN;
- twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
-}
-
-static void twl4030_power_down(struct snd_soc_codec *codec)
-{
- u8 popn;
-
- /* disable anti-pop ramp */
- popn = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET);
- popn &= ~TWL4030_RAMP_EN;
- twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
-
- /* disable bias out */
- popn &= ~TWL4030_VMID_EN;
- twl4030_write(codec, TWL4030_REG_HS_POPN_SET, popn);
-
- /* power down */
- twl4030_clear_codecpdz(codec);
-}
-
static int twl4030_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
+ struct twl4030_priv *twl4030 = codec->private_data;
+
switch (level) {
case SND_SOC_BIAS_ON:
- twl4030_power_up(codec);
+ twl4030_codec_mute(codec, 0);
break;
case SND_SOC_BIAS_PREPARE:
- /* TODO: develop a twl4030_prepare function */
+ twl4030_power_up(codec);
+ if (twl4030->bypass_state)
+ twl4030_codec_mute(codec, 0);
+ else
+ twl4030_codec_mute(codec, 1);
break;
case SND_SOC_BIAS_STANDBY:
- /* TODO: develop a twl4030_standby function */
- twl4030_power_down(codec);
+ twl4030_power_up(codec);
+ if (twl4030->bypass_state)
+ twl4030_codec_mute(codec, 0);
+ else
+ twl4030_codec_mute(codec, 1);
break;
case SND_SOC_BIAS_OFF:
twl4030_power_down(codec);
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
u8 mode, old_mode, format, old_format;
-
/* bit rate */
old_mode = twl4030_read_reg_cache(codec,
TWL4030_REG_CODEC_MODE) & ~TWL4030_CODECPDZ;
if (mode != old_mode) {
/* change rate and set CODECPDZ */
+ twl4030_codec_enable(codec, 0);
twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode);
- twl4030_set_codecpdz(codec);
+ twl4030_codec_enable(codec, 1);
}
/* sample size */
if (format != old_format) {
/* clear CODECPDZ before changing format (codec requirement) */
- twl4030_clear_codecpdz(codec);
+ twl4030_codec_enable(codec, 0);
/* change format */
twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
/* set CODECPDZ afterwards */
- twl4030_set_codecpdz(codec);
+ twl4030_codec_enable(codec, 1);
}
return 0;
}
if (format != old_format) {
/* clear CODECPDZ before changing format (codec requirement) */
- twl4030_clear_codecpdz(codec);
+ twl4030_codec_enable(codec, 0);
/* change format */
twl4030_write(codec, TWL4030_REG_AUDIO_IF, format);
/* set CODECPDZ afterwards */
- twl4030_set_codecpdz(codec);
+ twl4030_codec_enable(codec, 1);
}
return 0;
#define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000)
#define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE)
+static struct snd_soc_dai_ops twl4030_dai_ops = {
+ .hw_params = twl4030_hw_params,
+ .set_sysclk = twl4030_set_dai_sysclk,
+ .set_fmt = twl4030_set_dai_fmt,
+};
+
struct snd_soc_dai twl4030_dai = {
.name = "twl4030",
.playback = {
.channels_max = 2,
.rates = TWL4030_RATES,
.formats = TWL4030_FORMATS,},
- .ops = {
- .hw_params = twl4030_hw_params,
- .set_sysclk = twl4030_set_dai_sysclk,
- .set_fmt = twl4030_set_dai_fmt,
- }
+ .ops = &twl4030_dai_ops,
};
EXPORT_SYMBOL_GPL(twl4030_dai);
static int twl4030_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
static int twl4030_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
twl4030_set_bias_level(codec, codec->suspend_bias_level);
static int twl4030_init(struct snd_soc_device *socdev)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret = 0;
printk(KERN_INFO "TWL4030 Audio Codec init \n");
/* power on device */
twl4030_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
- twl4030_add_controls(codec);
+ snd_soc_add_controls(codec, twl4030_snd_controls,
+ ARRAY_SIZE(twl4030_snd_controls));
twl4030_add_widgets(codec);
ret = snd_soc_init_card(socdev);
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
struct snd_soc_codec *codec;
+ struct twl4030_priv *twl4030;
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
- socdev->codec = codec;
+ twl4030 = kzalloc(sizeof(struct twl4030_priv), GFP_KERNEL);
+ if (twl4030 == NULL) {
+ kfree(codec);
+ return -ENOMEM;
+ }
+
+ codec->private_data = twl4030;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int twl4030_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
printk(KERN_INFO "TWL4030 Audio Codec remove\n");
+ twl4030_set_bias_level(codec, SND_SOC_BIAS_OFF);
snd_soc_free_pcms(socdev);
snd_soc_dapm_free(socdev);
+ kfree(codec->private_data);
kfree(codec);
return 0;
#define TWL4030_CLK256FS_EN 0x02
#define TWL4030_AIF_EN 0x01
+/* EAR_CTL (0x21) */
+#define TWL4030_EAR_GAIN 0x30
+
/* HS_GAIN_SET (0x23) Fields */
#define TWL4030_HSR_GAIN 0x0C
#define TWL4030_RAMP_DELAY_2581MS 0x1C
#define TWL4030_RAMP_EN 0x02
+/* PREDL_CTL (0x25) */
+#define TWL4030_PREDL_GAIN 0x30
+
+/* PREDR_CTL (0x26) */
+#define TWL4030_PREDR_GAIN 0x30
+
+/* PRECKL_CTL (0x27) */
+#define TWL4030_PRECKL_GAIN 0x30
+
+/* PRECKR_CTL (0x28) */
+#define TWL4030_PRECKR_GAIN 0x30
+
/* HFL_CTL (0x29, 0x2A) Fields */
#define TWL4030_HF_CTL_HB_EN 0x04
#define TWL4030_HF_CTL_LOOP_EN 0x08
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct uda134x_priv *uda134x = codec->private_data;
struct snd_pcm_runtime *master_runtime;
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct uda134x_priv *uda134x = codec->private_data;
if (uda134x->master_substream == substream)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct uda134x_priv *uda134x = codec->private_data;
u8 hw_params;
SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
};
-static int uda134x_add_controls(struct snd_soc_codec *codec)
-{
- int err, i, n;
- const struct snd_kcontrol_new *ctrls;
- struct uda134x_platform_data *pd = codec->control_data;
-
- switch (pd->model) {
- case UDA134X_UDA1340:
- case UDA134X_UDA1344:
- n = ARRAY_SIZE(uda1340_snd_controls);
- ctrls = uda1340_snd_controls;
- break;
- case UDA134X_UDA1341:
- n = ARRAY_SIZE(uda1341_snd_controls);
- ctrls = uda1341_snd_controls;
- break;
- default:
- printk(KERN_ERR "%s unkown codec type: %d",
- __func__, pd->model);
- return -EINVAL;
- }
-
- for (i = 0; i < n; i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&ctrls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
+static struct snd_soc_dai_ops uda134x_dai_ops = {
+ .startup = uda134x_startup,
+ .shutdown = uda134x_shutdown,
+ .hw_params = uda134x_hw_params,
+ .digital_mute = uda134x_mute,
+ .set_sysclk = uda134x_set_dai_sysclk,
+ .set_fmt = uda134x_set_dai_fmt,
+};
struct snd_soc_dai uda134x_dai = {
.name = "UDA134X",
.formats = UDA134X_FORMATS,
},
/* pcm operations */
- .ops = {
- .startup = uda134x_startup,
- .shutdown = uda134x_shutdown,
- .hw_params = uda134x_hw_params,
- .digital_mute = uda134x_mute,
- .set_sysclk = uda134x_set_dai_sysclk,
- .set_fmt = uda134x_set_dai_fmt,
- }
+ .ops = &uda134x_dai_ops,
};
EXPORT_SYMBOL(uda134x_dai);
return -EINVAL;
}
- socdev->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
- if (socdev->codec == NULL)
+ socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
+ if (socdev->card->codec == NULL)
return ret;
- codec = socdev->codec;
+ codec = socdev->card->codec;
uda134x = kzalloc(sizeof(struct uda134x_priv), GFP_KERNEL);
if (uda134x == NULL)
goto pcm_err;
}
- ret = uda134x_add_controls(codec);
+ switch (pd->model) {
+ case UDA134X_UDA1340:
+ case UDA134X_UDA1344:
+ ret = snd_soc_add_controls(codec, uda1340_snd_controls,
+ ARRAY_SIZE(uda1340_snd_controls));
+ break;
+ case UDA134X_UDA1341:
+ ret = snd_soc_add_controls(codec, uda1341_snd_controls,
+ ARRAY_SIZE(uda1341_snd_controls));
+ break;
+ default:
+ printk(KERN_ERR "%s unkown codec type: %d",
+ __func__, pd->model);
+ return -EINVAL;
+ }
+
if (ret < 0) {
printk(KERN_ERR "UDA134X: failed to register controls\n");
goto pcm_err;
static int uda134x_soc_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
uda134x_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
uda134x_set_bias_level(codec, SND_SOC_BIAS_OFF);
static int uda134x_soc_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
uda134x_set_bias_level(codec, SND_SOC_BIAS_PREPARE);
uda134x_set_bias_level(codec, SND_SOC_BIAS_ON);
#include <linux/ioctl.h>
#include <linux/delay.h>
#include <linux/i2c.h>
+#include <linux/workqueue.h>
#include <sound/core.h>
#include <sound/control.h>
#include <sound/initval.h>
#include "uda1380.h"
-#define UDA1380_VERSION "0.6"
+static struct work_struct uda1380_work;
+static struct snd_soc_codec *uda1380_codec;
/*
* uda1380 register cache
0x0000, 0x8000, 0x0002, 0x0000,
};
+static unsigned long uda1380_cache_dirty;
+
/*
* read uda1380 register cache
*/
u16 reg, unsigned int value)
{
u16 *cache = codec->reg_cache;
+
if (reg >= UDA1380_CACHEREGNUM)
return;
+ if ((reg >= 0x10) && (cache[reg] != value))
+ set_bit(reg - 0x10, &uda1380_cache_dirty);
cache[reg] = value;
}
(data[0]<<8) | data[1]);
return -EIO;
}
+ if (reg >= 0x10)
+ clear_bit(reg - 0x10, &uda1380_cache_dirty);
return 0;
} else
return -EIO;
#define uda1380_reset(c) uda1380_write(c, UDA1380_RESET, 0)
+static void uda1380_flush_work(struct work_struct *work)
+{
+ int bit, reg;
+
+ for_each_bit(bit, &uda1380_cache_dirty, UDA1380_CACHEREGNUM - 0x10) {
+ reg = 0x10 + bit;
+ pr_debug("uda1380: flush reg %x val %x:\n", reg,
+ uda1380_read_reg_cache(uda1380_codec, reg));
+ uda1380_write(uda1380_codec, reg,
+ uda1380_read_reg_cache(uda1380_codec, reg));
+ clear_bit(bit, &uda1380_cache_dirty);
+ }
+}
+
/* declarations of ALSA reg_elem_REAL controls */
static const char *uda1380_deemp[] = {
"None",
SOC_SINGLE("DAC Polarity inverting Switch", UDA1380_MIXER, 15, 1, 0), /* DA_POL_INV */
SOC_ENUM("Noise Shaper", uda1380_sel_ns_enum), /* SEL_NS */
SOC_ENUM("Digital Mixer Signal Control", uda1380_mix_enum), /* MIX_POS, MIX */
- SOC_SINGLE("Silence Switch", UDA1380_MIXER, 7, 1, 0), /* SILENCE, force DAC output to silence */
SOC_SINGLE("Silence Detector Switch", UDA1380_MIXER, 6, 1, 0), /* SDET_ON */
SOC_ENUM("Silence Detector Setting", uda1380_sdet_enum), /* SD_VALUE */
SOC_ENUM("Oversampling Input", uda1380_os_enum), /* OS */
SOC_SINGLE("AGC Switch", UDA1380_AGC, 0, 1, 0),
};
-/* add non dapm controls */
-static int uda1380_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(uda1380_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&uda1380_snd_controls[i], codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
/* Input mux */
static const struct snd_kcontrol_new uda1380_input_mux_control =
SOC_DAPM_ENUM("Route", uda1380_input_sel_enum);
return 0;
}
-static int uda1380_set_dai_fmt(struct snd_soc_dai *codec_dai,
+static int uda1380_set_dai_fmt_both(struct snd_soc_dai *codec_dai,
unsigned int fmt)
{
struct snd_soc_codec *codec = codec_dai->codec;
iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
iface &= ~(R01_SFORI_MASK | R01_SIM | R01_SFORO_MASK);
- /* FIXME: how to select I2S for DATAO and MSB for DATAI correctly? */
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
case SND_SOC_DAIFMT_I2S:
iface |= R01_SFORI_I2S | R01_SFORO_I2S;
break;
case SND_SOC_DAIFMT_LSB:
- iface |= R01_SFORI_LSB16 | R01_SFORO_I2S;
+ iface |= R01_SFORI_LSB16 | R01_SFORO_LSB16;
break;
case SND_SOC_DAIFMT_MSB:
- iface |= R01_SFORI_MSB | R01_SFORO_I2S;
+ iface |= R01_SFORI_MSB | R01_SFORO_MSB;
}
- if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
- iface |= R01_SIM;
+ /* DATAI is slave only, so in single-link mode, this has to be slave */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
uda1380_write(codec, UDA1380_IFACE, iface);
return 0;
}
-/*
- * Flush reg cache
- * We can only write the interpolator and decimator registers
- * when the DAI is being clocked by the CPU DAI. It's up to the
- * machine and cpu DAI driver to do this before we are called.
- */
-static int uda1380_pcm_prepare(struct snd_pcm_substream *substream,
- struct snd_soc_dai *dai)
+static int uda1380_set_dai_fmt_playback(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
{
- struct snd_soc_pcm_runtime *rtd = substream->private_data;
- struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
- int reg, reg_start, reg_end, clk;
-
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
- reg_start = UDA1380_MVOL;
- reg_end = UDA1380_MIXER;
- } else {
- reg_start = UDA1380_DEC;
- reg_end = UDA1380_AGC;
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~R01_SFORI_MASK;
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORI_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORI_LSB16;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORI_MSB;
}
- /* FIXME disable DAC_CLK */
- clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
- uda1380_write(codec, UDA1380_CLK, clk & ~R00_DAC_CLK);
+ /* DATAI is slave only, so this has to be slave */
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+ return -EINVAL;
+
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+static int uda1380_set_dai_fmt_capture(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ int iface;
+
+ /* set up DAI based upon fmt */
+ iface = uda1380_read_reg_cache(codec, UDA1380_IFACE);
+ iface &= ~(R01_SIM | R01_SFORO_MASK);
- for (reg = reg_start; reg <= reg_end; reg++) {
- pr_debug("uda1380: flush reg %x val %x:", reg,
- uda1380_read_reg_cache(codec, reg));
- uda1380_write(codec, reg, uda1380_read_reg_cache(codec, reg));
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ iface |= R01_SFORO_I2S;
+ break;
+ case SND_SOC_DAIFMT_LSB:
+ iface |= R01_SFORO_LSB16;
+ break;
+ case SND_SOC_DAIFMT_MSB:
+ iface |= R01_SFORO_MSB;
}
- /* FIXME enable DAC_CLK */
- uda1380_write(codec, UDA1380_CLK, clk | R00_DAC_CLK);
+ if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) == SND_SOC_DAIFMT_CBM_CFM)
+ iface |= R01_SIM;
+ uda1380_write(codec, UDA1380_IFACE, iface);
+
+ return 0;
+}
+
+static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ uda1380_write_reg_cache(codec, UDA1380_MIXER,
+ mixer & ~R14_SILENCE);
+ schedule_work(&uda1380_work);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ uda1380_write_reg_cache(codec, UDA1380_MIXER,
+ mixer | R14_SILENCE);
+ schedule_work(&uda1380_work);
+ break;
+ }
return 0;
}
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
/* set WSPLL power and divider if running from this clock */
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_device *socdev = rtd->socdev;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
/* shut down WSPLL power if running from this clock */
uda1380_write(codec, UDA1380_CLK, clk);
}
-static int uda1380_mute(struct snd_soc_dai *codec_dai, int mute)
-{
- struct snd_soc_codec *codec = codec_dai->codec;
- u16 mute_reg = uda1380_read_reg_cache(codec, UDA1380_DEEMP) & ~R13_MTM;
-
- /* FIXME: mute(codec,0) is called when the magician clock is already
- * set to WSPLL, but for some unknown reason writing to interpolator
- * registers works only when clocked by SYSCLK */
- u16 clk = uda1380_read_reg_cache(codec, UDA1380_CLK);
- uda1380_write(codec, UDA1380_CLK, ~R00_DAC_CLK & clk);
- if (mute)
- uda1380_write(codec, UDA1380_DEEMP, mute_reg | R13_MTM);
- else
- uda1380_write(codec, UDA1380_DEEMP, mute_reg);
- uda1380_write(codec, UDA1380_CLK, clk);
- return 0;
-}
-
static int uda1380_set_bias_level(struct snd_soc_codec *codec,
enum snd_soc_bias_level level)
{
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000)
+static struct snd_soc_dai_ops uda1380_dai_ops = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_both,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_playback = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_playback,
+};
+
+static struct snd_soc_dai_ops uda1380_dai_ops_capture = {
+ .hw_params = uda1380_pcm_hw_params,
+ .shutdown = uda1380_pcm_shutdown,
+ .trigger = uda1380_trigger,
+ .set_fmt = uda1380_set_dai_fmt_capture,
+};
+
struct snd_soc_dai uda1380_dai[] = {
{
.name = "UDA1380",
.channels_max = 2,
.rates = UDA1380_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,},
- .ops = {
- .hw_params = uda1380_pcm_hw_params,
- .shutdown = uda1380_pcm_shutdown,
- .prepare = uda1380_pcm_prepare,
- .digital_mute = uda1380_mute,
- .set_fmt = uda1380_set_dai_fmt,
- },
+ .ops = &uda1380_dai_ops,
},
{ /* playback only - dual interface */
.name = "UDA1380",
.rates = UDA1380_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
- .ops = {
- .hw_params = uda1380_pcm_hw_params,
- .shutdown = uda1380_pcm_shutdown,
- .prepare = uda1380_pcm_prepare,
- .digital_mute = uda1380_mute,
- .set_fmt = uda1380_set_dai_fmt,
- },
+ .ops = &uda1380_dai_ops_playback,
},
{ /* capture only - dual interface*/
.name = "UDA1380",
.rates = UDA1380_RATES,
.formats = SNDRV_PCM_FMTBIT_S16_LE,
},
- .ops = {
- .hw_params = uda1380_pcm_hw_params,
- .shutdown = uda1380_pcm_shutdown,
- .prepare = uda1380_pcm_prepare,
- .set_fmt = uda1380_set_dai_fmt,
- },
+ .ops = &uda1380_dai_ops_capture,
},
};
EXPORT_SYMBOL_GPL(uda1380_dai);
static int uda1380_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
static int uda1380_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int i;
u8 data[2];
u16 *cache = codec->reg_cache;
*/
static int uda1380_init(struct snd_soc_device *socdev, int dac_clk)
{
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret = 0;
codec->name = "UDA1380";
codec->reg_cache_step = 1;
uda1380_reset(codec);
+ uda1380_codec = codec;
+ INIT_WORK(&uda1380_work, uda1380_flush_work);
+
/* register pcms */
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
}
/* uda1380 init */
- uda1380_add_controls(codec);
+ snd_soc_add_controls(codec, uda1380_snd_controls,
+ ARRAY_SIZE(uda1380_snd_controls));
uda1380_add_widgets(codec);
ret = snd_soc_init_card(socdev);
if (ret < 0) {
{
struct snd_soc_device *socdev = uda1380_socdev;
struct uda1380_setup_data *setup = socdev->codec_data;
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
int ret;
i2c_set_clientdata(i2c, codec);
struct snd_soc_codec *codec;
int ret;
- pr_info("UDA1380 Audio Codec %s", UDA1380_VERSION);
-
setup = socdev->codec_data;
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
if (codec == NULL)
return -ENOMEM;
- socdev->codec = codec;
+ socdev->card->codec = codec;
mutex_init(&codec->mutex);
INIT_LIST_HEAD(&codec->dapm_widgets);
INIT_LIST_HEAD(&codec->dapm_paths);
static int uda1380_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
if (codec->control_data)
uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF);
u16 mute;
};
+struct wm8350_jack_data {
+ struct snd_soc_jack *jack;
+ int report;
+};
+
struct wm8350_data {
struct snd_soc_codec codec;
struct wm8350_output out1;
struct wm8350_output out2;
+ struct wm8350_jack_data hpl;
+ struct wm8350_jack_data hpr;
struct regulator_bulk_data supplies[ARRAY_SIZE(supply_names)];
};
{"Beep", NULL, "IN3R PGA"},
};
-static int wm8350_add_controls(struct snd_soc_codec *codec)
-{
- int err, i;
-
- for (i = 0; i < ARRAY_SIZE(wm8350_snd_controls); i++) {
- err = snd_ctl_add(codec->card,
- snd_soc_cnew(&wm8350_snd_controls[i],
- codec, NULL));
- if (err < 0)
- return err;
- }
-
- return 0;
-}
-
static int wm8350_add_widgets(struct snd_soc_codec *codec)
{
int ret;
static int wm8350_suspend(struct platform_device *pdev, pm_message_t state)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
wm8350_set_bias_level(codec, SND_SOC_BIAS_OFF);
return 0;
static int wm8350_resume(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
return 0;
}
+static void wm8350_hp_jack_handler(struct wm8350 *wm8350, int irq, void *data)
+{
+ struct wm8350_data *priv = data;
+ u16 reg;
+ int report;
+ int mask;
+ struct wm8350_jack_data *jack = NULL;
+
+ switch (irq) {
+ case WM8350_IRQ_CODEC_JCK_DET_L:
+ jack = &priv->hpl;
+ mask = WM8350_JACK_L_LVL;
+ break;
+
+ case WM8350_IRQ_CODEC_JCK_DET_R:
+ jack = &priv->hpr;
+ mask = WM8350_JACK_R_LVL;
+ break;
+
+ default:
+ BUG();
+ }
+
+ if (!jack->jack) {
+ dev_warn(wm8350->dev, "Jack interrupt called with no jack\n");
+ return;
+ }
+
+ /* Debounce */
+ msleep(200);
+
+ reg = wm8350_reg_read(wm8350, WM8350_JACK_PIN_STATUS);
+ if (reg & mask)
+ report = jack->report;
+ else
+ report = 0;
+
+ snd_soc_jack_report(jack->jack, report, jack->report);
+}
+
+/**
+ * wm8350_hp_jack_detect - Enable headphone jack detection.
+ *
+ * @codec: WM8350 codec
+ * @which: left or right jack detect signal
+ * @jack: jack to report detection events on
+ * @report: value to report
+ *
+ * Enables the headphone jack detection of the WM8350.
+ */
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+ struct snd_soc_jack *jack, int report)
+{
+ struct wm8350_data *priv = codec->private_data;
+ struct wm8350 *wm8350 = codec->control_data;
+ int irq;
+ int ena;
+
+ switch (which) {
+ case WM8350_JDL:
+ priv->hpl.jack = jack;
+ priv->hpl.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_L;
+ ena = WM8350_JDL_ENA;
+ break;
+
+ case WM8350_JDR:
+ priv->hpr.jack = jack;
+ priv->hpr.report = report;
+ irq = WM8350_IRQ_CODEC_JCK_DET_R;
+ ena = WM8350_JDR_ENA;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+ wm8350_set_bits(wm8350, WM8350_JACK_DETECT, ena);
+
+ /* Sync status */
+ wm8350_hp_jack_handler(wm8350, irq, priv);
+
+ wm8350_unmask_irq(wm8350, irq);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(wm8350_hp_jack_detect);
+
static struct snd_soc_codec *wm8350_codec;
static int wm8350_probe(struct platform_device *pdev)
BUG_ON(!wm8350_codec);
- socdev->codec = wm8350_codec;
- codec = socdev->codec;
+ socdev->card->codec = wm8350_codec;
+ codec = socdev->card->codec;
wm8350 = codec->control_data;
priv = codec->private_data;
wm8350_set_bits(wm8350, WM8350_ROUT2_VOLUME,
WM8350_OUT2_VU | WM8350_OUT2R_MUTE);
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L,
+ wm8350_hp_jack_handler, priv);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R,
+ wm8350_hp_jack_handler, priv);
+
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
if (ret < 0) {
dev_err(&pdev->dev, "failed to create pcms\n");
return ret;
}
- wm8350_add_controls(codec);
+ snd_soc_add_controls(codec, wm8350_snd_controls,
+ ARRAY_SIZE(wm8350_snd_controls));
wm8350_add_widgets(codec);
wm8350_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
static int wm8350_remove(struct platform_device *pdev)
{
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
- struct snd_soc_codec *codec = socdev->codec;
+ struct snd_soc_codec *codec = socdev->card->codec;
struct wm8350 *wm8350 = codec->control_data;
+ struct wm8350_data *priv = codec->private_data;
int ret;
+ wm8350_clear_bits(wm8350, WM8350_JACK_DETECT,
+ WM8350_JDL_ENA | WM8350_JDR_ENA);
+ wm8350_clear_bits(wm8350, WM8350_POWER_MGMT_4, WM8350_TOCLK_ENA);
+
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
+ wm8350_mask_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_L);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CODEC_JCK_DET_R);
+
+ priv->hpl.jack = NULL;
+ priv->hpr.jack = NULL;
+
/* cancel any work waiting to be queued. */
ret = cancel_delayed_work(&codec->delayed_work);
SNDRV_PCM_FMTBIT_S20_3LE |\
SNDRV_PCM_FMTBIT_S24_LE)
+static struct snd_soc_dai_ops wm8350_dai_ops = {
+ .hw_params = wm8350_pcm_hw_params,
+ .digital_mute = wm8350_mute,
+ .trigger = wm8350_pcm_trigger,
+ .set_fmt = wm8350_set_dai_fmt,
+ .set_sysclk = wm8350_set_dai_sysclk,
+ .set_pll = wm8350_set_fll,
+ .set_clkdiv = wm8350_set_clkdiv,
+};
+
struct snd_soc_dai wm8350_dai = {
.name = "WM8350",
.playback = {
.rates = WM8350_RATES,
.formats = WM8350_FORMATS,
},
- .ops = {
- .hw_params = wm8350_pcm_hw_params,
- .digital_mute = wm8350_mute,
- .trigger = wm8350_pcm_trigger,
- .set_fmt = wm8350_set_dai_fmt,
- .set_sysclk = wm8350_set_dai_sysclk,
- .set_pll = wm8350_set_fll,
- .set_clkdiv = wm8350_set_clkdiv,
- },
+ .ops = &wm8350_dai_ops,
};
EXPORT_SYMBOL_GPL(wm8350_dai);
};
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8350);
-static int wm8350_codec_probe(struct platform_device *pdev)
+static __devinit int wm8350_codec_probe(struct platform_device *pdev)
{
struct wm8350 *wm8350 = platform_get_drvdata(pdev);
struct wm8350_data *priv;
extern struct snd_soc_dai wm8350_dai;
extern struct snd_soc_codec_device soc_codec_dev_wm8350;
+enum wm8350_jack {
+ WM8350_JDL = 1,
+ WM8350_JDR = 2,
+};
+
+int wm8350_hp_jack_detect(struct snd_soc_codec *codec, enum wm8350_jack which,
+ struct snd_soc_jack *jack, int report);
+
#endif
--- /dev/null
+/*
+ * wm8400.c -- WM8400 ALSA Soc Audio driver
+ *
+ * Copyright 2008, 2009 Wolfson Microelectronics PLC.
+ * Author: Mark Brown <broonie@opensource.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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mfd/wm8400-audio.h>
+#include <linux/mfd/wm8400-private.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 "wm8400.h"
+
+/* Fake register for internal state */
+#define WM8400_INTDRIVBITS (WM8400_REGISTER_COUNT + 1)
+#define WM8400_INMIXL_PWR 0
+#define WM8400_AINLMUX_PWR 1
+#define WM8400_INMIXR_PWR 2
+#define WM8400_AINRMUX_PWR 3
+
+static struct regulator_bulk_data power[] = {
+ {
+ .supply = "I2S1VDD",
+ },
+ {
+ .supply = "I2S2VDD",
+ },
+ {
+ .supply = "DCVDD",
+ },
+ {
+ .supply = "AVDD",
+ },
+ {
+ .supply = "FLLVDD",
+ },
+ {
+ .supply = "HPVDD",
+ },
+ {
+ .supply = "SPKVDD",
+ },
+};
+
+/* codec private data */
+struct wm8400_priv {
+ struct snd_soc_codec codec;
+ struct wm8400 *wm8400;
+ u16 fake_register;
+ unsigned int sysclk;
+ unsigned int pcmclk;
+ struct work_struct work;
+ int fll_in, fll_out;
+};
+
+static inline unsigned int wm8400_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct wm8400_priv *wm8400 = codec->private_data;
+
+ if (reg == WM8400_INTDRIVBITS)
+ return wm8400->fake_register;
+ else
+ return wm8400_reg_read(wm8400->wm8400, reg);
+}
+
+/*
+ * write to the wm8400 register space
+ */
+static int wm8400_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int value)
+{
+ struct wm8400_priv *wm8400 = codec->private_data;
+
+ if (reg == WM8400_INTDRIVBITS) {
+ wm8400->fake_register = value;
+ return 0;
+ } else
+ return wm8400_set_bits(wm8400->wm8400, reg, 0xffff, value);
+}
+
+static void wm8400_codec_reset(struct snd_soc_codec *codec)
+{
+ struct wm8400_priv *wm8400 = codec->private_data;
+
+ wm8400_reset_codec_reg_cache(wm8400->wm8400);
+}
+
+static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600);
+
+static const DECLARE_TLV_DB_LINEAR(in_pga_tlv, -1650, 3000);
+
+static const DECLARE_TLV_DB_LINEAR(out_mix_tlv, -2100, 0);
+
+static const DECLARE_TLV_DB_LINEAR(out_pga_tlv, -7300, 600);
+
+static const DECLARE_TLV_DB_LINEAR(out_omix_tlv, -600, 0);
+
+static const DECLARE_TLV_DB_LINEAR(out_dac_tlv, -7163, 0);
+
+static const DECLARE_TLV_DB_LINEAR(in_adc_tlv, -7163, 1763);
+
+static const DECLARE_TLV_DB_LINEAR(out_sidetone_tlv, -3600, 0);
+
+static int wm8400_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol,
+ struct snd_ctl_elem_value *ucontrol)
+{
+ struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol);
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ int reg = mc->reg;
+ int ret;
+ u16 val;
+
+ ret = snd_soc_put_volsw(kcontrol, ucontrol);
+ if (ret < 0)
+ return ret;
+
+ /* now hit the volume update bits (always bit 8) */
+ val = wm8400_read(codec, reg);
+ return wm8400_write(codec, reg, val | 0x0100);
+}
+
+#define WM8400_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert, tlv_array) \
+{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \
+ .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\
+ SNDRV_CTL_ELEM_ACCESS_READWRITE,\
+ .tlv.p = (tlv_array), \
+ .info = snd_soc_info_volsw, \
+ .get = snd_soc_get_volsw, .put = wm8400_outpga_put_volsw_vu, \
+ .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) }
+
+
+static const char *wm8400_digital_sidetone[] =
+ {"None", "Left ADC", "Right ADC", "Reserved"};
+
+static const struct soc_enum wm8400_left_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADC_TO_DACL_SHIFT, 2, wm8400_digital_sidetone);
+
+static const struct soc_enum wm8400_right_digital_sidetone_enum =
+SOC_ENUM_SINGLE(WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADC_TO_DACR_SHIFT, 2, wm8400_digital_sidetone);
+
+static const char *wm8400_adcmode[] =
+ {"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"};
+
+static const struct soc_enum wm8400_right_adcmode_enum =
+SOC_ENUM_SINGLE(WM8400_ADC_CTRL, WM8400_ADC_HPF_CUT_SHIFT, 3, wm8400_adcmode);
+
+static const struct snd_kcontrol_new wm8400_snd_controls[] = {
+/* INMIXL */
+SOC_SINGLE("LIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L12MNBST_SHIFT,
+ 1, 0),
+SOC_SINGLE("LIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_L34MNBST_SHIFT,
+ 1, 0),
+/* INMIXR */
+SOC_SINGLE("RIN12 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R12MNBST_SHIFT,
+ 1, 0),
+SOC_SINGLE("RIN34 PGA Boost", WM8400_INPUT_MIXER3, WM8400_R34MNBST_SHIFT,
+ 1, 0),
+
+/* LOMIX */
+SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LLI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LR12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER3,
+ WM8400_LL12LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRI3LOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER5,
+ WM8400_LRBLOVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* ROMIX */
+SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RRI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RL12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8400_OUTPUT_MIXER4,
+ WM8400_RR12ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RLI3ROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RLBROVOL_SHIFT, 7, 0, out_mix_tlv),
+SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8400_OUTPUT_MIXER6,
+ WM8400_RRBROVOL_SHIFT, 7, 0, out_mix_tlv),
+
+/* LOUT */
+WM8400_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8400_LEFT_OUTPUT_VOLUME,
+ WM8400_LOUTVOL_SHIFT, WM8400_LOUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOUT ZC", WM8400_LEFT_OUTPUT_VOLUME, WM8400_LOZC_SHIFT, 1, 0),
+
+/* ROUT */
+WM8400_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8400_RIGHT_OUTPUT_VOLUME,
+ WM8400_ROUTVOL_SHIFT, WM8400_ROUTVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROUT ZC", WM8400_RIGHT_OUTPUT_VOLUME, WM8400_ROZC_SHIFT, 1, 0),
+
+/* LOPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8400_LEFT_OPGA_VOLUME,
+ WM8400_LOPGAVOL_SHIFT, WM8400_LOPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("LOPGA ZC Switch", WM8400_LEFT_OPGA_VOLUME,
+ WM8400_LOPGAZC_SHIFT, 1, 0),
+
+/* ROPGA */
+WM8400_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8400_RIGHT_OPGA_VOLUME,
+ WM8400_ROPGAVOL_SHIFT, WM8400_ROPGAVOL_MASK, 0, out_pga_tlv),
+SOC_SINGLE("ROPGA ZC Switch", WM8400_RIGHT_OPGA_VOLUME,
+ WM8400_ROPGAZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LOPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("LOP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_LOATTN_SHIFT, 1, 0),
+SOC_SINGLE("RON Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_RONMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Mute Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_ROPMUTE_SHIFT, 1, 0),
+SOC_SINGLE("ROP Attenuation Switch", WM8400_LINE_OUTPUTS_VOLUME,
+ WM8400_ROATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT3 Mute Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT3MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT3 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT3ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("OUT4 Mute Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT4MUTE_SHIFT, 1, 0),
+SOC_SINGLE("OUT4 Attenuation Switch", WM8400_OUT3_4_VOLUME,
+ WM8400_OUT4ATTN_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Mode Switch", WM8400_CLASSD1,
+ WM8400_CDMODE_SHIFT, 1, 0),
+
+SOC_SINGLE("Speaker Output Attenuation Volume", WM8400_SPEAKER_VOLUME,
+ WM8400_SPKATTN_SHIFT, WM8400_SPKATTN_MASK, 0),
+SOC_SINGLE("Speaker DC Boost Volume", WM8400_CLASSD3,
+ WM8400_DCGAIN_SHIFT, 6, 0),
+SOC_SINGLE("Speaker AC Boost Volume", WM8400_CLASSD3,
+ WM8400_ACGAIN_SHIFT, 6, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume",
+ WM8400_LEFT_DAC_DIGITAL_VOLUME, WM8400_DACL_VOL_SHIFT,
+ 127, 0, out_dac_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume",
+ WM8400_RIGHT_DAC_DIGITAL_VOLUME, WM8400_DACR_VOL_SHIFT,
+ 127, 0, out_dac_tlv),
+
+SOC_ENUM("Left Digital Sidetone", wm8400_left_digital_sidetone_enum),
+SOC_ENUM("Right Digital Sidetone", wm8400_right_digital_sidetone_enum),
+
+SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADCL_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8400_DIGITAL_SIDE_TONE,
+ WM8400_ADCR_DAC_SVOL_SHIFT, 15, 0, out_sidetone_tlv),
+
+SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8400_ADC_CTRL,
+ WM8400_ADC_HPF_ENA_SHIFT, 1, 0),
+
+SOC_ENUM("ADC HPF Mode", wm8400_right_adcmode_enum),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume",
+ WM8400_LEFT_ADC_DIGITAL_VOLUME,
+ WM8400_ADCL_VOL_SHIFT,
+ WM8400_ADCL_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume",
+ WM8400_RIGHT_ADC_DIGITAL_VOLUME,
+ WM8400_ADCR_VOL_SHIFT,
+ WM8400_ADCR_VOL_MASK,
+ 0,
+ in_adc_tlv),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN12 Volume",
+ WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LIN12VOL_SHIFT,
+ WM8400_LIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN12 ZC Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN12 Mute Switch", WM8400_LEFT_LINE_INPUT_1_2_VOLUME,
+ WM8400_LI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("LIN34 Volume",
+ WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LIN34VOL_SHIFT,
+ WM8400_LIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("LIN34 ZC Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("LIN34 Mute Switch", WM8400_LEFT_LINE_INPUT_3_4_VOLUME,
+ WM8400_LI34MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN12 Volume",
+ WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RIN12VOL_SHIFT,
+ WM8400_RIN12VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN12 ZC Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RI12ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN12 Mute Switch", WM8400_RIGHT_LINE_INPUT_1_2_VOLUME,
+ WM8400_RI12MUTE_SHIFT, 1, 0),
+
+WM8400_OUTPGA_SINGLE_R_TLV("RIN34 Volume",
+ WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RIN34VOL_SHIFT,
+ WM8400_RIN34VOL_MASK,
+ 0,
+ in_pga_tlv),
+
+SOC_SINGLE("RIN34 ZC Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RI34ZC_SHIFT, 1, 0),
+
+SOC_SINGLE("RIN34 Mute Switch", WM8400_RIGHT_LINE_INPUT_3_4_VOLUME,
+ WM8400_RI34MUTE_SHIFT, 1, 0),
+
+};
+
+/* add non dapm controls */
+static int wm8400_add_controls(struct snd_soc_codec *codec)
+{
+ return snd_soc_add_controls(codec, wm8400_snd_controls,
+ ARRAY_SIZE(wm8400_snd_controls));
+}
+
+/*
+ * _DAPM_ Controls
+ */
+
+static int inmixer_event (struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol *kcontrol, int event)
+{
+ u16 reg, fakepower;
+
+ reg = wm8400_read(w->codec, WM8400_POWER_MANAGEMENT_2);
+ fakepower = wm8400_read(w->codec, WM8400_INTDRIVBITS);
+
+ if (fakepower & ((1 << WM8400_INMIXL_PWR) |
+ (1 << WM8400_AINLMUX_PWR))) {
+ reg |= WM8400_AINL_ENA;
+ } else {
+ reg &= ~WM8400_AINL_ENA;
+ }
+
+ if (fakepower & ((1 << WM8400_INMIXR_PWR) |
+ (1 << WM8400_AINRMUX_PWR))) {
+ reg |= WM8400_AINR_ENA;
+ } else {
+ reg &= ~WM8400_AINL_ENA;
+ }
+ wm8400_write(w->codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+ return 0;
+}
+
+static int outmixer_event (struct snd_soc_dapm_widget *w,
+ struct snd_kcontrol * kcontrol, int event)
+{
+ struct soc_mixer_control *mc =
+ (struct soc_mixer_control *)kcontrol->private_value;
+ u32 reg_shift = mc->shift;
+ int ret = 0;
+ u16 reg;
+
+ switch (reg_shift) {
+ case WM8400_SPEAKER_MIXER | (WM8400_LDSPK << 8) :
+ reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER1);
+ if (reg & WM8400_LDLO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 1 LDLO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_SPEAKER_MIXER | (WM8400_RDSPK << 8):
+ reg = wm8400_read(w->codec, WM8400_OUTPUT_MIXER2);
+ if (reg & WM8400_RDRO) {
+ printk(KERN_WARNING
+ "Cannot set as Output Mixer 2 RDRO Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_OUTPUT_MIXER1 | (WM8400_LDLO << 8):
+ reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+ if (reg & WM8400_LDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer LDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ case WM8400_OUTPUT_MIXER2 | (WM8400_RDRO << 8):
+ reg = wm8400_read(w->codec, WM8400_SPEAKER_MIXER);
+ if (reg & WM8400_RDSPK) {
+ printk(KERN_WARNING
+ "Cannot set as Speaker Mixer RDSPK Set\n");
+ ret = -1;
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* INMIX dB values */
+static const unsigned int in_mix_tlv[] = {
+ TLV_DB_RANGE_HEAD(1),
+ 0,7, TLV_DB_LINEAR_ITEM(-1200, 600),
+};
+
+/* Left In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_lin12_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN1 Switch", WM8400_INPUT_MIXER2, WM8400_LMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN2 Switch", WM8400_INPUT_MIXER2, WM8400_LMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_lin34_pga_controls[] = {
+SOC_DAPM_SINGLE("LIN3 Switch", WM8400_INPUT_MIXER2, WM8400_LMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LIN4 Switch", WM8400_INPUT_MIXER2, WM8400_LMP4_SHIFT, 1, 0),
+};
+
+/* Right In PGA Connections */
+static const struct snd_kcontrol_new wm8400_dapm_rin12_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN1 Switch", WM8400_INPUT_MIXER2, WM8400_RMN1_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN2 Switch", WM8400_INPUT_MIXER2, WM8400_RMP2_SHIFT, 1, 0),
+};
+
+static const struct snd_kcontrol_new wm8400_dapm_rin34_pga_controls[] = {
+SOC_DAPM_SINGLE("RIN3 Switch", WM8400_INPUT_MIXER2, WM8400_RMN3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RIN4 Switch", WM8400_INPUT_MIXER2, WM8400_RMP4_SHIFT, 1, 0),
+};
+
+/* INMIXL */
+static const struct snd_kcontrol_new wm8400_dapm_inmixl_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8400_INPUT_MIXER3,
+ WM8400_LDBVOL_SHIFT, WM8400_LDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8400_INPUT_MIXER5, WM8400_LI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("LINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+ 1, 0),
+SOC_DAPM_SINGLE("LINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+ 1, 0),
+};
+
+/* INMIXR */
+static const struct snd_kcontrol_new wm8400_dapm_inmixr_controls[] = {
+SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8400_INPUT_MIXER4,
+ WM8400_RDBVOL_SHIFT, WM8400_RDBVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8400_INPUT_MIXER6, WM8400_RI2BVOL_SHIFT,
+ 7, 0, in_mix_tlv),
+SOC_DAPM_SINGLE("RINPGA12 Switch", WM8400_INPUT_MIXER3, WM8400_L12MNB_SHIFT,
+ 1, 0),
+SOC_DAPM_SINGLE("RINPGA34 Switch", WM8400_INPUT_MIXER3, WM8400_L34MNB_SHIFT,
+ 1, 0),
+};
+
+/* AINLMUX */
+static const char *wm8400_ainlmux[] =
+ {"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"};
+
+static const struct soc_enum wm8400_ainlmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINLMODE_SHIFT,
+ ARRAY_SIZE(wm8400_ainlmux), wm8400_ainlmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainlmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainlmux_enum);
+
+/* DIFFINL */
+
+/* AINRMUX */
+static const char *wm8400_ainrmux[] =
+ {"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"};
+
+static const struct soc_enum wm8400_ainrmux_enum =
+SOC_ENUM_SINGLE( WM8400_INPUT_MIXER1, WM8400_AINRMODE_SHIFT,
+ ARRAY_SIZE(wm8400_ainrmux), wm8400_ainrmux);
+
+static const struct snd_kcontrol_new wm8400_dapm_ainrmux_controls =
+SOC_DAPM_ENUM("Route", wm8400_ainrmux_enum);
+
+/* RXVOICE */
+static const struct snd_kcontrol_new wm8400_dapm_rxvoice_controls[] = {
+SOC_DAPM_SINGLE_TLV("LIN4/RXN", WM8400_INPUT_MIXER5, WM8400_LR4BVOL_SHIFT,
+ WM8400_LR4BVOL_MASK, 0, in_mix_tlv),
+SOC_DAPM_SINGLE_TLV("RIN4/RXP", WM8400_INPUT_MIXER6, WM8400_RL4BVOL_SHIFT,
+ WM8400_RL4BVOL_MASK, 0, in_mix_tlv),
+};
+
+/* LOMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lomix_controls[] = {
+SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LRBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LLBLO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LRI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LLI3LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LR12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LL12LO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8400_OUTPUT_MIXER1,
+ WM8400_LDLO_SHIFT, 1, 0),
+};
+
+/* ROMIX */
+static const struct snd_kcontrol_new wm8400_dapm_romix_controls[] = {
+SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RLBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RRBRO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RLI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RRI3RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RL12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RR12RO_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8400_OUTPUT_MIXER2,
+ WM8400_RDRO_SHIFT, 1, 0),
+};
+
+/* LONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lonmix_controls[] = {
+SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LLOPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LROPGALON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8400_LINE_MIXER1,
+ WM8400_LOPLON_SHIFT, 1, 0),
+};
+
+/* LOPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_lopmix_controls[] = {
+SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER1,
+ WM8400_LR12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER1,
+ WM8400_LL12LOP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8400_LINE_MIXER1,
+ WM8400_LLOPGALOP_SHIFT, 1, 0),
+};
+
+/* RONMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ronmix_controls[] = {
+SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RROPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RLOPGARON_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8400_LINE_MIXER2,
+ WM8400_ROPRON_SHIFT, 1, 0),
+};
+
+/* ROPMIX */
+static const struct snd_kcontrol_new wm8400_dapm_ropmix_controls[] = {
+SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8400_LINE_MIXER2,
+ WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8400_LINE_MIXER2,
+ WM8400_RR12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8400_LINE_MIXER2,
+ WM8400_RROPGAROP_SHIFT, 1, 0),
+};
+
+/* OUT3MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out3mix_controls[] = {
+SOC_DAPM_SINGLE("OUT3MIX LIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+ WM8400_LI4O3_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8400_OUT3_4_MIXER,
+ WM8400_LPGAO3_SHIFT, 1, 0),
+};
+
+/* OUT4MIX */
+static const struct snd_kcontrol_new wm8400_dapm_out4mix_controls[] = {
+SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8400_OUT3_4_MIXER,
+ WM8400_RPGAO4_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("OUT4MIX RIN4/RXP Bypass Switch", WM8400_OUT3_4_MIXER,
+ WM8400_RI4O4_SHIFT, 1, 0),
+};
+
+/* SPKMIX */
+static const struct snd_kcontrol_new wm8400_dapm_spkmix_controls[] = {
+SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LI2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LB2SPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LOPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8400_SPEAKER_MIXER,
+ WM8400_LDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RDSPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8400_SPEAKER_MIXER,
+ WM8400_ROPGASPK_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RL12ROP_SHIFT, 1, 0),
+SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8400_SPEAKER_MIXER,
+ WM8400_RI2SPK_SHIFT, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget wm8400_dapm_widgets[] = {
+/* Input Side */
+/* Input Lines */
+SND_SOC_DAPM_INPUT("LIN1"),
+SND_SOC_DAPM_INPUT("LIN2"),
+SND_SOC_DAPM_INPUT("LIN3"),
+SND_SOC_DAPM_INPUT("LIN4/RXN"),
+SND_SOC_DAPM_INPUT("RIN3"),
+SND_SOC_DAPM_INPUT("RIN4/RXP"),
+SND_SOC_DAPM_INPUT("RIN1"),
+SND_SOC_DAPM_INPUT("RIN2"),
+SND_SOC_DAPM_INPUT("Internal ADC Source"),
+
+/* DACs */
+SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8400_POWER_MANAGEMENT_2,
+ WM8400_ADCL_ENA_SHIFT, 0),
+SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8400_POWER_MANAGEMENT_2,
+ WM8400_ADCR_ENA_SHIFT, 0),
+
+/* Input PGAs */
+SND_SOC_DAPM_MIXER("LIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_LIN12_ENA_SHIFT,
+ 0, &wm8400_dapm_lin12_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lin12_pga_controls)),
+SND_SOC_DAPM_MIXER("LIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_LIN34_ENA_SHIFT,
+ 0, &wm8400_dapm_lin34_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lin34_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN12 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_RIN12_ENA_SHIFT,
+ 0, &wm8400_dapm_rin12_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_rin12_pga_controls)),
+SND_SOC_DAPM_MIXER("RIN34 PGA", WM8400_POWER_MANAGEMENT_2,
+ WM8400_RIN34_ENA_SHIFT,
+ 0, &wm8400_dapm_rin34_pga_controls[0],
+ ARRAY_SIZE(wm8400_dapm_rin34_pga_controls)),
+
+/* INMIXL */
+SND_SOC_DAPM_MIXER_E("INMIXL", WM8400_INTDRIVBITS, WM8400_INMIXL_PWR, 0,
+ &wm8400_dapm_inmixl_controls[0],
+ ARRAY_SIZE(wm8400_dapm_inmixl_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINLMUX */
+SND_SOC_DAPM_MUX_E("AILNMUX", WM8400_INTDRIVBITS, WM8400_AINLMUX_PWR, 0,
+ &wm8400_dapm_ainlmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* INMIXR */
+SND_SOC_DAPM_MIXER_E("INMIXR", WM8400_INTDRIVBITS, WM8400_INMIXR_PWR, 0,
+ &wm8400_dapm_inmixr_controls[0],
+ ARRAY_SIZE(wm8400_dapm_inmixr_controls),
+ inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* AINRMUX */
+SND_SOC_DAPM_MUX_E("AIRNMUX", WM8400_INTDRIVBITS, WM8400_AINRMUX_PWR, 0,
+ &wm8400_dapm_ainrmux_controls, inmixer_event,
+ SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD),
+
+/* Output Side */
+/* DACs */
+SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8400_POWER_MANAGEMENT_3,
+ WM8400_DACL_ENA_SHIFT, 0),
+SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8400_POWER_MANAGEMENT_3,
+ WM8400_DACR_ENA_SHIFT, 0),
+
+/* LOMIX */
+SND_SOC_DAPM_MIXER_E("LOMIX", WM8400_POWER_MANAGEMENT_3,
+ WM8400_LOMIX_ENA_SHIFT,
+ 0, &wm8400_dapm_lomix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lomix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LONMIX */
+SND_SOC_DAPM_MIXER("LONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LON_ENA_SHIFT,
+ 0, &wm8400_dapm_lonmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lonmix_controls)),
+
+/* LOPMIX */
+SND_SOC_DAPM_MIXER("LOPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_LOP_ENA_SHIFT,
+ 0, &wm8400_dapm_lopmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_lopmix_controls)),
+
+/* OUT3MIX */
+SND_SOC_DAPM_MIXER("OUT3MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT3_ENA_SHIFT,
+ 0, &wm8400_dapm_out3mix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_out3mix_controls)),
+
+/* SPKMIX */
+SND_SOC_DAPM_MIXER_E("SPKMIX", WM8400_POWER_MANAGEMENT_1, WM8400_SPK_ENA_SHIFT,
+ 0, &wm8400_dapm_spkmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_spkmix_controls), outmixer_event,
+ SND_SOC_DAPM_PRE_REG),
+
+/* OUT4MIX */
+SND_SOC_DAPM_MIXER("OUT4MIX", WM8400_POWER_MANAGEMENT_1, WM8400_OUT4_ENA_SHIFT,
+ 0, &wm8400_dapm_out4mix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_out4mix_controls)),
+
+/* ROPMIX */
+SND_SOC_DAPM_MIXER("ROPMIX", WM8400_POWER_MANAGEMENT_3, WM8400_ROP_ENA_SHIFT,
+ 0, &wm8400_dapm_ropmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_ropmix_controls)),
+
+/* RONMIX */
+SND_SOC_DAPM_MIXER("RONMIX", WM8400_POWER_MANAGEMENT_3, WM8400_RON_ENA_SHIFT,
+ 0, &wm8400_dapm_ronmix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_ronmix_controls)),
+
+/* ROMIX */
+SND_SOC_DAPM_MIXER_E("ROMIX", WM8400_POWER_MANAGEMENT_3,
+ WM8400_ROMIX_ENA_SHIFT,
+ 0, &wm8400_dapm_romix_controls[0],
+ ARRAY_SIZE(wm8400_dapm_romix_controls),
+ outmixer_event, SND_SOC_DAPM_PRE_REG),
+
+/* LOUT PGA */
+SND_SOC_DAPM_PGA("LOUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_LOUT_ENA_SHIFT,
+ 0, NULL, 0),
+
+/* ROUT PGA */
+SND_SOC_DAPM_PGA("ROUT PGA", WM8400_POWER_MANAGEMENT_1, WM8400_ROUT_ENA_SHIFT,
+ 0, NULL, 0),
+
+/* LOPGA */
+SND_SOC_DAPM_PGA("LOPGA", WM8400_POWER_MANAGEMENT_3, WM8400_LOPGA_ENA_SHIFT, 0,
+ NULL, 0),
+
+/* ROPGA */
+SND_SOC_DAPM_PGA("ROPGA", WM8400_POWER_MANAGEMENT_3, WM8400_ROPGA_ENA_SHIFT, 0,
+ NULL, 0),
+
+/* MICBIAS */
+SND_SOC_DAPM_MICBIAS("MICBIAS", WM8400_POWER_MANAGEMENT_1,
+ WM8400_MIC1BIAS_ENA_SHIFT, 0),
+
+SND_SOC_DAPM_OUTPUT("LON"),
+SND_SOC_DAPM_OUTPUT("LOP"),
+SND_SOC_DAPM_OUTPUT("OUT3"),
+SND_SOC_DAPM_OUTPUT("LOUT"),
+SND_SOC_DAPM_OUTPUT("SPKN"),
+SND_SOC_DAPM_OUTPUT("SPKP"),
+SND_SOC_DAPM_OUTPUT("ROUT"),
+SND_SOC_DAPM_OUTPUT("OUT4"),
+SND_SOC_DAPM_OUTPUT("ROP"),
+SND_SOC_DAPM_OUTPUT("RON"),
+
+SND_SOC_DAPM_OUTPUT("Internal DAC Sink"),
+};
+
+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, "Left DAC"},
+ {"Internal DAC Sink", NULL, "Right DAC"},
+
+ /* Make ADCs turn on when recording
+ * even if not mixed from any inputs */
+ {"Left ADC", NULL, "Internal ADC Source"},
+ {"Right ADC", NULL, "Internal ADC Source"},
+
+ /* Input Side */
+ /* LIN12 PGA */
+ {"LIN12 PGA", "LIN1 Switch", "LIN1"},
+ {"LIN12 PGA", "LIN2 Switch", "LIN2"},
+ /* LIN34 PGA */
+ {"LIN34 PGA", "LIN3 Switch", "LIN3"},
+ {"LIN34 PGA", "LIN4 Switch", "LIN4/RXN"},
+ /* INMIXL */
+ {"INMIXL", "Record Left Volume", "LOMIX"},
+ {"INMIXL", "LIN2 Volume", "LIN2"},
+ {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"},
+ {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"},
+ /* AILNMUX */
+ {"AILNMUX", "INMIXL Mix", "INMIXL"},
+ {"AILNMUX", "DIFFINL Mix", "LIN12 PGA"},
+ {"AILNMUX", "DIFFINL Mix", "LIN34 PGA"},
+ {"AILNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AILNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Left ADC", NULL, "AILNMUX"},
+
+ /* RIN12 PGA */
+ {"RIN12 PGA", "RIN1 Switch", "RIN1"},
+ {"RIN12 PGA", "RIN2 Switch", "RIN2"},
+ /* RIN34 PGA */
+ {"RIN34 PGA", "RIN3 Switch", "RIN3"},
+ {"RIN34 PGA", "RIN4 Switch", "RIN4/RXP"},
+ /* INMIXL */
+ {"INMIXR", "Record Right Volume", "ROMIX"},
+ {"INMIXR", "RIN2 Volume", "RIN2"},
+ {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"},
+ {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"},
+ /* AIRNMUX */
+ {"AIRNMUX", "INMIXR Mix", "INMIXR"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN12 PGA"},
+ {"AIRNMUX", "DIFFINR Mix", "RIN34 PGA"},
+ {"AIRNMUX", "RXVOICE Mix", "LIN4/RXN"},
+ {"AIRNMUX", "RXVOICE Mix", "RIN4/RXP"},
+ /* ADC */
+ {"Right ADC", NULL, "AIRNMUX"},
+
+ /* LOMIX */
+ {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"},
+ {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"},
+ {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"LOMIX", "LOMIX Right ADC Bypass Switch", "AIRNMUX"},
+ {"LOMIX", "LOMIX Left ADC Bypass Switch", "AILNMUX"},
+ {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"},
+
+ /* ROMIX */
+ {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"},
+ {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"},
+ {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"},
+ {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"},
+ {"ROMIX", "ROMIX Right ADC Bypass Switch", "AIRNMUX"},
+ {"ROMIX", "ROMIX Left ADC Bypass Switch", "AILNMUX"},
+ {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"},
+
+ /* SPKMIX */
+ {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"},
+ {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"},
+ {"SPKMIX", "SPKMIX LADC Bypass Switch", "AILNMUX"},
+ {"SPKMIX", "SPKMIX RADC Bypass Switch", "AIRNMUX"},
+ {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"},
+ {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"},
+ {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"},
+ {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"},
+
+ /* LONMIX */
+ {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"},
+
+ /* LOPMIX */
+ {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"},
+
+ /* OUT3MIX */
+ {"OUT3MIX", "OUT3MIX LIN4/RXP Bypass Switch", "LIN4/RXN"},
+ {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"},
+
+ /* OUT4MIX */
+ {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"},
+ {"OUT4MIX", "OUT4MIX RIN4/RXP Bypass Switch", "RIN4/RXP"},
+
+ /* RONMIX */
+ {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"},
+ {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"},
+ {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"},
+
+ /* ROPMIX */
+ {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"},
+ {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"},
+
+ /* Out Mixer PGAs */
+ {"LOPGA", NULL, "LOMIX"},
+ {"ROPGA", NULL, "ROMIX"},
+
+ {"LOUT PGA", NULL, "LOMIX"},
+ {"ROUT PGA", NULL, "ROMIX"},
+
+ /* Output Pins */
+ {"LON", NULL, "LONMIX"},
+ {"LOP", NULL, "LOPMIX"},
+ {"OUT3", NULL, "OUT3MIX"},
+ {"LOUT", NULL, "LOUT PGA"},
+ {"SPKN", NULL, "SPKMIX"},
+ {"ROUT", NULL, "ROUT PGA"},
+ {"OUT4", NULL, "OUT4MIX"},
+ {"ROP", NULL, "ROPMIX"},
+ {"RON", NULL, "RONMIX"},
+};
+
+static int wm8400_add_widgets(struct snd_soc_codec *codec)
+{
+ snd_soc_dapm_new_controls(codec, wm8400_dapm_widgets,
+ ARRAY_SIZE(wm8400_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map));
+
+ snd_soc_dapm_new_widgets(codec);
+ return 0;
+}
+
+/*
+ * Clock after FLL and dividers
+ */
+static int wm8400_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 wm8400_priv *wm8400 = codec->private_data;
+
+ wm8400->sysclk = freq;
+ return 0;
+}
+
+struct fll_factors {
+ u16 n;
+ u16 k;
+ u16 outdiv;
+ u16 fratio;
+ u16 freq_ref;
+};
+
+#define FIXED_FLL_SIZE ((1 << 16) * 10)
+
+static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors,
+ unsigned int Fref, unsigned int Fout)
+{
+ u64 Kpart;
+ unsigned int K, Nmod, target;
+
+ factors->outdiv = 2;
+ while (Fout * factors->outdiv < 90000000 ||
+ Fout * factors->outdiv > 100000000) {
+ factors->outdiv *= 2;
+ if (factors->outdiv > 32) {
+ dev_err(wm8400->wm8400->dev,
+ "Unsupported FLL output frequency %dHz\n",
+ Fout);
+ return -EINVAL;
+ }
+ }
+ target = Fout * factors->outdiv;
+ factors->outdiv = factors->outdiv >> 2;
+
+ if (Fref < 48000)
+ factors->freq_ref = 1;
+ else
+ factors->freq_ref = 0;
+
+ if (Fref < 1000000)
+ factors->fratio = 9;
+ else
+ factors->fratio = 0;
+
+ /* Ensure we have a fractional part */
+ do {
+ if (Fref < 1000000)
+ factors->fratio--;
+ else
+ factors->fratio++;
+
+ if (factors->fratio < 1 || factors->fratio > 8) {
+ dev_err(wm8400->wm8400->dev,
+ "Unable to calculate FRATIO\n");
+ return -EINVAL;
+ }
+
+ factors->n = target / (Fref * factors->fratio);
+ Nmod = target % (Fref * factors->fratio);
+ } while (Nmod == 0);
+
+ /* Calculate fractional part - scale up so we can round. */
+ Kpart = FIXED_FLL_SIZE * (long long)Nmod;
+
+ do_div(Kpart, (Fref * factors->fratio));
+
+ K = Kpart & 0xFFFFFFFF;
+
+ if ((K % 10) >= 5)
+ K += 5;
+
+ /* Move down to proper range now rounding is done */
+ factors->k = K / 10;
+
+ dev_dbg(wm8400->wm8400->dev,
+ "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n",
+ Fref, Fout,
+ factors->n, factors->k, factors->fratio, factors->outdiv);
+
+ return 0;
+}
+
+static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id,
+ unsigned int freq_in, unsigned int freq_out)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ struct wm8400_priv *wm8400 = codec->private_data;
+ struct fll_factors factors;
+ int ret;
+ u16 reg;
+
+ if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out)
+ return 0;
+
+ if (freq_out != 0) {
+ ret = fll_factors(wm8400, &factors, freq_in, freq_out);
+ if (ret != 0)
+ return ret;
+ }
+
+ wm8400->fll_out = freq_out;
+ wm8400->fll_in = freq_in;
+
+ /* We *must* disable the FLL before any changes */
+ reg = wm8400_read(codec, WM8400_POWER_MANAGEMENT_2);
+ reg &= ~WM8400_FLL_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_2, reg);
+
+ reg = wm8400_read(codec, WM8400_FLL_CONTROL_1);
+ reg &= ~WM8400_FLL_OSC_ENA;
+ wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+ if (freq_out == 0)
+ return 0;
+
+ reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK);
+ reg |= WM8400_FLL_FRAC | factors.fratio;
+ reg |= factors.freq_ref << WM8400_FLL_REF_FREQ_SHIFT;
+ wm8400_write(codec, WM8400_FLL_CONTROL_1, reg);
+
+ wm8400_write(codec, WM8400_FLL_CONTROL_2, factors.k);
+ wm8400_write(codec, WM8400_FLL_CONTROL_3, factors.n);
+
+ reg = wm8400_read(codec, WM8400_FLL_CONTROL_4);
+ reg &= WM8400_FLL_OUTDIV_MASK;
+ reg |= factors.outdiv;
+ wm8400_write(codec, WM8400_FLL_CONTROL_4, reg);
+
+ return 0;
+}
+
+/*
+ * Sets ADC and Voice DAC format.
+ */
+static int wm8400_set_dai_fmt(struct snd_soc_dai *codec_dai,
+ unsigned int fmt)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 audio1, audio3;
+
+ audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+ audio3 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_3);
+
+ /* set master/slave audio interface */
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ audio3 &= ~WM8400_AIF_MSTR1;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ audio3 |= WM8400_AIF_MSTR1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ audio1 &= ~WM8400_AIF_FMT_MASK;
+
+ /* interface format */
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ audio1 |= WM8400_AIF_FMT_I2S;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_RIGHT_J:
+ audio1 |= WM8400_AIF_FMT_RIGHTJ;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ audio1 |= WM8400_AIF_FMT_LEFTJ;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_A:
+ audio1 |= WM8400_AIF_FMT_DSP;
+ audio1 &= ~WM8400_AIF_LRCLK_INV;
+ break;
+ case SND_SOC_DAIFMT_DSP_B:
+ audio1 |= WM8400_AIF_FMT_DSP | WM8400_AIF_LRCLK_INV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_3, audio3);
+ return 0;
+}
+
+static int wm8400_set_dai_clkdiv(struct snd_soc_dai *codec_dai,
+ int div_id, int div)
+{
+ struct snd_soc_codec *codec = codec_dai->codec;
+ u16 reg;
+
+ switch (div_id) {
+ case WM8400_MCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_MCLK_DIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_DACCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_DAC_CLKDIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_ADCCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_2) &
+ ~WM8400_ADC_CLKDIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_2, reg | div);
+ break;
+ case WM8400_BCLK_DIV:
+ reg = wm8400_read(codec, WM8400_CLOCKING_1) &
+ ~WM8400_BCLK_DIV_MASK;
+ wm8400_write(codec, WM8400_CLOCKING_1, reg | div);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * Set PCM DAI bit size and sample rate.
+ */
+static int wm8400_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_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+ u16 audio1 = wm8400_read(codec, WM8400_AUDIO_INTERFACE_1);
+
+ audio1 &= ~WM8400_AIF_WL_MASK;
+ /* bit size */
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S16_LE:
+ break;
+ case SNDRV_PCM_FORMAT_S20_3LE:
+ audio1 |= WM8400_AIF_WL_20BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S24_LE:
+ audio1 |= WM8400_AIF_WL_24BITS;
+ break;
+ case SNDRV_PCM_FORMAT_S32_LE:
+ audio1 |= WM8400_AIF_WL_32BITS;
+ break;
+ }
+
+ wm8400_write(codec, WM8400_AUDIO_INTERFACE_1, audio1);
+ return 0;
+}
+
+static int wm8400_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_codec *codec = dai->codec;
+ u16 val = wm8400_read(codec, WM8400_DAC_CTRL) & ~WM8400_DAC_MUTE;
+
+ if (mute)
+ wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+ else
+ wm8400_write(codec, WM8400_DAC_CTRL, val);
+
+ return 0;
+}
+
+/* TODO: set bias for best performance at standby */
+static int wm8400_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ struct wm8400_priv *wm8400 = codec->private_data;
+ u16 val;
+ int ret;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+
+ case SND_SOC_BIAS_PREPARE:
+ /* VMID=2*50k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+ ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x2);
+ break;
+
+ case SND_SOC_BIAS_STANDBY:
+ if (codec->bias_level == SND_SOC_BIAS_OFF) {
+ ret = regulator_bulk_enable(ARRAY_SIZE(power),
+ &power[0]);
+ if (ret != 0) {
+ dev_err(wm8400->wm8400->dev,
+ "Failed to enable regulators: %d\n",
+ ret);
+ return ret;
+ }
+
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1,
+ WM8400_CODEC_ENA | WM8400_SYSCLK_ENA);
+
+ /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL);
+
+ msleep(50);
+
+ /* Enable VREF & VMID at 2x50k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ val |= 0x2 | WM8400_VREF_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ /* Enable BUFIOEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL |
+ WM8400_BUFIOEN);
+
+ /* disable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_BUFIOEN);
+ }
+
+ /* VMID=2*300k */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1) &
+ ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val | 0x4);
+ break;
+
+ case SND_SOC_BIAS_OFF:
+ /* Enable POBCTRL and SOFT_ST */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_POBCTRL | WM8400_BUFIOEN);
+
+ /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */
+ wm8400_write(codec, WM8400_ANTIPOP2, WM8400_SOFTST |
+ WM8400_BUFDCOPEN | WM8400_POBCTRL |
+ WM8400_BUFIOEN);
+
+ /* mute DAC */
+ val = wm8400_read(codec, WM8400_DAC_CTRL);
+ wm8400_write(codec, WM8400_DAC_CTRL, val | WM8400_DAC_MUTE);
+
+ /* Enable any disabled outputs */
+ val = wm8400_read(codec, WM8400_POWER_MANAGEMENT_1);
+ val |= WM8400_SPK_ENA | WM8400_OUT3_ENA |
+ WM8400_OUT4_ENA | WM8400_LOUT_ENA |
+ WM8400_ROUT_ENA;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ /* Disable VMID */
+ val &= ~WM8400_VMID_MODE_MASK;
+ wm8400_write(codec, WM8400_POWER_MANAGEMENT_1, val);
+
+ msleep(300);
+
+ /* Enable all output discharge bits */
+ &n