diff options
Diffstat (limited to 'sound/soc/codecs/da7210.c')
| -rw-r--r-- | sound/soc/codecs/da7210.c | 589 | 
1 files changed, 589 insertions, 0 deletions
| diff --git a/sound/soc/codecs/da7210.c b/sound/soc/codecs/da7210.c new file mode 100644 index 000000000000..cf2975a7294a --- /dev/null +++ b/sound/soc/codecs/da7210.c @@ -0,0 +1,589 @@ +/* + * 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_MIC_L			0x07 +#define DA7210_MIC_R			0x08 +#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_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) + +/* 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_VOICE_EN		(1 << 7) + +/* ADC bit fields */ +#define DA7210_ADC_L_EN			(1 << 3) +#define DA7210_ADC_R_EN			(1 << 7) + +/* DAC_HPF fields */ +#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 u32 da7210_read_reg_cache(struct snd_soc_codec *codec, u32 reg) +{ +	u8 *cache = codec->reg_cache; +	BUG_ON(reg > ARRAY_SIZE(da7210_reg)); +	return cache[reg]; +} + +/* + * Write to the da7210 register space + */ +static int da7210_write(struct snd_soc_codec *codec, u32 reg, u32 value) +{ +	u8 *cache = codec->reg_cache; +	u8 data[2]; + +	BUG_ON(codec->volatile_register); + +	data[0] = reg & 0xff; +	data[1] = value & 0xff; + +	if (reg >= codec->reg_cache_size) +		return -EIO; + +	if (2 != codec->hw_write(codec->control_data, data, 2)) +		return -EIO; + +	cache[reg] = value; +	return 0; +} + +/* + * Read from the da7210 register space. + */ +static inline u32 da7210_read(struct snd_soc_codec *codec, u32 reg) +{ +	if (DA7210_STATUS == reg) +		return i2c_smbus_read_byte_data(codec->control_data, reg); + +	return da7210_read_reg_cache(codec, reg); +} + +static int da7210_startup(struct snd_pcm_substream *substream, +			  struct snd_soc_dai *dai) +{ +	int is_play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK; +	struct snd_soc_codec *codec = dai->codec; + +	if (is_play) { +		/* PlayBack Volume 40 */ +		snd_soc_update_bits(codec, DA7210_HP_L_VOL, 0x3F, 40); +		snd_soc_update_bits(codec, DA7210_HP_R_VOL, 0x3F, 40); + +		/* Enable Out */ +		snd_soc_update_bits(codec, DA7210_OUTMIX_L, 0x1F, 0x10); +		snd_soc_update_bits(codec, DA7210_OUTMIX_R, 0x1F, 0x10); + +	} else { +		/* Volume 7 */ +		snd_soc_update_bits(codec, DA7210_MIC_L, 0x7, 0x7); +		snd_soc_update_bits(codec, DA7210_MIC_R, 0x7, 0x7); + +		/* Enable Mic */ +		snd_soc_update_bits(codec, DA7210_INMIX_L, 0x1F, 0x1); +		snd_soc_update_bits(codec, DA7210_INMIX_R, 0x1F, 0x1); +	} + +	return 0; +} + +/* + * Set PCM DAI word length. + */ +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; +	u32 dai_cfg1; +	u32 reg, mask; + +	/* 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); + +	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; +	} + +	snd_soc_update_bits(codec, reg, mask, 0); + +	return 0; +} + +/* + * Set DAI mode and Format + */ +static int da7210_set_dai_fmt(struct snd_soc_dai *codec_dai, u32 fmt) +{ +	struct snd_soc_codec *codec = codec_dai->codec; +	u32 dai_cfg1; +	u32 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 = { +	.startup	= da7210_startup, +	.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_codec(codec); +	if (ret) { +		dev_err(codec->dev, "Failed to register CODEC: %d\n", ret); +		goto init_err; +	} + +	ret = snd_soc_register_dai(&da7210_dai); +	if (ret) { +		dev_err(codec->dev, "Failed to register DAI: %d\n", ret); +		goto init_err; +	} + +	/* FIXME +	 * +	 * This driver use fixed value here +	 */ + +	/* +	 * 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 Left and Right ADC */ +	da7210_write(codec, DA7210_ADC, DA7210_ADC_L_EN | DA7210_ADC_R_EN); + +	/* +	 * DAC settings +	 */ + +	/* 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); + +	/* 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); + +	/* Activate all enabled subsystem */ +	da7210_write(codec, DA7210_STARTUP1, DA7210_SC_MST_EN); + +	return ret; + +init_err: +	kfree(codec->reg_cache); +	codec->reg_cache = NULL; + +	return ret; + +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int __devinit 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 __devexit 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); + +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 = 0; +#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"); | 
