This original driver was created by Dialog Semiconductor, and cleanuped by Kuninori Morimoto. Special thanks to David Chen. This is very simple ASoC codec driver. It was tested by EcoVec24 board.
Signed-off-by: David Chen Dajun.chen@diasemi.com Signed-off-by: Kuninori Morimoto morimoto.kuninori@renesas.com --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/da7210.c | 773 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/da7210.h | 24 ++ 4 files changed, 803 insertions(+), 0 deletions(-) create mode 100644 sound/soc/codecs/da7210.c create mode 100644 sound/soc/codecs/da7210.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 52b005f..ff33b02 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -23,6 +23,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4671 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C + select SND_SOC_DA7210 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -112,6 +113,9 @@ config SND_SOC_AK4671 config SND_SOC_CS4270 tristate
+config SND_SOC_DA7210 + tristate + # 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 diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index dbaecb1..a495bf8 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -10,6 +10,7 @@ snd-soc-ak4642-objs := ak4642.o snd-soc-ak4671-objs := ak4671.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o +snd-soc-da7210-objs := da7210.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o @@ -66,6 +67,7 @@ obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o +obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c new file mode 100644 index 0000000..1356b90 --- /dev/null +++ b/sound/soc/codecs/da7210.c @@ -0,0 +1,773 @@ +/* + * DA7210 ALSA Soc codec driver + * + * Copyright (c) 2009 Dialog Semiconductor + * Written by David Chen Dajun.chen@diasemi.com + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Cleanups by Kuninori Morimoto morimoto.kuninori@renesas.com + * + * Tested on SuperH Ecovec24 board with S16/S24 LE in 48KHz using I2S + * + * 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/i2c.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/tlv.h> +#include <sound/initval.h> +#include <asm/div64.h> + +#include "da7210.h" + +/* DA7210 register space */ +#define DA7210_STATUS 0x02 +#define DA7210_STARTUP1 0x03 +#define DA7210_STARTUP2 0x04 +#define DA7210_STARTUP3 0x05 +#define DA7210_MIC_L 0x07 +#define DA7210_MIC_R 0x08 +#define DA7210_IN_GAIN 0x0C +#define DA7210_INMIX_L 0x0D +#define DA7210_INMIX_R 0x0E +#define DA7210_ADC_HPF 0x0F +#define DA7210_ADC 0x10 +#define DA7210_DAC_HPF 0x14 +#define DA7210_DAC_L 0x15 +#define DA7210_DAC_R 0x16 +#define DA7210_DAC_SEL 0x17 +#define DA7210_OUTMIX_L 0x1C +#define DA7210_OUTMIX_R 0x1D +#define DA7210_OUT2 0x20 +#define DA7210_HP_L_VOL 0x21 +#define DA7210_HP_R_VOL 0x22 +#define DA7210_HP_CFG 0x23 +#define DA7210_DAI_SRC_SEL 0x25 +#define DA7210_DAI_CFG1 0x26 +#define DA7210_DAI_CFG3 0x28 +#define DA7210_PLL_DIV3 0x2B +#define DA7210_PLL 0x2C + +/* STARTUP1 bit fields */ +#define DA7210_SC_MST_EN (1 << 0) + +/* STARTUP2 bit fields */ +#define DA7210_LOUT1_L_STBY (1 << 0) +#define DA7210_LOUT1_R_STBY (1 << 1) +#define DA7210_LOUT2_STBY (1 << 2) +#define DA7210_HP_L_STBY (1 << 3) +#define DA7210_HP_R_STBY (1 << 4) +#define DA7210_DAC_L_STBY (1 << 5) +#define DA7210_DAC_R_STBY (1 << 6) + +/* STARTUP3 bit fields */ +#define DA7210_MIC_L_STBY (1 << 0) +#define DA7210_MIC_R_STBY (1 << 1) +#define DA7210_LIN1_L_STBY (1 << 2) +#define DA7210_LIN1_R_STBY (1 << 3) +#define DA7210_LIN2_STBY (1 << 4) +#define DA7210_ADC_L_STBY (1 << 5) +#define DA7210_ADC_R_STBY (1 << 6) + +/* MIC_L bit fields */ +#define DA7210_MICBIAS_EN (1 << 6) +#define DA7210_MIC_L_EN (1 << 7) + +/* MIC_R bit fields */ +#define DA7210_MIC_R_EN (1 << 7) + +/* INMIX_L bit fields */ +#define DA7210_IN_L_EN (1 << 7) + +/* INMIX_R bit fields */ +#define DA7210_IN_R_EN (1 << 7) + +/* ADC_HPF bit fields */ +#define DA7210_ADC_HPF_EN (1 << 3) +#define DA7210_ADC_VOICE_EN (1 << 7) + +/* ADC bit fields */ +#define DA7210_ADC_L_EN (1 << 3) +#define DA7210_ADC_R_EN (1 << 7) + +/* DAC_HPF bit fields */ +#define DA7210_DAC_HPF_EN (1 << 3) +#define DA7210_DAC_VOICE_EN (1 << 7) + +/* DAC_SEL bit fields */ +#define DA7210_DAC_L_SRC_DAI_L (4 << 0) +#define DA7210_DAC_L_EN (1 << 3) +#define DA7210_DAC_R_SRC_DAI_R (5 << 4) +#define DA7210_DAC_R_EN (1 << 7) + +/* OUTMIX_L bit fields */ +#define DA7210_OUT_L_EN (1 << 7) + +/* OUTMIX_R bit fields */ +#define DA7210_OUT_R_EN (1 << 7) + +/* HP_CFG bit fields */ +#define DA7210_HP_2CAP_MODE (1 << 1) +#define DA7210_HP_SENSE_EN (1 << 2) +#define DA7210_HP_L_EN (1 << 3) +#define DA7210_HP_MODE (1 << 6) +#define DA7210_HP_R_EN (1 << 7) + +/* DAI_SRC_SEL bit fields */ +#define DA7210_DAI_OUT_L_SRC (6 << 0) +#define DA7210_DAI_OUT_R_SRC (7 << 4) + +/* DAI_CFG1 bit fields */ +#define DA7210_DAI_WORD_S16_LE (0 << 0) +#define DA7210_DAI_WORD_S24_LE (2 << 0) +#define DA7210_DAI_FLEN_64BIT (1 << 2) +#define DA7210_DAI_MODE_MASTER (1 << 7) + +/* DAI_CFG3 bit fields */ +#define DA7210_DAI_FORMAT_I2SMODE (0 << 0) +#define DA7210_DAI_OE (1 << 3) +#define DA7210_DAI_EN (1 << 7) + +/*PLL_DIV3 bit fields */ +#define DA7210_MCLK_RANGE_10_20_MHZ (1 << 4) +#define DA7210_PLL_BYP (1 << 6) + +/* PLL bit fields */ +#define DA7210_PLL_FS_48000 (11 << 0) + +#define DA7210_VERSION "0.0.1" + +/* Codec private data */ +struct da7210_priv { + struct snd_soc_codec codec; +}; + +static struct snd_soc_codec *da7210_codec; + +/* + * Register cache + */ +static const u8 da7210_reg[] = { + 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R0 - R7 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, /* R8 - RF */ + 0x00, 0x00, 0x00, 0x00, 0x08, 0x10, 0x10, 0x54, /* R10 - R17 */ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R18 - R1F */ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, /* R20 - R27 */ + 0x04, 0x00, 0x00, 0x30, 0x2A, 0x00, 0x40, 0x00, /* R28 - R2F */ + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, /* R30 - R37 */ + 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, /* R38 - R3F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R40 - R4F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R48 - R4F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R50 - R57 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R58 - R5F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R60 - R67 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R68 - R6F */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* R70 - R77 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x54, 0x00, /* R78 - R7F */ + 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x00, 0x00, /* R80 - R87 */ + 0x00, /* R88 */ +}; + +/* + * Read da7210 register cache + */ +static inline unsigned int da7210_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + BUG_ON(reg > ARRAY_SIZE(da7210_reg)); + return cache[reg]; +} + +/* + * Write da7210 register cache + */ +static inline void da7210_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u8 *cache = codec->reg_cache; + + cache[reg] = value; +} + +/* + * Write to the da7210 register space + */ +static int da7210_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + + u8 data[2]; + + /* data[0] da7210 register offset */ + /* data[1] register data */ + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (2 != codec->hw_write(codec->control_data, data, 2)) { + dev_warn(codec->dev, "I2C write Failed!\n"); + return -EIO; + } + + /* write the cache only if hardware write is successful */ + if (data[0] < ARRAY_SIZE(da7210_reg)) + da7210_write_reg_cache(codec, data[0], data[1]); + + return 0; +} + +/* + * Read from the da7210 register space. + */ +static inline unsigned int da7210_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + /* FIXME !! + * + * we should read status from I2C. + * But not supported now. + */ + if (DA7210_STATUS == reg) + return -EIO; + + return da7210_read_reg_cache(codec, reg); +} + +static const char *da7210_mic_bias_voltage[] = { "1.5", "1.6", "2.2", "2.3" }; + +static const struct soc_enum da7210_enum[] = { + SOC_ENUM_SINGLE(DA7210_MIC_L, 4, 4, da7210_mic_bias_voltage), +}; + +/* Add non DAPM controls */ +static const struct snd_kcontrol_new da7210_snd_controls[] = { + /* Mixer Playback controls */ + SOC_DOUBLE_R("DAC Gain", DA7210_DAC_L, DA7210_DAC_R, 0, 0x37, 1), + SOC_DOUBLE_R("HeadPhone Playback Volume", + DA7210_HP_L_VOL, DA7210_HP_R_VOL, 0, 0x3f, 0), + /* Mixer Capture controls */ + SOC_DOUBLE_R("Mic Capture Volume", + DA7210_MIC_L, DA7210_MIC_R, 0, 0x07, 0), + SOC_DOUBLE("In PGA Gain", DA7210_IN_GAIN, 0, 4, 0x0F, 0), + SOC_SINGLE("Mic Bias", DA7210_MIC_L, 6, 1, 0), + SOC_ENUM("Mic Bias Voltage", da7210_enum[0]), +}; + +/* ----------------------------Capture Mixers--------------------------- */ + +/* In Mixer Left */ +static const struct snd_kcontrol_new da7210_in_left_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC_L Switch", DA7210_INMIX_L, 0, 1, 0), + SOC_DAPM_SINGLE("MIC_R Switch", DA7210_INMIX_L, 1, 1, 0), + SOC_DAPM_SINGLE("Aux1_L Switch", DA7210_INMIX_L, 2, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_INMIX_L, 3, 1, 0), + SOC_DAPM_SINGLE("DAC_L Switch", DA7210_INMIX_L, 4, 1, 0), +}; + +/* In Mixer Right */ +static const struct snd_kcontrol_new da7210_in_right_mixer_controls[] = { + SOC_DAPM_SINGLE("MIC_R Switch", DA7210_INMIX_R, 0, 1, 0), + SOC_DAPM_SINGLE("MIC_L Switch", DA7210_INMIX_R, 1, 1, 0), + SOC_DAPM_SINGLE("Aux1_R Switch", DA7210_INMIX_R, 2, 1, 0), + SOC_DAPM_SINGLE("Aux2 Switch", DA7210_INMIX_R, 3, 1, 0), + SOC_DAPM_SINGLE("DAC_R Switch", DA7210_INMIX_R, 4, 1, 0), + SOC_DAPM_SINGLE("INPGA_L Switch", DA7210_INMIX_R, 5, 1, 0), +}; + +/*----------------------------Playback Mixers---------------------------*/ +/* Out Mixer Left */ +static const struct snd_kcontrol_new da7210_out_mixer_left_controls[] = { + SOC_DAPM_SINGLE("AUX1_L Switch", DA7210_OUTMIX_L, 0, 1, 0), + SOC_DAPM_SINGLE("AUX2 Switch", DA7210_OUTMIX_L, 1, 1, 0), + SOC_DAPM_SINGLE("IN_L Switch", DA7210_OUTMIX_L, 2, 1, 0), + SOC_DAPM_SINGLE("IN_R Switch", DA7210_OUTMIX_L, 3, 1, 0), + SOC_DAPM_SINGLE("DAC_L Switch", DA7210_OUTMIX_L, 4, 1, 0), +}; + +/* Out Mixer Right */ +static const struct snd_kcontrol_new da7210_out_mixer_right_controls[] = { + SOC_DAPM_SINGLE("AUX1_R Switch", DA7210_OUTMIX_R, 0, 1, 0), + SOC_DAPM_SINGLE("AUX2 Switch", DA7210_OUTMIX_R, 1, 1, 0), + SOC_DAPM_SINGLE("IN_L Switch", DA7210_OUTMIX_R, 2, 1, 0), + SOC_DAPM_SINGLE("IN_R Switch", DA7210_OUTMIX_R, 3, 1, 0), + SOC_DAPM_SINGLE("DAC_R Switch", DA7210_OUTMIX_R, 4, 1, 0), +}; + +/* Mono Mixer */ +static const struct snd_kcontrol_new da7210_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("IN_L Switch", DA7210_OUT2, 3, 1, 0), + SOC_DAPM_SINGLE("IN_R Switch", DA7210_OUT2, 4, 1, 0), + SOC_DAPM_SINGLE("DAC_L Switch", DA7210_OUT2, 5, 1, 0), + SOC_DAPM_SINGLE("DAC_R Switch", DA7210_OUT2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new da7210_headphone_left_control = + SOC_DAPM_SINGLE("Switch", DA7210_STARTUP2, 3, 1, 1); +static const struct snd_kcontrol_new da7210_headphone_right_control = + SOC_DAPM_SINGLE("Switch", DA7210_STARTUP2, 4, 1, 1); +static const struct snd_kcontrol_new da7210_MicIn_left_control = + SOC_DAPM_SINGLE("Switch", DA7210_STARTUP3, 0, 1, 1); +static const struct snd_kcontrol_new da7210_MicIn_right_control = + SOC_DAPM_SINGLE("Switch", DA7210_STARTUP3, 1, 1, 1); + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget da7210_dapm_widgets[] = { + /* DAMP stream domain - DAC. Enabled when playback is started */ + SND_SOC_DAPM_DAC("DAC Left", "Playback", DA7210_STARTUP2, 5, 1), + SND_SOC_DAPM_DAC("DAC Right", "Playback", DA7210_STARTUP2, 6, 1), + + /* DAMP stream domain - ADC. Enabled when capture is started */ + SND_SOC_DAPM_ADC("ADC Left", "Capture", DA7210_STARTUP3, 5, 1), + SND_SOC_DAPM_ADC("ADC Right", "Capture", DA7210_STARTUP3, 6, 1), + + /* DAPM path domain - switches and mixers */ + /* Automatically set when mixer settings are changed by the user */ + SND_SOC_DAPM_SWITCH("HPL Enable", SND_SOC_NOPM, 0, 0, + &da7210_headphone_left_control), + SND_SOC_DAPM_SWITCH("HPR Enable", SND_SOC_NOPM, 0, 0, + &da7210_headphone_right_control), + SND_SOC_DAPM_SWITCH("MicL Enable", SND_SOC_NOPM, 0, 0, + &da7210_MicIn_left_control), + SND_SOC_DAPM_SWITCH("MicR Enable", SND_SOC_NOPM, 0, 0, + &da7210_MicIn_right_control), + + SND_SOC_DAPM_MIXER("Out Mixer Left", SND_SOC_NOPM, 0, 0, + &da7210_out_mixer_left_controls[0], + ARRAY_SIZE(da7210_out_mixer_left_controls)), + + SND_SOC_DAPM_MIXER("Out Mixer Right", SND_SOC_NOPM, 0, 0, + &da7210_out_mixer_right_controls[0], + ARRAY_SIZE(da7210_out_mixer_right_controls)), + + SND_SOC_DAPM_MIXER("In Mixer Left", SND_SOC_NOPM, 0, 0, + &da7210_in_left_mixer_controls[0], + ARRAY_SIZE(da7210_in_left_mixer_controls)), + + SND_SOC_DAPM_MIXER("In Mixer Right", SND_SOC_NOPM, 0, 0, + &da7210_in_right_mixer_controls[0], + ARRAY_SIZE(da7210_in_right_mixer_controls)), + + /* DAPM Platform domain. Physically connected input and ouput pins */ + SND_SOC_DAPM_OUTPUT("HPL"), /*Headphone Out left*/ + SND_SOC_DAPM_OUTPUT("HPR"), /*Headphone out Right*/ + SND_SOC_DAPM_INPUT("MICL"), /*MicIn left*/ + SND_SOC_DAPM_INPUT("MICR"), /*MicIn Right*/ +}; + +/* DAPM audio route definition */ +static const struct snd_soc_dapm_route audio_map[] = { + /*Out Mixer Left*/ + {"Out Mixer Left", "DAC_L Switch", "DAC Left"}, + + /*Out Mixer Right*/ + {"Out Mixer Right", "DAC_R Switch", "DAC Right"}, + + /*In Mixer Left*/ + {"In Mixer Left", "MIC_L Switch", "MicL Enable"}, + + /*In Mixer Right*/ + {"In Mixer Right", "MIC_R Switch", "MicR Enable"}, + + /*HPL*/ + {"HPL", NULL, "HPL Enable"}, + {"HPL Enable", "Switch", "Out Mixer Left"}, + + /*HPR*/ + {"HPR", NULL, "HPR Enable"}, + {"HPR Enable", "Switch", "Out Mixer Right"}, + + /*MICL*/ + {"ADC Left", NULL, "In Mixer Left"}, + {"MicL Enable", "Switch", "MICL"}, + + /*MICR*/ + {"ADC Right", NULL, "In Mixer Right"}, + {"MicR Enable", "Switch", "MICR"}, +}; + +static int da7210_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, da7210_dapm_widgets, + ARRAY_SIZE(da7210_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_new_widgets(codec); + return 0; +} + +/* + * Set PCM DAI word length. + * Enable Voice Filter if Fs <= 16KHz + */ +static int da7210_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; + unsigned int dai_cfg1; + unsigned int value, reg, mask; + + dai_cfg1 = 0xFC & da7210_read(codec, DA7210_DAI_CFG1); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + dai_cfg1 |= DA7210_DAI_WORD_S16_LE; + break; + case SNDRV_PCM_FORMAT_S24_LE: + dai_cfg1 |= DA7210_DAI_WORD_S24_LE; + break; + default: + return -EINVAL; + } + + da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); + + /* FIXME + * + * It support 48K only now + */ + switch (params_rate(params)) { + case 48000: + if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) { + reg = DA7210_DAC_HPF; + mask = ~DA7210_DAC_VOICE_EN; + } else { + reg = DA7210_ADC_HPF; + mask = ~DA7210_ADC_VOICE_EN; + } + break; + default: + return -EINVAL; + } + + value = da7210_read_reg_cache(codec, reg); + value &= mask; + da7210_write(codec, reg, value); + + return 0; +} + +/* + * Set DAI mode and Format + */ +static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int dai_cfg1; + unsigned int dai_cfg3; + + dai_cfg1 = 0x7f & da7210_read(codec, DA7210_DAI_CFG1); + dai_cfg3 = 0xfc & da7210_read(codec, DA7210_DAI_CFG3); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + dai_cfg1 |= DA7210_DAI_MODE_MASTER; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support I2S only now + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + dai_cfg3 |= DA7210_DAI_FORMAT_I2SMODE; + break; + default: + return -EINVAL; + } + + /* FIXME + * + * It support 64bit data transmission only now + */ + dai_cfg1 |= DA7210_DAI_FLEN_64BIT; + + da7210_write(codec, DA7210_DAI_CFG1, dai_cfg1); + da7210_write(codec, DA7210_DAI_CFG3, dai_cfg3); + + return 0; +} + +#define DA7210_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_LE) + +/* DAI operations */ +static struct snd_soc_dai_ops da7210_dai_ops = { + .hw_params = da7210_hw_params, + .set_fmt = da7210_set_dai_fmt, +}; + +struct snd_soc_dai da7210_dai = { + .name = "DA7210 IIS", + .id = 0, + /* playback capabilities */ + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = DA7210_FORMATS, + }, + .ops = &da7210_dai_ops, +}; +EXPORT_SYMBOL_GPL(da7210_dai); + +/* + * Initialize the DA7210 driver + * register the mixer and dsp interfaces with the kernel + */ +static int da7210_init(struct da7210_priv *da7210) +{ + struct snd_soc_codec *codec = &da7210->codec; + int ret = 0; + + if (da7210_codec) { + dev_err(codec->dev, "Another da7210 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = da7210; + codec->name = "DA7210"; + codec->owner = THIS_MODULE; + codec->read = da7210_read; + codec->write = da7210_write; + codec->dai = &da7210_dai; + codec->num_dai = 1; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->reg_cache_size = ARRAY_SIZE(da7210_reg); + codec->reg_cache = kmemdup(da7210_reg, + sizeof(da7210_reg), GFP_KERNEL); + + if (!codec->reg_cache) + return -ENOMEM; + + da7210_dai.dev = codec->dev; + da7210_codec = codec; + + ret = snd_soc_register_dai(&da7210_dai); + if (ret) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + return -ENOMEM; + } + + /* + * ADC settings + */ + + /* Enable Left & Right MIC PGA and Mic Bias */ + da7210_write(codec, DA7210_MIC_L, DA7210_MIC_L_EN | DA7210_MICBIAS_EN); + da7210_write(codec, DA7210_MIC_R, DA7210_MIC_R_EN); + + /* Enable Left and Right input PGA */ + da7210_write(codec, DA7210_INMIX_L, DA7210_IN_L_EN); + da7210_write(codec, DA7210_INMIX_R, DA7210_IN_R_EN); + + /* Enable ADC Highpass Filter */ + da7210_write(codec, DA7210_ADC_HPF, DA7210_ADC_HPF_EN); + + /* Enable Left and Right ADC */ + da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN); + + /* + * DAC settings + */ + + /* Enable DAC Highpass Filter */ + da7210_write(codec, DA7210_DAC_HPF, DA7210_DAC_HPF_EN); + + /* Enable Left and Right DAC */ + da7210_write(codec, DA7210_DAC_SEL, + DA7210_DAC_L_SRC_DAI_L | DA7210_DAC_L_EN | + DA7210_DAC_R_SRC_DAI_R | DA7210_DAC_R_EN); + + /* Enable Left and Right out PGA */ + da7210_write(codec, DA7210_OUTMIX_L, DA7210_OUT_L_EN); + da7210_write(codec, DA7210_OUTMIX_R, DA7210_OUT_R_EN); + + /* Enable Left and Right HeadPhone PGA */ + da7210_write(codec, DA7210_HP_CFG, + DA7210_HP_2CAP_MODE | DA7210_HP_SENSE_EN | + DA7210_HP_L_EN | DA7210_HP_MODE | DA7210_HP_R_EN); + + /* set DAI source to Left and Right ADC */ + da7210_write(codec, DA7210_DAI_SRC_SEL, + DA7210_DAI_OUT_R_SRC | DA7210_DAI_OUT_L_SRC); + + /* Enable DAI */ + da7210_write(codec, DA7210_DAI_CFG3, DA7210_DAI_OE | DA7210_DAI_EN); + + /* Diable PLL and bypass it */ + da7210_write(codec, DA7210_PLL, DA7210_PLL_FS_48000); + + /* Bypass PLL and set MCLK freq rang to 10-20MHz */ + da7210_write(codec, DA7210_PLL_DIV3, + DA7210_MCLK_RANGE_10_20_MHZ | DA7210_PLL_BYP); + + /* Enable standbymode */ + da7210_write(codec, DA7210_STARTUP2, + DA7210_LOUT1_L_STBY | DA7210_LOUT1_R_STBY | + DA7210_LOUT2_STBY | DA7210_HP_L_STBY | + DA7210_HP_R_STBY | DA7210_DAC_L_STBY | DA7210_DAC_R_STBY); + da7210_write(codec, DA7210_STARTUP3, + DA7210_LIN1_L_STBY | DA7210_LIN1_R_STBY | + DA7210_LIN2_STBY | DA7210_MIC_L_STBY | + DA7210_MIC_R_STBY | DA7210_ADC_L_STBY | DA7210_ADC_R_STBY); + + /* Activate all enabled subsystem */ + da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); + + return ret; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int da7210_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct da7210_priv *da7210; + struct snd_soc_codec *codec; + int ret; + + da7210 = kzalloc(sizeof(struct da7210_priv), GFP_KERNEL); + if (!da7210) + return -ENOMEM; + + codec = &da7210->codec; + codec->dev = &i2c->dev; + + i2c_set_clientdata(i2c, da7210); + codec->control_data = i2c; + + ret = da7210_init(da7210); + if (ret < 0) + pr_err("Failed to initialise da7210 audio codec\n"); + + return ret; +} + +static int da7210_i2c_remove(struct i2c_client *client) +{ + struct da7210_priv *da7210 = i2c_get_clientdata(client); + + snd_soc_unregister_dai(&da7210_dai); + kfree(da7210->codec.reg_cache); + kfree(da7210); + da7210_codec = NULL; + + return 0; +} + +static const struct i2c_device_id da7210_i2c_id[] = { + { "da7210", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, da7210_i2c_id); + +/* I2C codec control layer */ +static struct i2c_driver da7210_i2c_driver = { + .driver = { + .name = "DA7210 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = da7210_i2c_probe, + .remove = __devexit_p(da7210_i2c_remove), + .id_table = da7210_i2c_id, +}; +#endif + +static int da7210_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if (!da7210_codec) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = da7210_codec; + codec = da7210_codec; + + /* Register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + dev_info(&pdev->dev, "DA7210 Audio Codec %s\n", DA7210_VERSION); + + /* Add the dapm controls */ + snd_soc_add_controls(codec, da7210_snd_controls, + ARRAY_SIZE(da7210_snd_controls)); + da7210_add_widgets(codec); + +pcm_err: + return ret; +} + +static int da7210_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_da7210 = { + .probe = da7210_probe, + .remove = da7210_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_da7210); + +static int __init da7210_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&da7210_i2c_driver); +#endif + return ret; +} +module_init(da7210_modinit); + +static void __exit da7210_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&da7210_i2c_driver); +#endif +} +module_exit(da7210_exit); + +MODULE_DESCRIPTION("ASoC DA7210 driver"); +MODULE_AUTHOR("David Chen, Kuninori Morimoto"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/da7210.h b/sound/soc/codecs/da7210.h new file mode 100644 index 0000000..390d621 --- /dev/null +++ b/sound/soc/codecs/da7210.h @@ -0,0 +1,24 @@ +/* + * da7210.h -- audio driver for da7210 + * + * Copyright (c) 2009 Dialog Semiconductor + * Written by David Chen Dajun.chen@diasemi.com + * + * Copyright (C) 2009 Renesas Solutions Corp. + * Cleanups by Kuninori Morimoto morimoto.kuninori@renesas.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. + * + */ + +#ifndef _DA7210_H +#define _DA7210_H + +extern struct snd_soc_dai da7210_dai; +extern struct snd_soc_codec_device soc_codec_dev_da7210; + +#endif +