808 lines
19 KiB
C
808 lines
19 KiB
C
/*
|
|
* wm8772.c -- WM8772 ALSA Soc Audio driver
|
|
*
|
|
* Copyright 2005 Wolfson Microelectronics PLC.
|
|
* Author: Liam Girdwood
|
|
* liam.girdwood@wolfsonmicro.com or linux@wolfsonmicro.com
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/version.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/pm.h>
|
|
#include <linux/i2c.h>
|
|
|
|
#include <sound/driver.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/initval.h>
|
|
|
|
#include "wm8772.h"
|
|
|
|
#define AUDIO_NAME "WM8772"
|
|
#define WM8772_VERSION "0.3"
|
|
|
|
/*
|
|
* wm8772 register cache
|
|
* We can't read the WM8772 register space when we
|
|
* are using 2 wire for device control, so we cache them instead.
|
|
*/
|
|
static const u16 wm8772_reg[] = {
|
|
0x00ff, 0x00ff, 0x0120, 0x0000, /* 0 */
|
|
0x00ff, 0x00ff, 0x00ff, 0x00ff, /* 4 */
|
|
0x00ff, 0x0000, 0x0080, 0x0040, /* 8 */
|
|
0x0000
|
|
};
|
|
|
|
#define WM8772_DAIFMT \
|
|
(SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_RIGHT_J | \
|
|
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_IB_NF)
|
|
|
|
#define WM8772_DIR \
|
|
(SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
|
|
|
|
#define WM8772_PRATES \
|
|
(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
|
|
SNDRV_PCM_RATE_96000 | SNDRV_PCM_RATE_192000)
|
|
|
|
#define WM8772_CRATES \
|
|
(SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\
|
|
SNDRV_PCM_RATE_96000)
|
|
|
|
static struct snd_soc_dai_mode wm8772_modes[] = {
|
|
/* common codec frame and clock master modes */
|
|
/* 32k */
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 768,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 512,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 384,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 256,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 192,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_32000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 128,
|
|
.bfs = 64,
|
|
},
|
|
|
|
/* 44.1k */
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 768,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 512,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 384,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 256,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 192,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_44100,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 128,
|
|
.bfs = 64,
|
|
},
|
|
|
|
/* 48k */
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 768,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 512,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 384,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 256,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 192,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_48000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 128,
|
|
.bfs = 64,
|
|
},
|
|
|
|
/* 96k */
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_96000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 384,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_96000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 256,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_96000,
|
|
.pcmdir = WM8772_DIR,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 192,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_96000,
|
|
.pcmdir = WM8772_DIR,
|
|
.pcmrate = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 128,
|
|
.bfs = 64,
|
|
},
|
|
|
|
/* 192k */
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_192000,
|
|
.pcmdir = SND_SOC_DAIDIR_PLAYBACK,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 192,
|
|
.bfs = 64,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT | SND_SOC_DAIFMT_CBM_CFM,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = SNDRV_PCM_RATE_192000,
|
|
.pcmdir = SND_SOC_DAIDIR_PLAYBACK,
|
|
.flags = SND_SOC_DAI_BFS_RATE,
|
|
.fs = 128,
|
|
.bfs = 64,
|
|
},
|
|
|
|
/* slave mode */
|
|
{
|
|
.fmt = WM8772_DAIFMT,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = WM8772_PRATES,
|
|
.pcmdir = SND_SOC_DAIDIR_PLAYBACK,
|
|
.fs = SND_SOC_FS_ALL,
|
|
.bfs = SND_SOC_FSB_ALL,
|
|
},
|
|
{
|
|
.fmt = WM8772_DAIFMT,
|
|
.pcmfmt = SNDRV_PCM_FMTBIT_S16_LE,
|
|
.pcmrate = WM8772_CRATES,
|
|
.pcmdir = SND_SOC_DAIDIR_CAPTURE,
|
|
.fs = SND_SOC_FS_ALL,
|
|
.bfs = SND_SOC_FSB_ALL,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* read wm8772 register cache
|
|
*/
|
|
static inline unsigned int wm8772_read_reg_cache(struct snd_soc_codec * codec,
|
|
unsigned int reg)
|
|
{
|
|
u16 *cache = codec->reg_cache;
|
|
if (reg > WM8772_CACHE_REGNUM)
|
|
return -1;
|
|
return cache[reg];
|
|
}
|
|
|
|
/*
|
|
* write wm8772 register cache
|
|
*/
|
|
static inline void wm8772_write_reg_cache(struct snd_soc_codec * codec,
|
|
unsigned int reg, unsigned int value)
|
|
{
|
|
u16 *cache = codec->reg_cache;
|
|
if (reg > WM8772_CACHE_REGNUM)
|
|
return;
|
|
cache[reg] = value;
|
|
}
|
|
|
|
static int wm8772_write(struct snd_soc_codec * codec, unsigned int reg,
|
|
unsigned int value)
|
|
{
|
|
u8 data[2];
|
|
|
|
/* data is
|
|
* D15..D9 WM8772 register offset
|
|
* D8...D0 register data
|
|
*/
|
|
data[0] = (reg << 1) | ((value >> 8) & 0x0001);
|
|
data[1] = value & 0x00ff;
|
|
|
|
wm8772_write_reg_cache (codec, reg, value);
|
|
if (codec->hw_write(codec->control_data, data, 2) == 2)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
#define wm8772_reset(c) wm8772_write(c, WM8772_RESET, 0)
|
|
|
|
/*
|
|
* WM8772 Controls
|
|
*/
|
|
static const char *wm8772_zero_flag[] = {"All Ch", "Ch 1", "Ch 2", "Ch3"};
|
|
|
|
static const struct soc_enum wm8772_enum[] = {
|
|
SOC_ENUM_SINGLE(WM8772_DACCTRL, 0, 4, wm8772_zero_flag),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new wm8772_snd_controls[] = {
|
|
|
|
SOC_SINGLE("Left1 Playback Volume", WM8772_LDAC1VOL, 0, 255, 0),
|
|
SOC_SINGLE("Left2 Playback Volume", WM8772_LDAC2VOL, 0, 255, 0),
|
|
SOC_SINGLE("Left3 Playback Volume", WM8772_LDAC3VOL, 0, 255, 0),
|
|
SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC1VOL, 0, 255, 0),
|
|
SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC2VOL, 0, 255, 0),
|
|
SOC_SINGLE("Right1 Playback Volume", WM8772_RDAC3VOL, 0, 255, 0),
|
|
SOC_SINGLE("Master Playback Volume", WM8772_MDACVOL, 0, 255, 0),
|
|
|
|
SOC_SINGLE("Playback Switch", WM8772_DACCH, 0, 1, 0),
|
|
SOC_SINGLE("Capture Switch", WM8772_ADCCTRL, 2, 1, 0),
|
|
|
|
SOC_SINGLE("Demp1 Playback Switch", WM8772_DACCTRL, 6, 1, 0),
|
|
SOC_SINGLE("Demp2 Playback Switch", WM8772_DACCTRL, 7, 1, 0),
|
|
SOC_SINGLE("Demp3 Playback Switch", WM8772_DACCTRL, 8, 1, 0),
|
|
|
|
SOC_SINGLE("Phase Invert 1 Switch", WM8772_IFACE, 6, 1, 0),
|
|
SOC_SINGLE("Phase Invert 2 Switch", WM8772_IFACE, 7, 1, 0),
|
|
SOC_SINGLE("Phase Invert 3 Switch", WM8772_IFACE, 8, 1, 0),
|
|
|
|
SOC_SINGLE("Playback ZC Switch", WM8772_DACCTRL, 0, 1, 0),
|
|
|
|
SOC_SINGLE("Capture High Pass Switch", WM8772_ADCCTRL, 3, 1, 0),
|
|
};
|
|
|
|
/* add non dapm controls */
|
|
static int wm8772_add_controls(struct snd_soc_codec *codec)
|
|
{
|
|
int err, i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(wm8772_snd_controls); i++) {
|
|
err = snd_ctl_add(codec->card,
|
|
snd_soc_cnew(&wm8772_snd_controls[i],codec, NULL));
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* valid wm8772 mclk frequencies */
|
|
static const int freq_table[5][6] = {
|
|
{4096000, 6144000, 8192000, 12288000, 16384000, 24576000},
|
|
{5644800, 8467000, 11289600, 16934000, 22579200, 33868800},
|
|
{6144000, 9216000, 12288000, 18432000, 24576000, 36864000},
|
|
{12288000, 18432000, 24576000, 36864000, 0, 0},
|
|
{24576000, 36864000, 0, 0, 0},
|
|
};
|
|
|
|
static unsigned int check_freq(int rate, unsigned int freq)
|
|
{
|
|
int i;
|
|
|
|
for(i = 0; i < 6; i++) {
|
|
if(freq == freq_table[i][rate])
|
|
return freq;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int wm8772_config_sysclk(struct snd_soc_codec_dai *dai,
|
|
struct snd_soc_clock_info *info, unsigned int clk)
|
|
{
|
|
switch (info->rate){
|
|
case 32000:
|
|
dai->mclk = check_freq(0, clk);
|
|
break;
|
|
case 44100:
|
|
dai->mclk = check_freq(1, clk);
|
|
break;
|
|
case 48000:
|
|
dai->mclk = check_freq(2, clk);
|
|
break;
|
|
case 96000:
|
|
dai->mclk = check_freq(3, clk);
|
|
break;
|
|
case 192000:
|
|
dai->mclk = check_freq(4, clk);
|
|
break;
|
|
default:
|
|
dai->mclk = 0;
|
|
}
|
|
return dai->mclk;
|
|
}
|
|
|
|
static int wm8772_pcm_prepare(struct snd_pcm_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;
|
|
u16 diface = wm8772_read_reg_cache(codec, WM8772_IFACE) & 0xffc0;
|
|
u16 diface_ctrl = wm8772_read_reg_cache(codec, WM8772_DACRATE) & 0xfe1f;
|
|
u16 aiface = 0;
|
|
u16 aiface_ctrl = wm8772_read_reg_cache(codec, WM8772_ADCCTRL) & 0xfcff;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
|
|
/* set master/slave audio interface */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
diface_ctrl |= 0x0010;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
break;
|
|
}
|
|
|
|
/* interface format */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
diface |= 0x0002;
|
|
break;
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
break;
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
diface |= 0x0001;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
diface |= 0x0003;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
diface |= 0x0007;
|
|
break;
|
|
}
|
|
|
|
/* bit size */
|
|
switch (rtd->codec_dai->dai_runtime.pcmfmt) {
|
|
case SNDRV_PCM_FMTBIT_S16_LE:
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S20_3LE:
|
|
diface |= 0x0010;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_3LE:
|
|
diface |= 0x0020;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S32_LE:
|
|
diface |= 0x0030;
|
|
break;
|
|
}
|
|
|
|
/* clock inversion */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
diface |= 0x0008;
|
|
break;
|
|
}
|
|
|
|
/* set rate */
|
|
switch (rtd->codec_dai->dai_runtime.fs) {
|
|
case 768:
|
|
diface_ctrl |= (0x5 << 6);
|
|
break;
|
|
case 512:
|
|
diface_ctrl |= (0x4 << 6);
|
|
break;
|
|
case 384:
|
|
diface_ctrl |= (0x3 << 6);
|
|
break;
|
|
case 256:
|
|
diface_ctrl |= (0x2 << 6);
|
|
break;
|
|
case 192:
|
|
diface_ctrl |= (0x1 << 6);
|
|
break;
|
|
}
|
|
|
|
wm8772_write(codec, WM8772_DACRATE, diface_ctrl);
|
|
wm8772_write(codec, WM8772_IFACE, diface);
|
|
|
|
} else {
|
|
|
|
/* set master/slave audio interface */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_CLOCK_MASK) {
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
aiface |= 0x0010;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
break;
|
|
}
|
|
|
|
/* interface format */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_I2S:
|
|
aiface |= 0x0002;
|
|
break;
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
break;
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
aiface |= 0x0001;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
aiface |= 0x0003;
|
|
break;
|
|
case SND_SOC_DAIFMT_DSP_B:
|
|
aiface |= 0x0003;
|
|
aiface_ctrl |= 0x0010;
|
|
break;
|
|
}
|
|
|
|
/* bit size */
|
|
switch (rtd->codec_dai->dai_runtime.pcmfmt) {
|
|
case SNDRV_PCM_FMTBIT_S16_LE:
|
|
break;
|
|
case SNDRV_PCM_FMTBIT_S20_3LE:
|
|
aiface |= 0x0004;
|
|
break;
|
|
case SNDRV_PCM_FMTBIT_S24_LE:
|
|
aiface |= 0x0008;
|
|
break;
|
|
case SNDRV_PCM_FMTBIT_S32_LE:
|
|
aiface |= 0x000c;
|
|
break;
|
|
}
|
|
|
|
/* clock inversion */
|
|
switch (rtd->codec_dai->dai_runtime.fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
aiface_ctrl |= 0x0020;
|
|
break;
|
|
}
|
|
|
|
/* set rate */
|
|
switch (rtd->codec_dai->dai_runtime.fs) {
|
|
case 768:
|
|
aiface |= (0x5 << 5);
|
|
break;
|
|
case 512:
|
|
aiface |= (0x4 << 5);
|
|
break;
|
|
case 384:
|
|
aiface |= (0x3 << 5);
|
|
break;
|
|
case 256:
|
|
aiface |= (0x2 << 5);
|
|
break;
|
|
}
|
|
|
|
wm8772_write(codec, WM8772_ADCCTRL, aiface_ctrl);
|
|
wm8772_write(codec, WM8772_ADCRATE, aiface);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wm8772_dapm_event(struct snd_soc_codec *codec, int event)
|
|
{
|
|
u16 master = wm8772_read_reg_cache(codec, WM8772_DACRATE) & 0xffe0;
|
|
|
|
switch (event) {
|
|
case SNDRV_CTL_POWER_D0: /* full On */
|
|
/* vref/mid, clk and osc on, dac unmute, active */
|
|
wm8772_write(codec, WM8772_DACRATE, master);
|
|
break;
|
|
case SNDRV_CTL_POWER_D1: /* partial On */
|
|
case SNDRV_CTL_POWER_D2: /* partial On */
|
|
break;
|
|
case SNDRV_CTL_POWER_D3hot: /* Off, with power */
|
|
/* everything off except vref/vmid, dac mute, inactive */
|
|
wm8772_write(codec, WM8772_DACRATE, master | 0x0f);
|
|
break;
|
|
case SNDRV_CTL_POWER_D3cold: /* Off, without power */
|
|
/* everything off, dac mute, inactive */
|
|
wm8772_write(codec, WM8772_DACRATE, master | 0x1f);
|
|
break;
|
|
}
|
|
codec->dapm_state = event;
|
|
return 0;
|
|
}
|
|
|
|
struct snd_soc_codec_dai wm8772_dai = {
|
|
.name = "WM8772",
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 6,
|
|
},
|
|
.capture = {
|
|
.stream_name = "Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
},
|
|
.config_sysclk = wm8772_config_sysclk,
|
|
.ops = {
|
|
.prepare = wm8772_pcm_prepare,
|
|
},
|
|
.caps = {
|
|
.num_modes = ARRAY_SIZE(wm8772_modes),
|
|
.mode = wm8772_modes,
|
|
},
|
|
};
|
|
EXPORT_SYMBOL_GPL(wm8772_dai);
|
|
|
|
static int wm8772_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;
|
|
|
|
wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
|
|
return 0;
|
|
}
|
|
|
|
static int wm8772_resume(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
|
struct snd_soc_codec *codec = socdev->codec;
|
|
int i;
|
|
u8 data[2];
|
|
u16 *cache = codec->reg_cache;
|
|
|
|
/* Sync reg_cache with the hardware */
|
|
for (i = 0; i < ARRAY_SIZE(wm8772_reg); i++) {
|
|
data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001);
|
|
data[1] = cache[i] & 0x00ff;
|
|
codec->hw_write(codec->control_data, data, 2);
|
|
}
|
|
wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
|
|
wm8772_dapm_event(codec, codec->suspend_dapm_state);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* initialise the WM8772 driver
|
|
* register the mixer and dsp interfaces with the kernel
|
|
*/
|
|
static int wm8772_init(struct snd_soc_device *socdev)
|
|
{
|
|
struct snd_soc_codec *codec = socdev->codec;
|
|
int reg, ret = 0;
|
|
|
|
codec->name = "WM8772";
|
|
codec->owner = THIS_MODULE;
|
|
codec->read = wm8772_read_reg_cache;
|
|
codec->write = wm8772_write;
|
|
codec->dapm_event = wm8772_dapm_event;
|
|
codec->dai = &wm8772_dai;
|
|
codec->num_dai = 1;
|
|
codec->reg_cache_size = ARRAY_SIZE(wm8772_reg);
|
|
codec->reg_cache =
|
|
kzalloc(sizeof(u16) * ARRAY_SIZE(wm8772_reg), GFP_KERNEL);
|
|
if (codec->reg_cache == NULL)
|
|
return -ENOMEM;
|
|
memcpy(codec->reg_cache, wm8772_reg,
|
|
sizeof(u16) * ARRAY_SIZE(wm8772_reg));
|
|
codec->reg_cache_size = sizeof(u16) * ARRAY_SIZE(wm8772_reg);
|
|
|
|
wm8772_reset(codec);
|
|
|
|
/* register pcms */
|
|
ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
|
|
if(ret < 0) {
|
|
kfree(codec->reg_cache);
|
|
return ret;
|
|
}
|
|
|
|
/* power on device */
|
|
wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3hot);
|
|
|
|
/* set the update bits */
|
|
reg = wm8772_read_reg_cache(codec, WM8772_MDACVOL);
|
|
wm8772_write(codec, WM8772_MDACVOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_LDAC1VOL);
|
|
wm8772_write(codec, WM8772_LDAC1VOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_LDAC2VOL);
|
|
wm8772_write(codec, WM8772_LDAC2VOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_LDAC3VOL);
|
|
wm8772_write(codec, WM8772_LDAC3VOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_RDAC1VOL);
|
|
wm8772_write(codec, WM8772_RDAC1VOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_RDAC2VOL);
|
|
wm8772_write(codec, WM8772_RDAC2VOL, reg | 0x0100);
|
|
reg = wm8772_read_reg_cache(codec, WM8772_RDAC3VOL);
|
|
wm8772_write(codec, WM8772_RDAC3VOL, reg | 0x0100);
|
|
|
|
wm8772_add_controls(codec);
|
|
ret = snd_soc_register_card(socdev);
|
|
if (ret < 0) {
|
|
snd_soc_free_pcms(socdev);
|
|
snd_soc_dapm_free(socdev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct snd_soc_device *wm8772_socdev;
|
|
|
|
static int wm8772_probe(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
|
struct wm8772_setup_data *setup;
|
|
struct snd_soc_codec *codec;
|
|
int ret = 0;
|
|
|
|
printk(KERN_INFO "WM8772 Audio Codec %s", WM8772_VERSION);
|
|
|
|
setup = socdev->codec_data;
|
|
codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL);
|
|
if (codec == NULL)
|
|
return -ENOMEM;
|
|
|
|
socdev->codec = codec;
|
|
mutex_init(&codec->mutex);
|
|
INIT_LIST_HEAD(&codec->dapm_widgets);
|
|
INIT_LIST_HEAD(&codec->dapm_paths);
|
|
|
|
wm8772_socdev = socdev;
|
|
|
|
/* Add other interfaces here */
|
|
#warning do SPI device probe here and then call wm8772_init()
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* power down chip */
|
|
static int wm8772_remove(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_device *socdev = platform_get_drvdata(pdev);
|
|
struct snd_soc_codec *codec = socdev->codec;
|
|
|
|
if (codec->control_data)
|
|
wm8772_dapm_event(codec, SNDRV_CTL_POWER_D3cold);
|
|
|
|
snd_soc_free_pcms(socdev);
|
|
kfree(codec);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct snd_soc_codec_device soc_codec_dev_wm8772 = {
|
|
.probe = wm8772_probe,
|
|
.remove = wm8772_remove,
|
|
.suspend = wm8772_suspend,
|
|
.resume = wm8772_resume,
|
|
};
|
|
|
|
EXPORT_SYMBOL_GPL(soc_codec_dev_wm8772);
|
|
|
|
MODULE_DESCRIPTION("ASoC WM8772 driver");
|
|
MODULE_AUTHOR("Liam Girdwood");
|
|
MODULE_LICENSE("GPL");
|
|
|