[alsa-devel] [PATCH 0/4] mc13783 sound support
Hello
This is patchset enable sound support on boards with mc13783 codec. The first patch adds the codec driver. It's Sascha's code ported to the current kernel with some minors additions. The second patch adds a generic SoC platform driver for imx3 boards with mc13783 codecs. The last two patches enable the sound on the mx31moboard boards and on mx31pdk_3ds boards.
This patchset has been tested on mx31moboard and mx31pdk_3ds.
It is based on a 3.3-rc6 kernel. I know there are some patches which change how audmux is handled on i.Mx processor but they are not yet merged so I prefer to post this patchset based on a known tree than to add another dependency. If the audmux change is merged I will rebase this patchset.
Small note: If you want to test this, make sure you use redboot as bootloader or that you have the patch i.MX 35/5 AIPS Setup applied.
Philippe Rétornaz (3): Add imx3sound sound support. mx31moboard: Add ssi configuration imx31pdk: Add sound support
Sascha Hauer (1): add a mc13783 codec driver
arch/arm/mach-imx/Kconfig | 2 + arch/arm/mach-imx/mach-mx31_3ds.c | 40 ++- arch/arm/mach-imx/mach-mx31moboard.c | 37 ++- drivers/mfd/mc13xxx-core.c | 3 +- include/linux/mfd/mc13xxx.h | 11 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/mc13783.c | 1036 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/mc13783.h | 30 + sound/soc/imx/Kconfig | 10 + sound/soc/imx/Makefile | 2 + sound/soc/imx/imx3sound-mc13783.c | 141 +++++ 12 files changed, 1315 insertions(+), 3 deletions(-) create mode 100644 sound/soc/codecs/mc13783.c create mode 100644 sound/soc/codecs/mc13783.h create mode 100644 sound/soc/imx/imx3sound-mc13783.c
From: Sascha Hauer s.hauer@pengutronix.de
sha 20111307: - rebased to 3.0-rc2 - implement tdm slot settings - made it work with single ssi port - more register names instead of hardcoded numbers
philippe 20120803: - Add headset detection - add mic2 bias - enable headset output by default
Signed-off-by: Sascha Hauer s.hauer@pengutronix.de Signed-off-by: Philippe Rétornaz philippe.retornaz@epfl.ch --- drivers/mfd/mc13xxx-core.c | 3 +- include/linux/mfd/mc13xxx.h | 11 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/mc13783.c | 1036 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/mc13783.h | 30 ++ 6 files changed, 1085 insertions(+), 1 deletions(-) create mode 100644 sound/soc/codecs/mc13783.c create mode 100644 sound/soc/codecs/mc13783.h
diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 7122386..7b7b55e 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -807,7 +807,8 @@ err_revision: mc13xxx_add_subdevice(mc13xxx, "%s-adc");
if (mc13xxx->flags & MC13XXX_USE_CODEC) - mc13xxx_add_subdevice(mc13xxx, "%s-codec"); + mc13xxx_add_subdevice_pdata(mc13xxx, "%s-codec", + pdata->codec, sizeof(*pdata->codec));
if (mc13xxx->flags & MC13XXX_USE_RTC) mc13xxx_add_subdevice(mc13xxx, "%s-rtc"); diff --git a/include/linux/mfd/mc13xxx.h b/include/linux/mfd/mc13xxx.h index b86ee45..3ce5f3c 100644 --- a/include/linux/mfd/mc13xxx.h +++ b/include/linux/mfd/mc13xxx.h @@ -157,6 +157,16 @@ struct mc13xxx_buttons_platform_data { unsigned short b3on_key; };
+enum mc13783_ssi_port { + MC13783_SSI1_PORT, + MC13783_SSI2_PORT, +}; + +struct mc13xxx_codec_platform_data { + enum mc13783_ssi_port adc_ssi_port; + enum mc13783_ssi_port dac_ssi_port; +}; + struct mc13xxx_platform_data { #define MC13XXX_USE_TOUCHSCREEN (1 << 0) #define MC13XXX_USE_CODEC (1 << 1) @@ -167,6 +177,7 @@ struct mc13xxx_platform_data { struct mc13xxx_regulator_platform_data regulators; struct mc13xxx_leds_platform_data *leds; struct mc13xxx_buttons_platform_data *buttons; + struct mc13xxx_codec_platform_data *codec; };
#define MC13XXX_ADC_MODE_TS 1 diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7c205e7..5d58ffa 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -41,6 +41,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9877 if I2C + select SND_SOC_MC13783 if SPI select SND_SOC_PCM3008 select SND_SOC_RT5631 if I2C select SND_SOC_SGTL5000 if I2C @@ -224,6 +225,9 @@ config SND_SOC_MAX98095 config SND_SOC_MAX9850 tristate
+config SND_SOC_MC13783 + tristate + config SND_SOC_PCM3008 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index de80781..4c00c64 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -28,6 +28,7 @@ snd-soc-lm4857-objs := lm4857.o snd-soc-max98088-objs := max98088.o snd-soc-max98095-objs := max98095.o snd-soc-max9850-objs := max9850.o +snd-soc-mc13783-objs := mc13783.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-rt5631-objs := rt5631.o snd-soc-sgtl5000-objs := sgtl5000.o @@ -132,6 +133,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_MAX98095) += snd-soc-max98095.o obj-$(CONFIG_SND_SOC_MAX9850) += snd-soc-max9850.o +obj-$(CONFIG_SND_SOC_MC13783) += snd-soc-mc13783.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_RT5631) += snd-soc-rt5631.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o diff --git a/sound/soc/codecs/mc13783.c b/sound/soc/codecs/mc13783.c new file mode 100644 index 0000000..d172691 --- /dev/null +++ b/sound/soc/codecs/mc13783.c @@ -0,0 +1,1036 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.de + * Copyright 2009 Sascha Hauer, s.hauer@pengutronix.de + * + * Initial development of this code was funded by + * Phytec Messtechnik GmbH, http://www.phytec.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. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +#include <linux/module.h> +#include <linux/device.h> +#include <linux/mfd/mc13xxx.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/initval.h> +#include <sound/soc-dapm.h> + +#include "mc13783.h" + +#define MC13783_AUDIO_RX0 36 +#define MC13783_AUDIO_RX1 37 +#define MC13783_AUDIO_TX 38 +#define MC13783_SSI_NETWORK 39 +#define MC13783_AUDIO_CODEC 40 +#define MC13783_AUDIO_DAC 41 + +#define AUDIO_RX0_ALSPEN (1 << 5) +#define AUDIO_RX0_ALSPSEL (1 << 7) +#define AUDIO_RX0_ADDCDC (1 << 21) +#define AUDIO_RX0_ADDSTDC (1 << 22) +#define AUDIO_RX0_ADDRXIN (1 << 23) + +#define AUDIO_RX1_PGARXEN (1 << 0); +#define AUDIO_RX1_PGASTEN (1 << 5) +#define AUDIO_RX1_ARXINEN (1 << 10) + +#define AUDIO_TX_AMC1REN (1 << 5) +#define AUDIO_TX_AMC1LEN (1 << 7) +#define AUDIO_TX_AMC2EN (1 << 9) +#define AUDIO_TX_ATXINEN (1 << 11) +#define AUDIO_TX_RXINREC (1 << 13) + +#define SSI_NETWORK_CDCTXRXSLOT(x) (((x) & 0x3) << 2) +#define SSI_NETWORK_CDCTXSECSLOT(x) (((x) & 0x3) << 4) +#define SSI_NETWORK_CDCRXSECSLOT(x) (((x) & 0x3) << 6) +#define SSI_NETWORK_CDCRXSECGAIN(x) (((x) & 0x3) << 8) +#define SSI_NETWORK_CDCSUMGAIN(x) (1 << 10) +#define SSI_NETWORK_CDCFSDLY(x) (1 << 11) +#define SSI_NETWORK_DAC_SLOTS_8 (1 << 12) +#define SSI_NETWORK_DAC_SLOTS_4 (2 << 12) +#define SSI_NETWORK_DAC_SLOTS_2 (3 << 12) +#define SSI_NETWORK_DAC_SLOT_MASK (3 << 12) +#define SSI_NETWORK_DAC_RXSLOT_0_1 (0 << 14) +#define SSI_NETWORK_DAC_RXSLOT_2_3 (1 << 14) +#define SSI_NETWORK_DAC_RXSLOT_4_5 (2 << 14) +#define SSI_NETWORK_DAC_RXSLOT_6_7 (3 << 14) +#define SSI_NETWORK_DAC_RXSLOT_MASK (3 << 14) +#define SSI_NETWORK_STDCRXSECSLOT(x) (((x) & 0x3) << 16) +#define SSI_NETWORK_STDCRXSECGAIN(x) (((x) & 0x3) << 18) +#define SSI_NETWORK_STDCSUMGAIN (1 << 20) + +/* + * MC13783_AUDIO_CODEC and MC13783_AUDIO_DAC mostly share the same + * register layout + */ +#define AUDIO_SSI_SEL (1 << 0) +#define AUDIO_CLK_SEL (1 << 1) +#define AUDIO_CSM (1 << 2) +#define AUDIO_BCL_INV (1 << 3) +#define AUDIO_CFS_INV (1 << 4) +#define AUDIO_CFS(x) (((x) & 0x3) << 5) +#define AUDIO_CLK(x) (((x) & 0x7) << 7) +#define AUDIO_C_EN (1 << 11) +#define AUDIO_C_CLK_EN (1 << 12) +#define AUDIO_C_RESET (1 << 15) + +#define AUDIO_CODEC_CDCFS8K16K (1 << 10) +#define AUDIO_DAC_CFS_DLY_B (1 << 10) + +struct mc13783_priv { + struct snd_soc_codec codec; + struct mc13xxx *mc13xxx; + + u32 reg_cache[42]; + + int mc13783_asp_val; + int mc13783_alsp_val; + enum mc13783_ssi_port adc_ssi_port; + enum mc13783_ssi_port dac_ssi_port; + int mc13783_ahsout_val; +}; + +static unsigned int mc13783_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + + return priv->reg_cache[reg]; +} + +static int mc13783_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + int ret; + + mc13xxx_lock(priv->mc13xxx); + priv->reg_cache[reg] = value; + + ret = mc13xxx_reg_write(priv->mc13xxx, reg, value); + + mc13xxx_unlock(priv->mc13xxx); + + return ret; +} + +/* Mapping between sample rates and register value */ +static unsigned int mc13783_rates[] = { + 8000, 11025, 12000, 16000, + 22050, 24000, 32000, 44100, + 48000, 64000, 96000 +}; + +static int mc13783_pcm_hw_params_dac(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + unsigned int rate = params_rate(params); + int i; + + for (i = 0; i < ARRAY_SIZE(mc13783_rates); i++) { + if (rate == mc13783_rates[i]) { + snd_soc_update_bits(codec, MC13783_AUDIO_DAC, + 0xf << 17, i << 17); + return 0; + } + } + + return -EINVAL; +} + +static int mc13783_pcm_hw_params_codec(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + unsigned int rate = params_rate(params); + unsigned int val; + + switch (rate) { + case 8000: + val = 0; + break; + case 16000: + val = AUDIO_CODEC_CDCFS8K16K; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, MC13783_AUDIO_CODEC, AUDIO_CODEC_CDCFS8K16K, + val); + + return 0; +} + +static int mc13783_pcm_hw_params_sync(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return mc13783_pcm_hw_params_dac(substream, params, dai); + else + return mc13783_pcm_hw_params_codec(substream, params, dai); +} + +static int mc13783_set_fmt(struct snd_soc_dai *dai, unsigned int fmt, unsigned int reg) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + val = mc13783_read(codec, reg); + + val &= ~AUDIO_CFS(3); + val &= ~AUDIO_BCL_INV; + val &= ~AUDIO_CFS_INV; + val &= ~AUDIO_CSM; + val &= ~AUDIO_C_CLK_EN; + + /* DAI mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + val |= AUDIO_CFS(2); + break; + case SND_SOC_DAIFMT_DSP_A: + val |= AUDIO_CFS(1); + break; + default: + return -EINVAL; + } + + /* DAI clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val |= AUDIO_BCL_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + val |= AUDIO_BCL_INV; + val |= AUDIO_CFS_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + val |= AUDIO_CFS_INV; + break; + } + + /* DAI clock master masks */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + val |= AUDIO_C_CLK_EN; + break; + case SND_SOC_DAIFMT_CBS_CFS: + val |= AUDIO_CSM; + break; + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + return -EINVAL; + } + + val |= AUDIO_C_RESET; + + mc13783_write(codec, reg, val); + + return 0; +} + +static int mc13783_set_fmt_async(struct snd_soc_dai *dai, unsigned int fmt) +{ + if (dai->id == MC13783_ID_STEREO_DAC) + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + else + return mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_fmt_sync(struct snd_soc_dai *dai, unsigned int fmt) +{ + int ret; + + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC); + if (ret) + return ret; + + /* + * In synchronous mode force the voice codec into slave mode + * so that the clock / framesync from the stereo DAC is used + */ + fmt &= ~SND_SOC_DAIFMT_MASTER_MASK; + fmt |= SND_SOC_DAIFMT_CBS_CFS; + ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC); + + return ret; +} + +static int mc13783_sysclk[] = { + 13000000, + 15360000, + 16800000, + -1, + 26000000, + -1, /* 12000000, invalid for voice codec */ + -1, /* 3686400, invalid for voice codec */ + 33600000, +}; + +static int mc13783_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir, + unsigned int reg) +{ + struct snd_soc_codec *codec = dai->codec; + int clk; + unsigned int val; + + val = mc13783_read(codec, reg); + + val &= ~AUDIO_CLK(0x7); + val &= ~AUDIO_CLK_SEL; + + for (clk = 0; clk < ARRAY_SIZE(mc13783_sysclk); clk++) { + if (mc13783_sysclk[clk] < 0) + continue; + if (mc13783_sysclk[clk] == freq) + break; + } + + if (clk == ARRAY_SIZE(mc13783_sysclk)) + return -EINVAL; + + if (clk_id == MC13783_CLK_CLIB) + val |= AUDIO_CLK_SEL; + + val |= AUDIO_CLK(clk); + + mc13783_write(codec, reg, val); + + return 0; +} + +static int mc13783_set_sysclk_dac(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); +} + +static int mc13783_set_sysclk_codec(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_sysclk_sync(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + int ret; + + ret = mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_DAC); + if (ret) + return ret; + + return mc13783_set_sysclk(dai, clk_id, freq, dir, MC13783_AUDIO_CODEC); +} + +static int mc13783_set_tdm_slot_dac(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + val = mc13783_read(codec, MC13783_SSI_NETWORK); + + val &= ~SSI_NETWORK_DAC_SLOT_MASK; + val &= ~SSI_NETWORK_DAC_RXSLOT_MASK; + + switch (slots) { + case 2: + val |= SSI_NETWORK_DAC_SLOTS_2; + break; + case 4: + val |= SSI_NETWORK_DAC_SLOTS_4; + break; + case 8: + val |= SSI_NETWORK_DAC_SLOTS_8; + break; + default: + return -EINVAL; + } + + switch (rx_mask) { + case 0xfffffffc: + val |= SSI_NETWORK_DAC_RXSLOT_0_1; + break; + case 0xfffffff3: + val |= SSI_NETWORK_DAC_RXSLOT_2_3; + break; + case 0xffffffcf: + val |= SSI_NETWORK_DAC_RXSLOT_4_5; + break; + case 0xffffff3f: + val |= SSI_NETWORK_DAC_RXSLOT_6_7; + break; + default: + return -EINVAL; + }; + + mc13783_write(codec, MC13783_SSI_NETWORK, val); + + return 0; +} + +static int mc13783_set_tdm_slot_codec(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + if (slots != 4) + return -EINVAL; + + if (tx_mask != 0xfffffffc) + return -EINVAL; + + val = mc13783_read(codec, MC13783_SSI_NETWORK); + + val &= ~(0x3f << 0); + val |= (0x00 << 2); /* primary timeslot RX/TX(?) is 0 */ + val |= (0x01 << 4); /* secondary timeslot TX is 1 */ + + mc13783_write(codec, MC13783_SSI_NETWORK, val); + + return 0; +} + +static int mc13783_set_tdm_slot_sync(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, int slots, + int slot_width) +{ + int ret; + + ret = mc13783_set_tdm_slot_dac(dai, tx_mask, rx_mask, slots, + slot_width); + if (ret) + return ret; + + ret = mc13783_set_tdm_slot_codec(dai, tx_mask, rx_mask, slots, + slot_width); + + return ret; +} + +static void mc13783_startup(struct snd_soc_dai *dai, int reg) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + val = mc13783_read(codec, reg); + val &= ~AUDIO_C_RESET; + val |= AUDIO_C_EN; + mc13783_write(codec, reg, val); +} + +static int mc13783_startup_dac(struct snd_pcm_substream *stream, + struct snd_soc_dai *dai) +{ + mc13783_startup(dai, MC13783_AUDIO_DAC); + return 0; +} + +static int mc13783_startup_codec(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + mc13783_startup(dai, MC13783_AUDIO_CODEC); + return 0; +} + +static int mc13783_startup_sync(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mc13783_startup(dai, MC13783_AUDIO_DAC); + else + mc13783_startup(dai, MC13783_AUDIO_CODEC); + return 0; +} + +static void mc13783_shutdown(struct snd_soc_dai *dai, int reg) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int val; + + val = mc13783_read(codec, reg); + mc13783_write(codec, reg, val & ~AUDIO_C_EN); +} + +static void mc13783_shutdown_dac(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + mc13783_shutdown(dai, MC13783_AUDIO_DAC); +} + +static void mc13783_shutdown_codec(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + mc13783_shutdown(dai, MC13783_AUDIO_CODEC); +} + +static void mc13783_shutdown_sync(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mc13783_shutdown(dai, MC13783_AUDIO_DAC); + else + mc13783_shutdown(dai, MC13783_AUDIO_CODEC); +} + +static int mc13783_get_alsp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = priv->mc13783_alsp_val; + + return 0; +} + +static int mc13783_put_alsp(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + priv->mc13783_alsp_val = ucontrol->value.integer.value[0]; + + val = mc13783_read(codec, MC13783_AUDIO_RX0); + + val &= ~(AUDIO_RX0_ALSPEN | AUDIO_RX0_ALSPSEL); + + if (priv->mc13783_alsp_val) + val |= AUDIO_RX0_ALSPEN; + + if (priv->mc13783_alsp_val == 2) + val |= AUDIO_RX0_ALSPSEL; + + mc13783_write(codec, MC13783_AUDIO_RX0, val); + + return 0; +} + +static int mc13783_ahsout_i_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.integer.value[0] = priv->mc13783_ahsout_val; + + return 0; +} + +static int mc13783_ahsout_i_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + unsigned int reg; + + priv->mc13783_ahsout_val = ucontrol->value.integer.value[0]; + + reg = mc13783_read(codec, MC13783_AUDIO_RX0); + + reg &= ~((1 << 13) | (1 << 14)); + + if (priv->mc13783_ahsout_val == 1) + reg |= 1 << 13; + else if (priv->mc13783_ahsout_val == 2) + reg |= 1 << 14; + + + mc13783_write(codec, MC13783_AUDIO_RX0, reg); + + return 0; +} + +static int mc13783_pcm_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int val; + + val = mc13783_read(codec, MC13783_AUDIO_RX0); + ucontrol->value.enumerated.item[0] = (val >> 22) & 1; + + return 0; +} + +static int mc13783_pcm_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r36, r37; + + r36 = mc13783_read(codec, MC13783_AUDIO_RX0); + r37 = mc13783_read(codec, MC13783_AUDIO_RX1); + + r36 &= ~AUDIO_RX0_ADDSTDC; + r37 &= ~AUDIO_RX1_PGASTEN; + + if (ucontrol->value.enumerated.item[0]) { + r36 |= AUDIO_RX0_ADDSTDC; + r37 |= AUDIO_RX1_PGASTEN; + } + + mc13783_write(codec, MC13783_AUDIO_RX0, r36); + mc13783_write(codec, MC13783_AUDIO_RX1, r37); + + return 0; +} + +static int mc13783_linein_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int val; + + val = mc13783_read(codec, MC13783_AUDIO_RX0); + ucontrol->value.enumerated.item[0] = (val >> 23) & 1; + + return 0; +} + +static int mc13783_linein_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r36, r37; + + r36 = mc13783_read(codec, MC13783_AUDIO_RX0); + r37 = mc13783_read(codec, MC13783_AUDIO_RX1); + + r36 &= ~AUDIO_RX0_ADDRXIN; + r37 &= ~AUDIO_RX1_ARXINEN; + + if (ucontrol->value.enumerated.item[0]) { + r36 |= AUDIO_RX0_ADDRXIN; + r37 |= AUDIO_RX1_ARXINEN; + } + + mc13783_write(codec, MC13783_AUDIO_RX0, r36); + mc13783_write(codec, MC13783_AUDIO_RX1, r37); + + return 0; +} + +static int mc13783_voice_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int val; + + val = mc13783_read(codec, MC13783_AUDIO_RX0); + ucontrol->value.enumerated.item[0] = (val >> 21) & 1; + + return 0; +} + +static int mc13783_voice_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r36, r37; + + r36 = mc13783_read(codec, MC13783_AUDIO_RX0); + r37 = mc13783_read(codec, MC13783_AUDIO_RX1); + + r36 &= ~AUDIO_RX0_ADDCDC; + r37 &= ~AUDIO_RX1_PGARXEN; + + if (ucontrol->value.enumerated.item[0]) { + r36 |= AUDIO_RX0_ADDCDC; + r37 |= AUDIO_RX1_PGARXEN; + } + + mc13783_write(codec, MC13783_AUDIO_RX0, r36); + mc13783_write(codec, MC13783_AUDIO_RX1, r37); + + return 0; +} + +static int mc13783_capure_cache; + +static int mc13783_get_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = mc13783_capure_cache; + return 0; +} + +static int mc13783_put_capture(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int r38, change; + + r38 = mc13783_read(codec, MC13783_AUDIO_TX); + + change = (mc13783_capure_cache != ucontrol->value.enumerated.item[0]); + mc13783_capure_cache = ucontrol->value.enumerated.item[0]; + r38 &= ~(AUDIO_TX_AMC1REN | AUDIO_TX_AMC2EN | AUDIO_TX_ATXINEN | + AUDIO_TX_RXINREC | AUDIO_TX_AMC1LEN); + + switch (mc13783_capure_cache) { + case 0: + break; + case 1: + r38 |= AUDIO_TX_RXINREC; + break; + case 2: + r38 |= AUDIO_TX_AMC1REN | AUDIO_TX_AMC1LEN; + break; + case 3: + r38 |= AUDIO_TX_AMC1REN; + break; + case 4: + r38 |= AUDIO_TX_AMC2EN; + break; + case 5: + r38 |= AUDIO_TX_AMC1LEN | AUDIO_TX_AMC2EN; + break; + case 6: + r38 |= AUDIO_TX_ATXINEN; + break; + case 7: + r38 |= AUDIO_TX_AMC1LEN | AUDIO_TX_ATXINEN; + break; + case 8: + r38 |= AUDIO_TX_AMC1LEN | AUDIO_TX_RXINREC; + break; + case 9: + r38 |= AUDIO_TX_AMC1LEN; + break; + default: + break; + } + + mc13783_write(codec, MC13783_AUDIO_TX, r38); + + return change; +} + +static const char *mc13783_asp[] = {"Off", "Codec", "Right"}; +static const char *mc13783_alsp[] = {"Off", "Codec", "Right"}; + +static const char *mc13783_ahs[] = {"Codec", "Mixer"}; + +static const char *mc13783_ahsout[] = {"Off", "Auto", "On"}; + +static const char *mc13783_arxout[] = {"Codec", "Mixer"}; + +static const char *mc13783_capture[] = {"off/off", "rxinl/rxinr", + "mc1lin/mc1rin", "off/mc1rin", "off/mc2in", "mc1lin/mc2in", + "off/txin", "mc1lin/txin", "mc1lin/rxinr", "mc1lin/off"}; + +static const char *mc13783_3d_mixer[] = {"Stereo", "Phase Mix", + "Mono", "Mono Mix"}; + +static const struct soc_enum mc13783_enum_asp = + SOC_ENUM_SINGLE(MC13783_AUDIO_RX0, 3, ARRAY_SIZE(mc13783_asp), mc13783_asp); + +static const struct soc_enum mc13783_enum_alsp = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_alsp), mc13783_alsp); + +static const struct soc_enum mc13783_enum_ahs = + SOC_ENUM_SINGLE(MC13783_AUDIO_RX0, 11, ARRAY_SIZE(mc13783_ahs), + mc13783_ahs); + +static const struct soc_enum mc13783_enum_ahsout = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_ahsout), mc13783_ahsout); + +static const struct soc_enum mc13783_enum_arxout = + SOC_ENUM_SINGLE(MC13783_AUDIO_RX0, 17, ARRAY_SIZE(mc13783_arxout), + mc13783_arxout); + +static const struct soc_enum mc13783_enum_capture = + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(mc13783_capture), mc13783_capture); + +static const struct soc_enum mc13783_enum_3d_mixer = + SOC_ENUM_SINGLE(MC13783_AUDIO_RX1, 16, ARRAY_SIZE(mc13783_3d_mixer), + mc13783_3d_mixer); + +static struct snd_kcontrol_new mc13783_control_list[] = { + /* Output Routing */ + SOC_ENUM("Asp Source", mc13783_enum_asp), + SOC_ENUM_EXT("Alsp Source", mc13783_enum_alsp, mc13783_get_alsp, + mc13783_put_alsp), + SOC_ENUM("Ahs Source", mc13783_enum_ahs), + SOC_SINGLE("Ahsr enable", MC13783_AUDIO_RX0, 9, 1, 0), + SOC_SINGLE("Ahsl enable", MC13783_AUDIO_RX0, 10, 1, 0), + SOC_ENUM_EXT("Ahs enable", mc13783_enum_ahsout, mc13783_ahsout_i_get, + mc13783_ahsout_i_put), + SOC_ENUM("Arxout Source", mc13783_enum_arxout), + SOC_SINGLE("ArxoutR enable", MC13783_AUDIO_RX0, 16, 1, 0), + SOC_SINGLE("ArxoutL enable", MC13783_AUDIO_RX0, 15, 1, 0), + SOC_SINGLE_EXT("PCM Playback Switch", 0, 0, 1, 0, mc13783_pcm_get, + mc13783_pcm_put), + SOC_SINGLE("PCM Playback Volume", MC13783_AUDIO_RX1, 6, 15, 0), + SOC_SINGLE_EXT("Line in Switch", 0, 0, 1, 0, mc13783_linein_get, + mc13783_linein_put), + SOC_SINGLE("Line in Volume", MC13783_AUDIO_RX1, 12, 15, 0), + SOC_ENUM_EXT("Capture Source", mc13783_enum_capture, mc13783_get_capture, + mc13783_put_capture), + SOC_DOUBLE("PCM Capture Volume", MC13783_AUDIO_TX, 19, 14, 31, 0), + SOC_ENUM("3D Control", mc13783_enum_3d_mixer), + SOC_SINGLE("MC1 Bias enable", 38, 0, 1, 0), + SOC_SINGLE("MC2 Bias enable", 38, 1, 1, 0), + SOC_SINGLE("Codec Bypass enable", MC13783_AUDIO_CODEC, 16, 1, 0), + SOC_SINGLE_EXT("Voice Codec Switch", 0, 0, 1, 0, mc13783_voice_get, + mc13783_voice_put), + SOC_SINGLE("Voice Codec Volume", MC13783_AUDIO_RX1, 1, 15, 0), +}; + +static int mc13783_probe(struct snd_soc_codec *codec) +{ + struct mc13783_priv *priv = snd_soc_codec_get_drvdata(codec); + int i, ret = 0, val; + + /* these are the reset values */ + priv->reg_cache[MC13783_AUDIO_RX0] = 0x001E00; + priv->reg_cache[MC13783_AUDIO_RX1] = 0x00d35A; + priv->reg_cache[MC13783_AUDIO_TX] = 0x420000; + priv->reg_cache[MC13783_SSI_NETWORK] = 0x013060; + priv->reg_cache[MC13783_AUDIO_CODEC] = 0x180027; + priv->reg_cache[MC13783_AUDIO_DAC] = 0x0e0004; + + /* VAUDIOON -> supply audio part, BIAS enable */ + priv->reg_cache[MC13783_AUDIO_RX0] |= 0x3; + + priv->mc13783_ahsout_val = 2; + + for (i = 36; i < 42; i++) + mc13783_write(codec, i, priv->reg_cache[i]); + + snd_soc_add_controls(codec, mc13783_control_list, + ARRAY_SIZE(mc13783_control_list)); + + val = mc13783_read(codec, MC13783_AUDIO_CODEC); + if (priv->adc_ssi_port == MC13783_SSI1_PORT) + val &= ~AUDIO_SSI_SEL; + else + val |= AUDIO_SSI_SEL; + mc13783_write(codec, MC13783_AUDIO_CODEC, val); + + val = mc13783_read(codec, MC13783_AUDIO_DAC); + if (priv->dac_ssi_port == MC13783_SSI1_PORT) + val &= ~AUDIO_SSI_SEL; + else + val |= AUDIO_SSI_SEL; + mc13783_write(codec, MC13783_AUDIO_DAC, val); + + return ret; +} + +static int mc13783_remove(struct snd_soc_codec *codec) +{ + unsigned int val; + + val = mc13783_read(codec, MC13783_AUDIO_RX0); + + /* VAUDIOON -> switch off audio part, BIAS disable */ + val &= ~0x3; + + mc13783_write(codec, MC13783_AUDIO_RX0, val); + + return 0; +} + +#define MC13783_RATES_RECORD (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000) + +#define MC13783_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops mc13783_ops_dac = { + .hw_params = mc13783_pcm_hw_params_dac, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_dac, + .set_tdm_slot = mc13783_set_tdm_slot_dac, + .prepare = mc13783_startup_dac, + .shutdown = mc13783_shutdown_dac, +}; + +static struct snd_soc_dai_ops mc13783_ops_codec = { + .hw_params = mc13783_pcm_hw_params_codec, + .set_fmt = mc13783_set_fmt_async, + .set_sysclk = mc13783_set_sysclk_codec, + .set_tdm_slot = mc13783_set_tdm_slot_codec, + .prepare = mc13783_startup_codec, + .shutdown = mc13783_shutdown_codec, +}; + +/* + * The mc13783 has two SSI ports, both of them can be routed either + * to the voice codec or the stereo DAC. When two different SSI ports + * are used for the voice codec and the stereo DAC we can do different + * formats and sysclock settings for playback and capture + * (mc13783-hifi-playback and mc13783-hifi-capture). Using the same port + * forces us to use symmetric rates (mc13783-hifi). + */ +static struct snd_soc_dai_driver mc13783_dai_async[] = { + { + .name = "mc13783-hifi-playback", + .id = MC13783_ID_STEREO_DAC, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_dac, + }, { + .name = "mc13783-hifi-capture", + .id = MC13783_ID_STEREO_CODEC, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_codec, + }, +}; + +static struct snd_soc_dai_ops mc13783_ops_sync = { + .hw_params = mc13783_pcm_hw_params_sync, + .set_fmt = mc13783_set_fmt_sync, + .set_sysclk = mc13783_set_sysclk_sync, + .set_tdm_slot = mc13783_set_tdm_slot_sync, + .prepare = mc13783_startup_sync, + .shutdown = mc13783_shutdown_sync, +}; + +static struct snd_soc_dai_driver mc13783_dai_sync[] = { + { + .name = "mc13783-hifi", + .id = MC13783_ID_SYNC, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = MC13783_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = MC13783_RATES_RECORD, + .formats = MC13783_FORMATS, + }, + .ops = &mc13783_ops_sync, + .symmetric_rates = 1, + } +}; + +static struct snd_soc_codec_driver soc_codec_dev_mc13783 = { + .probe = mc13783_probe, + .remove = mc13783_remove, + .read = mc13783_read, + .write = mc13783_write, +}; + +static int mc13783_codec_probe(struct platform_device *pdev) +{ + struct mc13xxx *mc13xxx; + struct mc13783_priv *priv; + struct mc13xxx_codec_platform_data *pdata = pdev->dev.platform_data; + int ret; + + mc13xxx = dev_get_drvdata(pdev->dev.parent); + + priv = kzalloc(sizeof(struct mc13783_priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + + dev_set_drvdata(&pdev->dev, priv); + priv->mc13xxx = mc13xxx; + if (pdata) { + priv->adc_ssi_port = pdata->adc_ssi_port; + priv->dac_ssi_port = pdata->dac_ssi_port; + } else { + priv->adc_ssi_port = MC13783_SSI1_PORT; + priv->dac_ssi_port = MC13783_SSI2_PORT; + } + + if (priv->adc_ssi_port == priv->dac_ssi_port) + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_mc13783, + mc13783_dai_sync, ARRAY_SIZE(mc13783_dai_sync)); + else + ret = snd_soc_register_codec(&pdev->dev, &soc_codec_dev_mc13783, + mc13783_dai_async, ARRAY_SIZE(mc13783_dai_async)); + + if (ret) + goto err_register_codec; + + return 0; + +err_register_codec: + dev_err(&pdev->dev, "register codec failed with %d\n", ret); + kfree(priv); + + return ret; +} + +static int mc13783_codec_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + + return 0; +} + +static struct platform_driver mc13783_codec_driver = { + .driver = { + .name = "mc13783-codec", + .owner = THIS_MODULE, + }, + .probe = mc13783_codec_probe, + .remove = __devexit_p(mc13783_codec_remove), +}; + +static __init int mc13783_init(void) +{ + return platform_driver_register(&mc13783_codec_driver); +} + +static __exit void mc13783_exit(void) +{ + platform_driver_unregister(&mc13783_codec_driver); +} + +module_init(mc13783_init); +module_exit(mc13783_exit); + +MODULE_DESCRIPTION("ASoC MC13783 driver"); +MODULE_AUTHOR("Sascha Hauer, Pengutronix s.hauer@pengutronix.de"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/mc13783.h b/sound/soc/codecs/mc13783.h new file mode 100644 index 0000000..ae37e99 --- /dev/null +++ b/sound/soc/codecs/mc13783.h @@ -0,0 +1,30 @@ +/* + * Copyright 2008 Juergen Beisert, kernel@pengutronix.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. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef MC13783_MIXER_H +#define MC13783_MIXER_H + +extern int mc13783_add_ctl(struct snd_card*, void *); + +#define MC13783_CLK_CLIA 1 +#define MC13783_CLK_CLIB 2 + +#define MC13783_ID_STEREO_DAC 1 +#define MC13783_ID_STEREO_CODEC 2 +#define MC13783_ID_SYNC 3 + +#endif /* MC13783_MIXER_H */
Several boards are using the mc13783 codec with the same configuration. Provide an unique asoc platform driver for it.
Signed-off-by: Philippe Rétornaz philippe.retornaz@epfl.ch --- sound/soc/imx/Kconfig | 10 +++ sound/soc/imx/Makefile | 2 + sound/soc/imx/imx3sound-mc13783.c | 141 +++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 0 deletions(-) create mode 100644 sound/soc/imx/imx3sound-mc13783.c
diff --git a/sound/soc/imx/Kconfig b/sound/soc/imx/Kconfig index 7383917..244a5fe 100644 --- a/sound/soc/imx/Kconfig +++ b/sound/soc/imx/Kconfig @@ -57,4 +57,14 @@ config SND_SOC_EUKREA_TLV320 Enable I2S based access to the TLV320AIC23B codec attached to the SSI interface
+config SND_SOC_IMX3SOUND_MC13783 + tristate "SoC Audio support for imx31 with MC13783 codecs" + depends on IMX_SDMA + select SND_MXC_SOC_SSI + select SND_SOC_MC13783 + select SND_MXC_SOC_MX2 + help + Say Y if you want to add support for SoC audio on mx31moboard + and imx31pdk_3ds boards using a MC13783 Codec. + endif # SND_IMX_SOC diff --git a/sound/soc/imx/Makefile b/sound/soc/imx/Makefile index d6d609b..742fd35 100644 --- a/sound/soc/imx/Makefile +++ b/sound/soc/imx/Makefile @@ -12,8 +12,10 @@ snd-soc-eukrea-tlv320-objs := eukrea-tlv320.o snd-soc-phycore-ac97-objs := phycore-ac97.o snd-soc-mx27vis-aic32x4-objs := mx27vis-aic32x4.o snd-soc-wm1133-ev1-objs := wm1133-ev1.o +snd-soc-imx3sound-mc13783-objs := imx3sound-mc13783.o
obj-$(CONFIG_SND_SOC_EUKREA_TLV320) += snd-soc-eukrea-tlv320.o obj-$(CONFIG_SND_SOC_PHYCORE_AC97) += snd-soc-phycore-ac97.o obj-$(CONFIG_SND_SOC_MX27VIS_AIC32X4) += snd-soc-mx27vis-aic32x4.o obj-$(CONFIG_SND_MXC_SOC_WM1133_EV1) += snd-soc-wm1133-ev1.o +obj-$(CONFIG_SND_SOC_IMX3SOUND_MC13783) += snd-soc-imx3sound-mc13783.o diff --git a/sound/soc/imx/imx3sound-mc13783.c b/sound/soc/imx/imx3sound-mc13783.c new file mode 100644 index 0000000..b11e455 --- /dev/null +++ b/sound/soc/imx/imx3sound-mc13783.c @@ -0,0 +1,141 @@ +/* + * imx3sound-mc13783.c -- SoC audio for imx31_3ds based boards + * + * Copyright 2012 Philippe Retornaz, philippe.retornaz@epfl.ch + * + * Heavly based on phycore-mc13783: + * Copyright 2009 Sascha Hauer, Pengutronix s.hauer@pengutronix.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 <linux/moduleparam.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <asm/mach-types.h> + +#include "../codecs/mc13783.h" +#include "imx-ssi.h" + +static struct snd_soc_card imx3sound; + +#define FMT_SSI (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | \ + SND_SOC_DAIFMT_CBM_CFM) + +static int imx3sound_hifi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + int ret; + + ret = snd_soc_dai_set_fmt(codec_dai, FMT_SSI); + if (ret) + return ret; + + ret = snd_soc_dai_set_fmt(cpu_dai, FMT_SSI); + if (ret) + return ret; + + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xfffffffc, 0xfffffffc, 4, 16); + if (ret) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, MC13783_CLK_CLIA, 26000000, 0); + if (ret) + return ret; + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x0, 0xfffffffc, 2, 16); + if (ret) + return ret; + + return 0; +} + +static int imx3sound_hifi_hw_free(struct snd_pcm_substream *substream) +{ + return 0; +} + +static struct snd_soc_ops imx3sound_hifi_ops = { + .hw_params = imx3sound_hifi_hw_params, + .hw_free = imx3sound_hifi_hw_free, +}; + +static int imx3sound_probe(struct snd_soc_card *dev) +{ + return 0; +} + +static int imx3sound_remove(struct snd_soc_card *dev) +{ + return 0; +} + +static struct snd_soc_dai_link imx3sound_dai_mc13783[] = { + { + .name = "MC13783", + .stream_name = "Sound", + .codec_dai_name = "mc13783-hifi", + .codec_name = "mc13783-codec", + .cpu_dai_name = "imx-ssi.0", + .platform_name = "imx-pcm-audio.0", + .ops = &imx3sound_hifi_ops, + .symmetric_rates = 1, + }, +}; + +static struct snd_soc_card imx3sound = { + .name = "imx3sound", + .probe = imx3sound_probe, + .remove = imx3sound_remove, + .dai_link = imx3sound_dai_mc13783, + .num_links = ARRAY_SIZE(imx3sound_dai_mc13783), +}; + +static struct platform_device *imx3sound_snd_device; + +static int __init imx3sound_init(void) +{ + int ret; + + if (!(machine_is_mx31moboard() || machine_is_mx31_3ds())) + /* return happy. We might run on a totally different machine */ + return 0; + + imx3sound_snd_device = platform_device_alloc("soc-audio", -1); + if (!imx3sound_snd_device) + return -ENOMEM; + + platform_set_drvdata(imx3sound_snd_device, &imx3sound); + ret = platform_device_add(imx3sound_snd_device); + + if (ret) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + platform_device_put(imx3sound_snd_device); + } + + return ret; +} + +static void __exit imx3sound_exit(void) +{ + platform_device_unregister(imx3sound_snd_device); +} + +late_initcall(imx3sound_init); +module_exit(imx3sound_exit); + +MODULE_AUTHOR("Sascha Hauer s.hauer@pengutronix.de"); +MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch"); +MODULE_DESCRIPTION("mx31moboard & mx31_3ds ALSA SoC driver"); +MODULE_LICENSE("GPL");
Signed-off-by: Philippe Rétornaz philippe.retornaz@epfl.ch --- arch/arm/mach-imx/Kconfig | 1 + arch/arm/mach-imx/mach-mx31moboard.c | 37 +++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 4defb97..2c60f63 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -493,6 +493,7 @@ config MACH_MX31MOBOARD select SOC_IMX31 select IMX_HAVE_PLATFORM_FSL_USB2_UDC select IMX_HAVE_PLATFORM_IMX_I2C + select IMX_HAVE_PLATFORM_IMX_SSI select IMX_HAVE_PLATFORM_IMX_UART select IMX_HAVE_PLATFORM_IPU_CORE select IMX_HAVE_PLATFORM_MXC_EHCI diff --git a/arch/arm/mach-imx/mach-mx31moboard.c b/arch/arm/mach-imx/mach-mx31moboard.c index f225262..23e9d6e 100644 --- a/arch/arm/mach-imx/mach-mx31moboard.c +++ b/arch/arm/mach-imx/mach-mx31moboard.c @@ -42,11 +42,13 @@ #include <asm/mach/time.h> #include <asm/mach/map.h> #include <asm/memblock.h> +#include <mach/audmux.h> #include <mach/board-mx31moboard.h> #include <mach/common.h> #include <mach/hardware.h> #include <mach/iomux-mx3.h> #include <mach/ulpi.h> +#include <mach/ssi.h>
#include "devices-imx31.h"
@@ -102,6 +104,9 @@ static unsigned int moboard_pins[] = { MX31_PIN_CSPI3_MOSI__MOSI, MX31_PIN_CSPI3_MISO__MISO, MX31_PIN_CSPI3_SCLK__SCLK, MX31_PIN_CSPI3_SPI_RDY__SPI_RDY, MX31_PIN_CSPI2_SS1__CSPI3_SS1, + /* SSI */ + MX31_PIN_STXD4__STXD4, MX31_PIN_SRXD4__SRXD4, + MX31_PIN_SCK4__SCK4, MX31_PIN_SFS4__SFS4, };
static struct physmap_flash_data mx31moboard_flash_data = { @@ -276,6 +281,11 @@ static struct mc13xxx_buttons_platform_data moboard_buttons = { .b1on_key = KEY_POWER, };
+static struct mc13xxx_codec_platform_data moboard_codec = { + .dac_ssi_port = MC13783_SSI1_PORT, + .adc_ssi_port = MC13783_SSI1_PORT, +}; + static struct mc13xxx_platform_data moboard_pmic = { .regulators = { .regulators = moboard_regulators, @@ -283,7 +293,12 @@ static struct mc13xxx_platform_data moboard_pmic = { }, .leds = &moboard_leds, .buttons = &moboard_buttons, - .flags = MC13XXX_USE_RTC | MC13XXX_USE_ADC, + .codec = &moboard_codec, + .flags = MC13XXX_USE_RTC | MC13XXX_USE_ADC | MC13XXX_USE_CODEC, +}; + +static struct imx_ssi_platform_data moboard_ssi_pdata = { + .flags = IMX_SSI_DMA | IMX_SSI_NET, };
static struct spi_board_info moboard_spi_board_info[] __initdata = { @@ -527,6 +542,24 @@ static void __init mx31moboard_init(void) mxc_iomux_setup_multiple_pins(moboard_pins, ARRAY_SIZE(moboard_pins), "moboard");
+ mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + MXC_AUDMUX_V2_PTCR_SYN, + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) | + MXC_AUDMUX_V2_PDCR_MODE(1) | + MXC_AUDMUX_V2_PDCR_INMMASK(0xfc)); + + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + MXC_AUDMUX_V2_PTCR_SYN | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_RFSDIR | + MXC_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_RCLKDIR | + MXC_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4), + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4)); + platform_add_devices(devices, ARRAY_SIZE(devices)); gpio_led_register_device(-1, &mx31moboard_led_pdata);
@@ -552,6 +585,8 @@ static void __init mx31moboard_init(void)
moboard_usbh2_init();
+ imx31_add_imx_ssi(0, &moboard_ssi_pdata); + pm_power_off = mx31moboard_poweroff;
switch (mx31moboard_baseboard) {
Signed-off-by: Philippe Rétornaz philippe.retornaz@epfl.ch --- arch/arm/mach-imx/Kconfig | 1 + arch/arm/mach-imx/mach-mx31_3ds.c | 40 ++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletions(-)
diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig index 2c60f63..61084f2 100644 --- a/arch/arm/mach-imx/Kconfig +++ b/arch/arm/mach-imx/Kconfig @@ -468,6 +468,7 @@ config MACH_MX31_3DS select IMX_HAVE_PLATFORM_IMX2_WDT select IMX_HAVE_PLATFORM_IMX_I2C select IMX_HAVE_PLATFORM_IMX_KEYPAD + select IMX_HAVE_PLATFORM_IMX_SSI select IMX_HAVE_PLATFORM_IMX_UART select IMX_HAVE_PLATFORM_IPU_CORE select IMX_HAVE_PLATFORM_MXC_EHCI diff --git a/arch/arm/mach-imx/mach-mx31_3ds.c b/arch/arm/mach-imx/mach-mx31_3ds.c index 4d1aab1..a00efda 100644 --- a/arch/arm/mach-imx/mach-mx31_3ds.c +++ b/arch/arm/mach-imx/mach-mx31_3ds.c @@ -41,6 +41,8 @@ #include <mach/iomux-mx3.h> #include <mach/3ds_debugboard.h> #include <mach/ulpi.h> +#include <mach/audmux.h> +#include <mach/ssi.h>
#include "devices-imx31.h"
@@ -156,6 +158,11 @@ static int mx31_3ds_pins[] = { MX31_PIN_CSI_VSYNC__CSI_VSYNC, MX31_PIN_CSI_D5__GPIO3_5, /* CMOS PWDN */ IOMUX_MODE(MX31_PIN_RI_DTE1, IOMUX_CONFIG_GPIO), /* CMOS reset */ + /* SSI */ + MX31_PIN_STXD4__STXD4, + MX31_PIN_SRXD4__SRXD4, + MX31_PIN_SCK4__SCK4, + MX31_PIN_SFS4__SFS4, };
/* @@ -488,12 +495,23 @@ static struct mc13xxx_regulator_init_data mx31_3ds_regulators[] = { };
/* MC13783 */ +static struct mc13xxx_codec_platform_data mx31pdk_codec = { + .dac_ssi_port = MC13783_SSI1_PORT, + .adc_ssi_port = MC13783_SSI1_PORT, +}; + static struct mc13xxx_platform_data mc13783_pdata = { .regulators = { .regulators = mx31_3ds_regulators, .num_regulators = ARRAY_SIZE(mx31_3ds_regulators), }, - .flags = MC13XXX_USE_TOUCHSCREEN | MC13XXX_USE_RTC, + .codec = &mx31pdk_codec, + .flags = MC13XXX_USE_TOUCHSCREEN | MC13XXX_USE_RTC | + MC13XXX_USE_CODEC, +}; + +static struct imx_ssi_platform_data mx31pdk_ssi_pdata = { + .flags = IMX_SSI_DMA | IMX_SSI_NET, };
/* SPI */ @@ -694,6 +712,24 @@ static void __init mx31_3ds_init(void) mxc_iomux_setup_multiple_pins(mx31_3ds_pins, ARRAY_SIZE(mx31_3ds_pins), "mx31_3ds");
+ mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT4_SSI_PINS_4, + MXC_AUDMUX_V2_PTCR_SYN, + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT1_SSI0) | + MXC_AUDMUX_V2_PDCR_MODE(1) | + MXC_AUDMUX_V2_PDCR_INMMASK(0xfc)); + + mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0, + MXC_AUDMUX_V2_PTCR_SYN | + MXC_AUDMUX_V2_PTCR_TFSDIR | + MXC_AUDMUX_V2_PTCR_TFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_TCLKDIR | + MXC_AUDMUX_V2_PTCR_TCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_RFSDIR | + MXC_AUDMUX_V2_PTCR_RFSEL(MX31_AUDMUX_PORT4_SSI_PINS_4) | + MXC_AUDMUX_V2_PTCR_RCLKDIR | + MXC_AUDMUX_V2_PTCR_RCSEL(MX31_AUDMUX_PORT4_SSI_PINS_4), + MXC_AUDMUX_V2_PDCR_RXDSEL(MX31_AUDMUX_PORT4_SSI_PINS_4)); + imx31_add_imx_uart0(&uart_pdata); imx31_add_mxc_nand(&mx31_3ds_nand_board_info);
@@ -741,6 +777,8 @@ static void __init mx31_3ds_init(void) }
mx31_3ds_init_camera(); + + imx31_add_imx_ssi(0, &mx31pdk_ssi_pdata); }
static void __init mx31_3ds_timer_init(void)
On Fri, Mar 09, 2012 at 03:55:15PM +0100, Philippe Rétornaz wrote:
- mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0,
MXC_AUDMUX_V2_PTCR_SYN |
MXC_AUDMUX_V2_PTCR_TFSDIR |
This will need to be rebased off current code, the audmux has been moved into sound/soc.
Philippe,
2012/3/11 Mark Brown broonie@opensource.wolfsonmicro.com:
On Fri, Mar 09, 2012 at 03:55:15PM +0100, Philippe Rétornaz wrote:
- mxc_audmux_v2_configure_port(MX31_AUDMUX_PORT1_SSI0,
- MXC_AUDMUX_V2_PTCR_SYN |
- MXC_AUDMUX_V2_PTCR_TFSDIR |
This will need to be rebased off current code, the audmux has been moved into sound/soc.
You can use linux-next tree as it already contains the recent audmux changes.
Thanks,
Fabio Estevam
On Fri, Mar 09, 2012 at 03:55:14PM +0100, Philippe Rétornaz wrote:
+static int imx3sound_hifi_hw_free(struct snd_pcm_substream *substream) +{
- return 0;
+}
Remove all empty functions.
- imx3sound_snd_device = platform_device_alloc("soc-audio", -1);
- if (!imx3sound_snd_device)
return -ENOMEM;
No soc-audio usage, use snd_soc_register_card() from a platform device.
On Fri, Mar 9, 2012 at 11:55 AM, Philippe Rétornaz philippe.retornaz@epfl.ch wrote:
--- /dev/null +++ b/sound/soc/imx/imx3sound-mc13783.c @@ -0,0 +1,141 @@ +/*
- imx3sound-mc13783.c -- SoC audio for imx31_3ds based boards
Please rename this file to "imx-mc13783.c".
mx27pdk board also has a mc13783 and will use this file.
It is not restricted to mx3 boards.
Thanks,
Fabio Estevam
Le dimanche 11 mars 2012 11:59:40 Fabio Estevam a écrit :
On Fri, Mar 9, 2012 at 11:55 AM, Philippe Rétornaz
philippe.retornaz@epfl.ch wrote:
--- /dev/null +++ b/sound/soc/imx/imx3sound-mc13783.c @@ -0,0 +1,141 @@ +/*
- imx3sound-mc13783.c -- SoC audio for imx31_3ds based boards
Please rename this file to "imx-mc13783.c".
mx27pdk board also has a mc13783 and will use this file.
It is not restricted to mx3 boards.
Ok, will do.
Thanks,
Philippe
On Fri, Mar 09, 2012 at 03:55:13PM +0100, Philippe Rétornaz wrote:
From: Sascha Hauer s.hauer@pengutronix.de
sha 20111307:
- rebased to 3.0-rc2
- implement tdm slot settings
- made it work with single ssi port
- more register names instead of hardcoded numbers
philippe 20120803:
- Add headset detection
- add mic2 bias
- enable headset output by default
If including stuff like this please include it after the ---. Things like notes about the odd arrangements with the SSI ports which will have ongoing meaning are much more useful in the changelog.
Overall this looks fairly clean but very out of date, for example there's a custom register cache impelementation and no DAPM.
diff --git a/drivers/mfd/mc13xxx-core.c b/drivers/mfd/mc13xxx-core.c index 7122386..7b7b55e 100644 --- a/drivers/mfd/mc13xxx-core.c +++ b/drivers/mfd/mc13xxx-core.c @@ -807,7 +807,8 @@ err_revision: mc13xxx_add_subdevice(mc13xxx, "%s-adc");
if (mc13xxx->flags & MC13XXX_USE_CODEC)
mc13xxx_add_subdevice(mc13xxx, "%s-codec");
mc13xxx_add_subdevice_pdata(mc13xxx, "%s-codec",
pdata->codec, sizeof(*pdata->codec));
if (mc13xxx->flags & MC13XXX_USE_RTC) mc13xxx_add_subdevice(mc13xxx, "%s-rtc");
It's probably worth pushing the MFD updates separately to Samuel, ideally they could go in during the next merge window even if the CODEC driver doesn't make it.
select SND_SOC_MAX98095 if I2C select SND_SOC_MAX9850 if I2C select SND_SOC_MAX9877 if I2C
- select SND_SOC_MC13783 if SPI select SND_SOC_PCM3008
This should depend on the MFD core, not the bus - it won't build without the MFD core.
+struct mc13783_priv {
- struct snd_soc_codec codec;
- struct mc13xxx *mc13xxx;
- u32 reg_cache[42];
No custom register caches please (use the framework), and magic numbers are generally bad. Ideally the MFD would be converted over to regmap which would give even more of a win.
- /* DAI clock master masks */
- switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
- case SND_SOC_DAIFMT_CBM_CFM:
val |= AUDIO_C_CLK_EN;
break;
- case SND_SOC_DAIFMT_CBS_CFS:
val |= AUDIO_CSM;
break;
- case SND_SOC_DAIFMT_CBM_CFS:
- case SND_SOC_DAIFMT_CBS_CFM:
return -EINVAL;
- }
- val |= AUDIO_C_RESET;
- mc13783_write(codec, reg, val);
This would be better using snd_soc_update_bits(), that way repeated calls won't actually write to the hardware. A similar issue applies throughout, in some cases it looks like this will also fix locking issues with doing read/modify/writes on partial register bits.
+static int mc13783_set_fmt_sync(struct snd_soc_dai *dai, unsigned int fmt) +{
- int ret;
- ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_DAC);
- if (ret)
return ret;
- /*
* In synchronous mode force the voice codec into slave mode
* so that the clock / framesync from the stereo DAC is used
*/
- fmt &= ~SND_SOC_DAIFMT_MASTER_MASK;
- fmt |= SND_SOC_DAIFMT_CBS_CFS;
- ret = mc13783_set_fmt(dai, fmt, MC13783_AUDIO_CODEC);
This looks like you want to be complaining about some invalid setups?
+static void mc13783_startup(struct snd_soc_dai *dai, int reg) +{
- struct snd_soc_codec *codec = dai->codec;
- unsigned int val;
- val = mc13783_read(codec, reg);
- val &= ~AUDIO_C_RESET;
- val |= AUDIO_C_EN;
- mc13783_write(codec, reg, val);
+}
This looks like it should be either DAPM or set_bias_level...
+static void mc13783_shutdown(struct snd_soc_dai *dai, int reg) +{
- struct snd_soc_codec *codec = dai->codec;
- unsigned int val;
- val = mc13783_read(codec, reg);
- mc13783_write(codec, reg, val & ~AUDIO_C_EN);
+}
...and I suspect there's issues with simultaneous playback and record.
+static int mc13783_capure_cache;
Don't use global statics, use the private data.
+static const char *mc13783_asp[] = {"Off", "Codec", "Right"}; +static const char *mc13783_alsp[] = {"Off", "Codec", "Right"};
+static const char *mc13783_ahs[] = {"Codec", "Mixer"};
+static const char *mc13783_ahsout[] = {"Off", "Auto", "On"};
+static const char *mc13783_arxout[] = {"Codec", "Mixer"};
A lot of this looks like it should be in DAPM - it looks like routing control.
+static const char *mc13783_capture[] = {"off/off", "rxinl/rxinr",
- "mc1lin/mc1rin", "off/mc1rin", "off/mc2in", "mc1lin/mc2in",
- "off/txin", "mc1lin/txin", "mc1lin/rxinr", "mc1lin/off"};
This does too, looking at the code that implements the enum it appears that the chip is more flexible than the what's being offered here, it looks like there's a bunch of separate controls.
- SOC_SINGLE("MC1 Bias enable", 38, 0, 1, 0),
- SOC_SINGLE("MC2 Bias enable", 38, 1, 1, 0),
This should definitely be DAPM, users shouldn't have to manually control the bias from userspace.
- priv = kzalloc(sizeof(struct mc13783_priv), GFP_KERNEL);
- if (priv == NULL)
return -ENOMEM;
devm_kzalloc() (which will fix a memory leak on remove).
+static __init int mc13783_init(void) +{
- return platform_driver_register(&mc13783_codec_driver);
+}
module_platform_driver().
Le dimanche 11 mars 2012 11:38:48 Mark Brown a écrit :
On Fri, Mar 09, 2012 at 03:55:13PM +0100, Philippe Rétornaz wrote:
From: Sascha Hauer s.hauer@pengutronix.de
sha 20111307:
- rebased to 3.0-rc2
- implement tdm slot settings
- made it work with single ssi port
- more register names instead of hardcoded numbers
philippe 20120803:
- Add headset detection
- add mic2 bias
- enable headset output by default
If including stuff like this please include it after the ---. Things like notes about the odd arrangements with the SSI ports which will have ongoing meaning are much more useful in the changelog.
Overall this looks fairly clean but very out of date, for example there's a custom register cache impelementation and no DAPM.
Thank you for this detailed review. I will redo the patch based on your comments. I had a quick look at DAPM (dapm.txt in asoc documentation), but is there any good "reference" implementation which is in the kernel where I could have a look ?
Thanks,
Philippe
On Mon, Mar 12, 2012 at 09:11:08AM +0100, Philippe Rétornaz wrote:
Thank you for this detailed review. I will redo the patch based on your comments. I had a quick look at DAPM (dapm.txt in asoc documentation), but is there any good "reference" implementation which is in the kernel where I could have a look ?
Pretty much all ASoC drivers use DAPM and there's really not much variation in quality in terms of how they do so, it's mostly an either it works or doesn't thing. Just look for something maintained that you can follow and/or which is fairly close to your hardware.
participants (3)
-
Fabio Estevam
-
Mark Brown
-
Philippe Rétornaz