diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0348bdde60cc91da9c0c22e5cf9fb4e51d81345e..7c7969a6ffdeead191f470c390889567f78a2fde 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -50,6 +50,8 @@ config SND_SOC_ALL_CODECS imply SND_SOC_AK5558 imply SND_SOC_ALC5623 imply SND_SOC_ALC5632 + imply SND_SOC_ALC655 + imply SND_SOC_AC97_STANDARD imply SND_SOC_BT_SCO imply SND_SOC_BD28623 imply SND_SOC_CQ0093VC @@ -1466,6 +1468,16 @@ config SND_SOC_WCD934X config SND_SOC_WL1273 tristate +config SND_SOC_ALC655 + depends on SND_SOC_AC97_BUS + select REGMAP_AC97 + tristate + +config SND_SOC_AC97_STANDARD + depends on SND_SOC_AC97_BUS + select REGMAP_AC97 + tristate + config SND_SOC_WM0010 tristate depends on SPI_MASTER diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 6cd9ddbb5a1829b5c83934383017990e05a3bb8f..d368c85c065d1e200354162913e959ed4ecc39eb 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -191,6 +191,8 @@ snd-soc-rt715-objs := rt715.o rt715-sdw.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o +snd-soc-alc655-objs := alc655.o +snd-soc-ac97_standard-objs := ac97_standard.o snd-soc-sigmadsp-objs := sigmadsp.o snd-soc-sigmadsp-i2c-objs := sigmadsp-i2c.o snd-soc-sigmadsp-regmap-objs := sigmadsp-regmap.o @@ -551,6 +553,8 @@ obj-$(CONFIG_SND_SOC_UDA134X) += snd-soc-uda134x.o obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WCD9335) += snd-soc-wcd9335.o obj-$(CONFIG_SND_SOC_WCD934X) += snd-soc-wcd934x.o +obj-$(CONFIG_SND_SOC_ALC655) += snd-soc-alc655.o +obj-$(CONFIG_SND_SOC_AC97_STANDARD) += snd-soc-ac97_standard.o obj-$(CONFIG_SND_SOC_WL1273) += snd-soc-wl1273.o obj-$(CONFIG_SND_SOC_WM0010) += snd-soc-wm0010.o obj-$(CONFIG_SND_SOC_WM1250_EV1) += snd-soc-wm1250-ev1.o diff --git a/sound/soc/codecs/ac97_standard.c b/sound/soc/codecs/ac97_standard.c new file mode 100644 index 0000000000000000000000000000000000000000..494f5c10dcf84cde6a6807bf3624a0c0313d5b48 --- /dev/null +++ b/sound/soc/codecs/ac97_standard.c @@ -0,0 +1,483 @@ +/* +* ac97std.c -- ALSA SoC ac97std AC'97 codec support +* +* Copyright 2010-2015 Seco s.r.l. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 2 as published by the Free Software Foundation. +* +* 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 St, Fifth Floor, Boston, MA +* 02110-1301 USA +* +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/ac97_codec.h> + +#include "ac97_standard.h" +#define DRV_NAME "ac97-standard-codec" + + +struct ac97std_priv { + struct snd_ac97 *ac97; + struct regmap *regmap; +}; + +static struct reg_default ac97std_reg[] = { + { 0x00 , 0x0140 }, /* Reset */ + { 0x02 , 0x8000 }, /* Stereo Output Volume */ + { 0x04 , 0x8000 }, /* HP Stereo Output Volume */ + { 0x0A , 0x0000 }, /* PC Beep Volume */ + { 0x0C , 0x8008 }, /* Phone Volume */ + { 0x0E , 0x8008 }, /* MIC Volume */ + { 0x10 , 0x8808 }, /* Line In Volume */ + { 0x12 , 0x8808 }, /* CD Volume */ + { 0x16 , 0x8808 }, /* AUX Volume */ + { 0x18 , 0x8808 }, /* PCM out Volume */ + { 0x1A , 0x0000 }, /* Record Select */ + { 0x1C , 0x8000 }, /* Record Gain */ + { 0x20 , 0x0000 }, /* General Purpose */ + { 0x24 , 0x0001 }, /* Audio Interrupt & Paging (AC'97 2.3) */ + { 0x26 , 0x0000 }, /* Powerdown control / status */ + { 0x28 , 0x097C }, /* Extended Audio ID */ + { 0x2A , 0x3830 }, /* Extended Audio Status and Control */ + { 0x2C , 0xBB80 }, /* PCM Front DAC Rate */ + { 0x32 , 0xBB80 }, /* PCM LR ADC Rate */ + { 0x3A , 0x2000 }, /* S/PDIF Control */ + { 0x5A , 0x0000 }, /* Vendor Reserved Register */ + { 0x5C , 0x00A9 }, /* Vendor Reserved Register */ + { 0x62 , 0xFFFF }, /* PCI SVID Page ID = 01h */ + { 0x64 , 0xFFFF }, /* PCI SID Page ID = 01h */ + { 0x66 , 0x0000 }, /* S/PDIF RX Status Page ID = 00h */ + { 0x68 , 0x0000 }, /* S/PDIF RX Status Page ID = 00h */ + { 0x6A , 0x0000 }, /* S/PDIF RX Control Page ID = 00h */ + { 0x6C , 0x376A }, /* DAC Slot Mapping Page ID = 01h */ + { 0x6E , 0x0000 }, /* ADC Slot Mapping Page ID = 01h */ + { 0x70 , 0x0000 }, /* ADC / SPDIF RX Left Peak */ + { 0x74 , 0x0000 }, /* PLL Setting /Debugging */ + { 0x76 , 0x1182 }, /* Miscellaneous */ + { 0x78 , 0x0070 }, /* GPIO Control */ + { 0x7A , 0x0070 }, /* GPIO Status */ + { 0x7C , 0x5649 }, /* Vendor ID1 */ + { 0x7E , 0x4120 }, /* Vendor ID2 */ +}; + + +static bool ac97std_volatile_reg( struct device *dev, unsigned int reg ) { + return true; +} + + +static bool ac97std_readable_reg ( struct device *dev, unsigned int reg ) { + switch (reg) { + case AC97_RESET ... AC97_HEADPHONE: + case AC97_PC_BEEP ... AC97_CD: + case AC97_AUX ... AC97_GENERAL_PURPOSE: + case AC97_INT_PAGING ... AC97_PCM_LR_ADC_RATE: + case AC97_SPDIF: + case AC97_AD_TEST: + case AC97_AC97STD_STEREO_MIC: + case AC97_PCI_SVID ... AC97_SENSE_INFO: + case AC97_AC97STD_DAC_SLOT_MAP: + case AC97_AC97STD_ADC_SLOT_MAP: + case AC97_AD_CODEC_CFG: + case AC97_AD_SERIAL_CFG: + case AC97_AD_MISC: + case AC97_AC97STD_GPIO_CTRL: + case AC97_AC97STD_GPIO_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return true; + default: + return false; + } +} + + +static bool ac97std_writeable_reg ( struct device *dev, unsigned int reg ) { + switch (reg) { + case AC97_RESET: + case AC97_EXTENDED_ID: + case AC97_PCM_SURR_DAC_RATE: + case AC97_PCM_LFE_DAC_RATE: + case AC97_FUNC_SELECT: + case AC97_FUNC_INFO: + case AC97_AC97STD_ADC_SLOT_MAP: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return false; + default: + return true; + } +} + + +static const struct regmap_config ac97std_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AC97_VENDOR_ID2, + .reg_defaults = ac97std_reg, + .num_reg_defaults = ARRAY_SIZE(ac97std_reg), + + .readable_reg = ac97std_readable_reg, + .writeable_reg = ac97std_writeable_reg, + .volatile_reg = ac97std_volatile_reg, + + .cache_type = REGCACHE_RBTREE, +}; + + +static const char *ac97std_record_mux[] = {"Mic", "CD", "--", "AUX", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static SOC_ENUM_DOUBLE_DECL(ac97std_record_enum, + AC97_REC_SEL, 8, 0, ac97std_record_mux); + +static const char *ac97std_mic_mux[] = {"Mic1", "Mic2"}; +static SOC_ENUM_SINGLE_DECL(ac97std_mic_enum, + AC97_GENERAL_PURPOSE, 8, ac97std_mic_mux); + +static const char *ac97std_boost[] = {"0dB", "20dB"}; +static SOC_ENUM_SINGLE_DECL(ac97std_boost_enum, + AC97_MIC, 6, ac97std_boost); + +static const char *ac97std_mic_sel[] = {"MonoMic", "StereoMic"}; +static SOC_ENUM_SINGLE_DECL(ac97std_mic_sel_enum, + AC97_AC97STD_STEREO_MIC, 2, ac97std_mic_sel); + +static const DECLARE_TLV_DB_LINEAR(master_tlv, -4650, 0); +static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250); +static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0); +static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200); + +static const struct snd_kcontrol_new ac97std_snd_ac97_controls[] = { + SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), + + SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + + SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + + SOC_DOUBLE_TLV("Record Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), + SOC_SINGLE("Record Capture Switch", AC97_REC_GAIN, 15, 1, 1), + + SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv), + SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 0, mix_tlv), + SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), + + /* Mono Mic and Stereo Mic's right channel controls */ + SOC_SINGLE_TLV("Mic/StereoMic_R Volume", AC97_MIC, 0, 31, 0, mix_tlv), + SOC_SINGLE("Mic/StereoMic_R Switch", AC97_MIC, 15, 1, 1), + + /* Stereo Mic's left channel controls */ + SOC_SINGLE("StereoMic_L Switch", AC97_MIC, 7, 1, 1), + SOC_SINGLE_TLV("StereoMic_L Volume", AC97_MIC, 8, 31, 0, mix_tlv), + + SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), + SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1), + SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), + + SOC_SINGLE("Analog Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0), + + SOC_ENUM("Mic Boost", ac97std_boost_enum), + SOC_ENUM("Mic1/2 Mux", ac97std_mic_enum), + SOC_ENUM("Mic Select", ac97std_mic_sel_enum), + SOC_ENUM("Record Mux", ac97std_record_enum), +}; + + +static const unsigned int ac97std_rates[] = { + 48000, 48000 +}; + + +static const struct snd_pcm_hw_constraint_list ac97std_rate_constraints = { + .count = ARRAY_SIZE(ac97std_rates), + .list = ac97std_rates, +}; + + +static int ac97std_analog_prepare (struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + //struct ac97std_priv *ac97std = snd_soc_codec_get_drvdata(codec); + int reg; + + /* enable variable rate audio (VRA) and disable S/PDIF output */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0001, 0x0005); + // soc_ac97_ops->write(ac97std->ac97, AC97_EXTENDED_STATUS, + // (soc_ac97_ops->read(ac97std->ac97, AC97_EXTENDED_STATUS) | 0x1) & ~0x4); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){ + reg = AC97_PCM_FRONT_DAC_RATE; + } else { + reg = AC97_PCM_LR_ADC_RATE; + } + + return snd_soc_component_write(component, reg, runtime->rate); + // soc_ac97_ops->write(ac97std->ac97, reg, runtime->rate); + // return 0; +} + + +static int ac97std_digital_prepare (struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_component_write(component, AC97_SPDIF, 0x2002); + + /* enable VRA and S/PDIF output */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0005, 0x0005); + //ac97std_write (codec, AC97_EXTENDED_STATUS, ac97std_read (codec, AC97_EXTENDED_STATUS) | 0x5); + + return snd_soc_component_write(component, AC97_PCM_FRONT_DAC_RATE, runtime->rate); +} + + +static int ac97std_set_bias_level( struct snd_soc_component *component, enum snd_soc_bias_level level ) { + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + case SND_SOC_BIAS_PREPARE: /* partial On */ + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + snd_soc_component_write(component, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* disable everything including AC link */ + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + break; + } + //codec->dapm.bias_level = level; + return 0; +} + + +static int ac97std_reset (struct snd_soc_component *component, int try_warm) { + struct ac97std_priv *ac97std = snd_soc_component_get_drvdata( component ); + + if (try_warm && soc_ac97_ops->warm_reset) { + soc_ac97_ops->warm_reset(ac97std->ac97); + if ( snd_soc_component_read( component, AC97_RESET ) == 0x140 ) + return 1; + } + soc_ac97_ops->reset(ac97std->ac97); + + if (soc_ac97_ops->warm_reset) + soc_ac97_ops->warm_reset(ac97std->ac97); + + return 0; +} + + +static struct snd_soc_dai_ops ac97std_dai_ops_analog = { +// .startup = ac97std_startup, + .prepare = ac97std_analog_prepare, +}; + + +static struct snd_soc_dai_ops ac97std_dai_ops_digital = { + .prepare = ac97std_digital_prepare, +}; + + +static struct snd_soc_dai_driver ac97std_dai[] = { + { + .name = "ac97std-hifi-analog", + + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, //SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS, + }, + + .ops = &ac97std_dai_ops_analog, + }, + { + .name = "ac97std-hifi-IEC958", + ///.ac97_control = 1, + + .playback = { + .stream_name = "ac97std IEC958", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE, + }, + + .ops = &ac97std_dai_ops_digital, + }, +}; + + +static int ac97std_soc_probe( struct snd_soc_component *component ) { + int ret = 0; + u32 reg1, reg2; + + struct ac97std_priv *ac97std = snd_soc_component_get_drvdata( component ); + + // ac97std->regmap = devm_regmap_init( &pdev->dev, NULL, ac97std, &ac97std_regmap ); + // if ( IS_ERR( ac97std->regmap ) ) { + // ret = PTR_ERR( ac97std->regmap ); + // return ret; + // } + +#ifdef CONFIG_SND_SOC_AC97_BUS + ac97std->ac97 = snd_soc_new_ac97_component( component, 0x0, + 0x0); + if ( IS_ERR( ac97std->ac97 ) ) + return PTR_ERR( ac97std->ac97 ); + ac97std->regmap = regmap_init_ac97( ac97std->ac97, &ac97std_regmap ); + if ( IS_ERR( ac97std->regmap ) ) { + snd_soc_free_ac97_component( ac97std->ac97 ); + return PTR_ERR( ac97std->regmap ); + } +#endif + + snd_soc_component_init_regmap( component, ac97std->regmap ); + // regcache_mark_dirty(component->regmap); + // snd_soc_component_cache_sync(component); + + + +// if ( !soc_ac97_ops ){ +// dev_err(codec->dev, "Failed to register AC97 codec: %d\n", ret); +// return -ENOMEM; +// } + + +// snd_soc_codec_set_drvdata (codec, ac97std->ac97); + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + ac97std_reset (component, 0); + ret = ac97std_reset (component, 1); + if ( ret < 0 ) { + printk(KERN_ERR "Failed to reset ac97std: AC97 link error\n"); + goto err_put_device; + } + + // ret = device_add(&ac97std->ac97->dev); + // if (ret) + // goto err_put_device; + + /* Read out vendor IDs */ + reg1 = snd_soc_component_read( component, AC97_VENDOR_ID1 ); + reg2 = snd_soc_component_read( component, AC97_VENDOR_ID2 ); + printk( KERN_INFO "ac97std SoC Audio Codec [ID = %04x - %04x]\n", reg1, reg2 ); + + if (reg1 == 0x414c) + ac97std_dai->playback.rates = SNDRV_PCM_RATE_48000; + else if (reg1 == 0x5649) + ac97std_dai->playback.rates = SNDRV_PCM_RATE_44100; + else + printk(KERN_ERR "ac97_standard: Unrecognized vendor ID, bitrate could be wrong"); + + /* Set initial state of the codec */ + ac97std_set_bias_level( component, SND_SOC_BIAS_STANDBY ); + + /* unmute captures and playbacks volume */ + snd_soc_component_write( component, AC97_MASTER, 0x0000 ); + snd_soc_component_write( component, AC97_PCM, 0x0000 ); + snd_soc_component_write( component, AC97_REC_GAIN, 0x0000 ); + + /* At 3.3V analog supply, for the bits 3:2 should be set 10b for the lowest power instead of default 00b */ + snd_soc_component_update_bits(component, AC97_AD_TEST, 0x0008, 0x0008); + + /* To maximize recording quality by removing white noise */ + snd_soc_component_update_bits(component, AC97_AD_TEST, 0x0400, 0x0400); + + return 0; + +err_put_device: +// put_device(&ac97std->ac97->dev); + return ret; +} + +struct snd_soc_component_driver ac97std_codec = { + .probe = ac97std_soc_probe, + .set_bias_level = ac97std_set_bias_level, + + .controls = ac97std_snd_ac97_controls, + .num_controls = ARRAY_SIZE(ac97std_snd_ac97_controls), +}; + + +static int ac97std_probe (struct platform_device *pdev) { + int ret; + struct ac97std_priv *ac97std; + + ac97std = devm_kzalloc( &pdev->dev, sizeof(*ac97std), GFP_KERNEL ); + if ( ac97std == NULL ) + return -ENOMEM; + + platform_set_drvdata( pdev, ac97std ); + + ret = devm_snd_soc_register_component( &pdev->dev, + &ac97std_codec, ac97std_dai, ARRAY_SIZE(ac97std_dai) ); + + return ret; +} + + +static int ac97std_remove (struct platform_device *pdev) { + //snd_soc_unregister_codec (&pdev->dev); + return 0; +} + + +static const struct of_device_id ac97_standard_of_match[] = { + { .compatible = "seco,ac97_standard", }, + { } +}; +MODULE_DEVICE_TABLE(of, ac97_standard_of_match); + + +static struct platform_driver ac97std_codec_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = ac97_standard_of_match, + }, + .probe = ac97std_probe, + .remove = ac97std_remove, +}; + +module_platform_driver(ac97std_codec_driver); + + +MODULE_DESCRIPTION("ASoC AC'97 standard codec driver"); +MODULE_AUTHOR("Seco s.r.l."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/codecs/ac97_standard.h b/sound/soc/codecs/ac97_standard.h new file mode 100644 index 0000000000000000000000000000000000000000..0321e16f8d4a6f92e615b5cb0750c56050a24dd4 --- /dev/null +++ b/sound/soc/codecs/ac97_standard.h @@ -0,0 +1,28 @@ +/* + * vt1613.h - AC97STD audio codec interface + * + * Copyright 2010-2015 Seco s.r.l. + * + * 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 _AC97STD_H +#define _AC97STD_H + +#define AC97_AC97STD_DAC_SLOT_MAP 0x6C +#define AC97_AC97STD_ADC_SLOT_MAP 0x6E + +#define AC97_AC97STD_GPIO_CTRL 0x78 +#define AC97_AC97STD_GPIO_STATUS 0x7A + +#define AC97_AC97STD_STEREO_MIC 0x5C + +/* AC97STD DAI ID's */ +#define AC97STD_DAI_AC97_ANALOG 0 +#define AC97STD_DAI_AC97_DIGITAL 1 + + +#endif diff --git a/sound/soc/codecs/alc655.c b/sound/soc/codecs/alc655.c new file mode 100644 index 0000000000000000000000000000000000000000..28e994082bb1f4c69f41dec5d8181407e641e537 --- /dev/null +++ b/sound/soc/codecs/alc655.c @@ -0,0 +1,469 @@ +/* +* alc655.c -- ALSA SoC alc655 AC'97 codec support +* +* Copyright 2010-2015 Seco s.r.l. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* version 2 as published by the Free Software Foundation. +* +* 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 St, Fifth Floor, Boston, MA +* 02110-1301 USA +* +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/ac97_codec.h> + +#include "alc655.h" +#define DRV_NAME "alc655-codec" + + +struct alc655_priv { + struct snd_ac97 *ac97; + struct regmap *regmap; +}; + + +static const struct reg_default alc655_reg[] = { + { 0x00 , 0x0000 }, /* Reset */ + { 0x02 , 0x8000 }, /* Master Volume */ + { 0x06 , 0x8000 }, /* Mono-Out Volume */ + { 0x0A , 0x0000 }, /* PC Beep Volume */ + { 0x0C , 0x8008 }, /* Phone Volume */ + { 0x0E , 0x8008 }, /* MIC Volume */ + { 0x10 , 0x8808 }, /* Line In Volume */ + { 0x12 , 0x8808 }, /* CD Volume */ + { 0x16 , 0x8808 }, /* AUX Volume */ + { 0x18 , 0x8808 }, /* PCM out Volume */ + { 0x1A , 0x0000 }, /* Record Select */ + { 0x1C , 0x8000 }, /* Record Gain */ + { 0x20 , 0x0000 }, /* General Purpose */ + { 0x24 , 0x0001 }, /* Audio Interrupt & Paging (AC'97 2.3) */ + { 0x26 , 0x0000 }, /* Powerdown control / status */ + { 0x28 , 0x097C }, /* Extended Audio ID */ + { 0x2A , 0x3830 }, /* Extended Audio Status and Control */ + { 0x2C , 0xBB80 }, /* PCM Front DAC Rate */ + { 0x2E , 0XBB80 }, /* PCM Surround Sample Rate */ + { 0x30 , 0XBB80 }, /* PCM LFE Sample Rate */ + { 0x32 , 0xBB80 }, /* PCM Input Sample Rate */ + { 0x36 , 0x8080 }, /* Center/LFE Volume */ + { 0x3A , 0x2000 }, /* S/PDIF Control */ + { 0x64 , 0xFFFF }, /* Sorrount DAC Volume */ + { 0x66 , 0x0000 }, /* S/PDIF RX Status */ + { 0x6A , 0x0000 }, /* Multi Channel Ctl */ + { 0x7A , 0x60A2 }, /* Extension Control */ + { 0x7C , 0x414C }, /* Vendor ID1 */ + { 0x7E , 0x4760 }, /* Vendor ID2 */ +}; + + + +static bool alc655_volatile_reg( struct device *dev, unsigned int reg ) { + return true; +} + + +static bool alc655_readable_reg( struct device *dev, unsigned int reg ) { + switch (reg) { + case AC97_RESET ... AC97_HEADPHONE: + case AC97_PC_BEEP ... AC97_CD: + case AC97_AUX ... AC97_GENERAL_PURPOSE: + case AC97_INT_PAGING ... AC97_PCM_LR_ADC_RATE: + case AC97_SPDIF: + case AC97_AD_TEST: + case AC97_ALC655_STEREO_MIC: + case AC97_PCI_SVID ... AC97_SENSE_INFO: + case AC97_ALC655_DAC_SLOT_MAP: + case AC97_ALC655_ADC_SLOT_MAP: + case AC97_AD_CODEC_CFG: + case AC97_AD_SERIAL_CFG: + case AC97_AD_MISC: + case AC97_ALC655_GPIO_CTRL: + case AC97_ALC655_GPIO_STATUS: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return true; + default: + return false; + } +} + + +static bool alc655_writeable_reg( struct device *dev, unsigned int reg ) { + switch (reg) { + case AC97_RESET: + case AC97_EXTENDED_ID: + case AC97_PCM_SURR_DAC_RATE: + case AC97_PCM_LFE_DAC_RATE: + case AC97_FUNC_SELECT: + case AC97_FUNC_INFO: + case AC97_ALC655_ADC_SLOT_MAP: + case AC97_VENDOR_ID1: + case AC97_VENDOR_ID2: + return false; + default: + return true; + } +} + + +static const struct regmap_config alc655_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = AC97_VENDOR_ID2, + .reg_defaults = alc655_reg, + .num_reg_defaults = ARRAY_SIZE(alc655_reg), + + .readable_reg = alc655_readable_reg, + .writeable_reg = alc655_writeable_reg, + .volatile_reg = alc655_volatile_reg, + + .cache_type = REGCACHE_RBTREE, +}; + + +static const char *alc655_record_mux[] = {"Mic", "CD", "--", "AUX", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static SOC_ENUM_DOUBLE_DECL(alc655_record_enum, + AC97_REC_SEL, 8, 0, alc655_record_mux); + +static const char *alc655_mic_mux[] = {"Mic1", "Mic2"}; +static SOC_ENUM_SINGLE_DECL(alc655_mic_enum, + AC97_GENERAL_PURPOSE, 8, alc655_mic_mux); + +static const char *alc655_boost[] = {"0dB", "20dB"}; +static SOC_ENUM_SINGLE_DECL(alc655_boost_enum, + AC97_MIC, 6, alc655_boost); + +static const char *alc655_mic_sel[] = {"MonoMic", "StereoMic"}; +static SOC_ENUM_SINGLE_DECL(alc655_mic_sel_enum, + AC97_ALC655_STEREO_MIC, 2, alc655_mic_sel); + +static const DECLARE_TLV_DB_LINEAR(master_tlv, -4650, 0); +static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250); +static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0); +static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200); + +static const struct snd_kcontrol_new alc655_snd_ac97_controls[] = { + SOC_DOUBLE_TLV("Speaker Playback Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Speaker Playback Switch", AC97_MASTER, 15, 1, 1), + + SOC_DOUBLE_TLV("Headphone Playback Volume", AC97_HEADPHONE, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Headphone Playback Switch", AC97_HEADPHONE, 15, 1, 1), + + SOC_DOUBLE_TLV("PCM Playback Volume", AC97_PCM, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("PCM Playback Switch", AC97_PCM, 15, 1, 1), + + SOC_DOUBLE_TLV("Record Capture Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), + SOC_SINGLE("Record Capture Switch", AC97_REC_GAIN, 15, 1, 1), + + SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv), + SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 0, mix_tlv), + SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), + + /* Mono Mic and Stereo Mic's right channel controls */ + SOC_SINGLE_TLV("Mic/StereoMic_R Volume", AC97_MIC, 0, 31, 0, mix_tlv), + SOC_SINGLE("Mic/StereoMic_R Switch", AC97_MIC, 15, 1, 1), + + /* Stereo Mic's left channel controls */ + SOC_SINGLE("StereoMic_L Switch", AC97_MIC, 7, 1, 1), + SOC_SINGLE_TLV("StereoMic_L Volume", AC97_MIC, 8, 31, 0, mix_tlv), + + SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), + SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1), + SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 0, mix_tlv), + SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), + + SOC_SINGLE("Analog Loopback", AC97_GENERAL_PURPOSE, 7, 1, 0), + + SOC_ENUM("Mic Boost", alc655_boost_enum), + SOC_ENUM("Mic1/2 Mux", alc655_mic_enum), + SOC_ENUM("Mic Select", alc655_mic_sel_enum), + SOC_ENUM("Record Mux", alc655_record_enum), +}; + + +static const unsigned int alc655_rates[] = { + 48000, 48000 +}; + + +static const struct snd_pcm_hw_constraint_list alc655_rate_constraints = { + .count = ARRAY_SIZE(alc655_rates), + .list = alc655_rates, +}; + + +static int alc655_analog_prepare (struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + int reg; + + /* enable variable rate audio (VRA) and disable S/PDIF output */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0001, 0x0005); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK){ + reg = AC97_PCM_FRONT_DAC_RATE; + } else { + reg = AC97_PCM_LR_ADC_RATE; + } + + return snd_soc_component_write(component, reg, runtime->rate); +} + + +static int alc655_digital_prepare (struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { + struct snd_soc_component *component = dai->component; + struct snd_pcm_runtime *runtime = substream->runtime; + + snd_soc_component_write(component, AC97_SPDIF, 0x2002); + + /* enable VRA and S/PDIF output */ + snd_soc_component_update_bits(component, AC97_EXTENDED_STATUS, 0x0005, 0x0005); + //alc655_write (codec, AC97_EXTENDED_STATUS, alc655_read (codec, AC97_EXTENDED_STATUS) | 0x5); + + return snd_soc_component_write(component, AC97_PCM_FRONT_DAC_RATE, runtime->rate); +} + + +static int alc655_set_bias_level( struct snd_soc_component *component, enum snd_soc_bias_level level ) { + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + case SND_SOC_BIAS_PREPARE: /* partial On */ + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + snd_soc_component_write(component, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* disable everything including AC link */ + snd_soc_component_write(component, AC97_POWERDOWN, 0xffff); + break; + } + //codec->dapm.bias_level = level; + return 0; +} + + +static int alc655_reset (struct snd_soc_component *component, int try_warm) { + struct alc655_priv *alc655 = snd_soc_component_get_drvdata( component ); + + if (try_warm && soc_ac97_ops->warm_reset) { + soc_ac97_ops->warm_reset(alc655->ac97); + if ( snd_soc_component_read( component, AC97_RESET ) == 0x0 ) + return 1; + } + soc_ac97_ops->reset(alc655->ac97); + + if (soc_ac97_ops->warm_reset) + soc_ac97_ops->warm_reset(alc655->ac97); + + if ( snd_soc_component_read( component, AC97_RESET ) == 0x0 ) + return 0; + + return -EIO; +} + + +static struct snd_soc_dai_ops alc655_dai_ops_analog = { +// .startup = alc655_startup, + .prepare = alc655_analog_prepare, +}; + + +static struct snd_soc_dai_ops alc655_dai_ops_digital = { + .prepare = alc655_digital_prepare, +}; + + +static struct snd_soc_dai_driver alc655_dai[] = { + { + .name = "alc655-hifi-analog", + + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_48000, //SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = SND_SOC_STD_AC97_FMTS, + }, + + .ops = &alc655_dai_ops_analog, + }, + { + .name = "alc655-hifi-IEC958", + ///.ac97_control = 1, + + .playback = { + .stream_name = "alc655 IEC958", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE, + }, + + .ops = &alc655_dai_ops_digital, + }, +}; + + +static int alc655_soc_probe( struct snd_soc_component *component ) { + int ret = 0; + u32 reg1, reg2; + + struct alc655_priv *alc655 = snd_soc_component_get_drvdata( component ); + + // alc655->regmap = devm_regmap_init( &pdev->dev, NULL, alc655, &alc655_regmap ); + // if ( IS_ERR( alc655->regmap ) ) { + // ret = PTR_ERR( alc655->regmap ); + // return ret; + // } + +#ifdef CONFIG_SND_SOC_AC97_BUS + alc655->ac97 = snd_soc_new_ac97_component( component, 0x0, + 0x0); + if ( IS_ERR( alc655->ac97 ) ) + return PTR_ERR( alc655->ac97 ); + alc655->regmap = regmap_init_ac97( alc655->ac97, &alc655_regmap ); + if ( IS_ERR( alc655->regmap ) ) { + snd_soc_free_ac97_component( alc655->ac97 ); + return PTR_ERR( alc655->regmap ); + } +#endif + + snd_soc_component_init_regmap( component, alc655->regmap ); + // regcache_mark_dirty(component->regmap); + // snd_soc_component_cache_sync(component); + + + +// if ( !soc_ac97_ops ){ +// dev_err(codec->dev, "Failed to register AC97 codec: %d\n", ret); +// return -ENOMEM; +// } + + +// snd_soc_codec_set_drvdata (codec, alc655->ac97); + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + alc655_reset (component, 0); + ret = alc655_reset (component, 1); + if ( ret < 0 ) { + printk(KERN_ERR "Failed to reset alc655: AC97 link error\n"); + goto err_put_device; + } + + // ret = device_add(&alc655->ac97->dev); + // if (ret) + // goto err_put_device; + + /* Read out vendor IDs */ + reg1 = snd_soc_component_read( component, AC97_VENDOR_ID1 ); + reg2 = snd_soc_component_read( component, AC97_VENDOR_ID2 ); + printk( KERN_INFO "alc655 SoC Audio Codec [ID = %04x - %04x]\n", reg1, reg2 ); + + /* Set initial state of the codec */ + alc655_set_bias_level( component, SND_SOC_BIAS_STANDBY ); + + /* unmute captures and playbacks volume */ + snd_soc_component_write( component, AC97_MASTER, 0x0000 ); + snd_soc_component_write( component, AC97_PCM, 0x0000 ); + snd_soc_component_write( component, AC97_REC_GAIN, 0x0000 ); + + /* At 3.3V analog supply, for the bits 3:2 should be set 10b for the lowest power instead of default 00b */ + snd_soc_component_update_bits(component, AC97_AD_TEST, 0x0008, 0x0008); + + /* To maximize recording quality by removing white noise */ + snd_soc_component_update_bits(component, AC97_AD_TEST, 0x0400, 0x0400); + + return 0; + +err_put_device: +// put_device(&alc655->ac97->dev); + return ret; +} + + +struct snd_soc_component_driver alc655_codec = { + .probe = alc655_soc_probe, + .set_bias_level = alc655_set_bias_level, + .controls = alc655_snd_ac97_controls, + .num_controls = ARRAY_SIZE(alc655_snd_ac97_controls), +}; + + +static int alc655_probe (struct platform_device *pdev) { + int ret; + struct alc655_priv *alc655; + + alc655 = devm_kzalloc( &pdev->dev, sizeof(*alc655), GFP_KERNEL ); + if ( alc655 == NULL ) + return -ENOMEM; + + platform_set_drvdata( pdev, alc655 ); + + ret = devm_snd_soc_register_component( &pdev->dev, + &alc655_codec, alc655_dai, ARRAY_SIZE(alc655_dai) ); + + return ret; +} + + +static int alc655_remove (struct platform_device *pdev) { + //snd_soc_unregister_codec (&pdev->dev); + return 0; +} + + +static const struct of_device_id alc655_of_match[] = { + { .compatible = "seco,alc655", }, + { } +}; +MODULE_DEVICE_TABLE(of, alc655_of_match); + + +static struct platform_driver alc655_codec_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = alc655_of_match, + }, + .probe = alc655_probe, + .remove = alc655_remove, +}; + +module_platform_driver(alc655_codec_driver); + + +MODULE_DESCRIPTION("ASoC AC'97 ALC655 codec driver"); +MODULE_AUTHOR("Seco s.r.l."); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/sound/soc/codecs/alc655.h b/sound/soc/codecs/alc655.h new file mode 100644 index 0000000000000000000000000000000000000000..c2da36c78d1c19ca0a003f5154ca296a868ddf8b --- /dev/null +++ b/sound/soc/codecs/alc655.h @@ -0,0 +1,28 @@ +/* + * vt1613.h - ALC655 audio codec interface + * + * Copyright 2010-2015 Seco s.r.l. + * + * 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 _ALC655_H +#define _ALC655_H + +#define AC97_ALC655_DAC_SLOT_MAP 0x6C +#define AC97_ALC655_ADC_SLOT_MAP 0x6E + +#define AC97_ALC655_GPIO_CTRL 0x78 +#define AC97_ALC655_GPIO_STATUS 0x7A + +#define AC97_ALC655_STEREO_MIC 0x5C + +/* ALC655 DAI ID's */ +#define ALC655_DAI_AC97_ANALOG 0 +#define ALC655_DAI_AC97_DIGITAL 1 + + +#endif diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index bb5aac95f548b041aa8f4e30c0ce5250f27af62e..4099cd8656ed85e34c286afebd7e1bbc502c4b1d 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -395,6 +395,32 @@ config SND_SOC_IMX_WM8958 Say Y if you want to add support for SoC audio on an i.MX board with a wm8958 codec. + +config SND_SOC_SECO_AC97_STANDARD + tristate "SoC Audio support for i.MX SECO boards with STANDARD AC'97" + depends on OF + select SND_SOC_AC97_BUS + select SND_SOC_AC97_STANDARD + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for SoC audio on an i.MX board with + a standard codec in AC97 mode. + +config SND_SOC_SECO_AC97_ALC655 + tristate "SoC Audio support for i.MX SECO boards with ALC655 AC'97" + depends on OF + select SND_SOC_AC97_BUS + select SND_SOC_ALC655 + select SND_SOC_IMX_PCM_DMA + select SND_SOC_IMX_AUDMUX + select SND_SOC_FSL_SSI + help + Say Y if you want to add support for SoC audio on an i.MX board with + a ALC655 codec in AC97 mode. + + config SND_SOC_IMX_MICFIL tristate "SoC Audio support for i.MX boards with micfil" depends on OF && I2C diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 98bf58311964b8ec932daa4123edd8cfc3b2b2dc..032cbe32a16aa719d02068bac6b77bcefb634288 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -34,6 +34,12 @@ snd-soc-fsl-esai-client-objs := fsl_esai_client.o snd-soc-fsl-aud2htx-objs := fsl_aud2htx.o snd-soc-fsl-rpmsg-objs := fsl_rpmsg.o +snd-soc-seco-ac97-alc655-objs := seco-ac97-alc655.o +snd-soc-seco-ac97-standard-objs := seco-ac97-standard.o + +obj-$(CONFIG_SND_SOC_SECO_AC97_ALC655) += snd-soc-seco-ac97-alc655.o +#obj-$(CONFIG_SND_SOC_SECO_AC97_STANDARD) += snd-soc-seco-ac97-standard.o + obj-$(CONFIG_SND_SOC_FSL_AUDMIX) += snd-soc-fsl-audmix.o obj-$(CONFIG_SND_SOC_FSL_ASOC_CARD) += snd-soc-fsl-asoc-card.o snd-soc-fsl-hdmi-objs := fsl_hdmi.o diff --git a/sound/soc/fsl/seco-ac97-alc655.c b/sound/soc/fsl/seco-ac97-alc655.c new file mode 100644 index 0000000000000000000000000000000000000000..250b617644a564017bc2812c21fe4ac60f1f5012 --- /dev/null +++ b/sound/soc/fsl/seco-ac97-alc655.c @@ -0,0 +1,273 @@ +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <linux/clk.h> +#include <sound/pcm_params.h> + +#include "../codecs/alc655.h" +#include "imx-audmux.h" +#include "fsl_ssi.h" + +#define DRV_NAME "imx-ac97-alc655" +#define DAI_NAME_SIZE 32 + +struct imx_alc655_data { + struct snd_soc_dai_link dai; + struct snd_soc_card card; + char codec_dai_name[DAI_NAME_SIZE]; + char platform_name[DAI_NAME_SIZE]; + struct clk *codec_clk; + unsigned int clk_frequency; +}; + + +static int imx_alc655_dai_init(struct snd_soc_pcm_runtime *rtd) { + return 0; +} + + +static int imx_alc655_audio_params (struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 1); + struct snd_soc_card *card = rtd->card; + struct device *dev = card->dev; + unsigned int fmt; + int ret = 0; + + // struct snd_soc_pcm_runtime *rtd = substream->private_data; + // struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + // int ret; + + fmt = SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFS; + ret = snd_soc_dai_set_fmt( cpu_dai, fmt ); + if ( ret ) { + dev_err( dev, "failed to set cpu dai fmt: %d\n", ret ); + return ret; + } + + ret = snd_soc_dai_set_fmt(codec_dai, fmt); + + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0, 2, + params_physical_width(params)); + if (ret) { + dev_err(dev, "failed to set cpu dai tdm slot: %d\n", ret); + return ret; + } + ret = snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_IN); + + return ret; +} + + +static int imx_audmux_ac97_config (struct platform_device *pdev, int intPort, int extPort) { + int ret; + unsigned int ptcr, pdcr; + + intPort = intPort - 1; + extPort = extPort - 1; + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | IMX_AUDMUX_V2_PTCR_TCLKDIR | IMX_AUDMUX_V2_PTCR_TCSEL(extPort); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(extPort); + + ret = imx_audmux_v2_configure_port (intPort, ptcr, pdcr); + if ( ret ) { + dev_err (&pdev->dev, "Audmux internal port setup failed\n"); + return ret; + } + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TFSEL(intPort); + + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL (intPort); + + ret = imx_audmux_v2_configure_port (extPort, ptcr, pdcr); + if ( ret ) { + dev_err (&pdev->dev, "Audmux external port setup failed\n"); + return ret; + } + + return 0; +} + + +static int imx_alc655_probe (struct platform_device *pdev) { + struct device_node *ssi_np, *codec_np, *np = pdev->dev.of_node; + + struct platform_device *codec_pdev; + struct platform_device *ssi_pdev; + struct imx_alc655_data *data = NULL; + struct snd_soc_dai_link_component *comp; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32 (np, "mux-int-port", &int_port); + if ( ret ) { + dev_err (&pdev->dev, "mux-int-port property missing or invalid\n"); + return ret; + } + + ret = of_property_read_u32 (np, "mux-ext-port", &ext_port); + if ( ret ) { + dev_err (&pdev->dev, "mux-ext-port property missing or invalid\n"); + return ret; + } + + ret = imx_audmux_ac97_config (pdev, int_port, ext_port); + if ( ret ) { + dev_err (&pdev->dev, "Audmux port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle (np, "ssi-controller", 0); + if ( !ssi_np ) { + dev_err (&pdev->dev, "ssi-controller phandle missing or invalid\n"); + return -EINVAL; + } + ssi_pdev = of_find_device_by_node (ssi_np); + if ( !ssi_pdev ) { + dev_err (&pdev->dev, "Failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle (np, "audio-codec", 0); + if ( !codec_np ) { + dev_err (&pdev->dev, "audio-codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + codec_pdev = of_find_device_by_node (codec_np); + if ( !codec_pdev ) { + dev_err (&pdev->dev, "Failed to find codec device\n"); + ret = -EINVAL; + goto fail; + } + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto fail_data; + } + + comp = devm_kzalloc(&pdev->dev, 3 * sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + goto fail_comp; + } + + data->dai.cpus = &comp[0]; + data->dai.codecs = &comp[1]; + data->dai.platforms = &comp[2]; + + data->dai.num_cpus = 1; + data->dai.num_codecs = 1; + data->dai.num_platforms = 1; + + data->dai.name = "alc655-AC97"; + data->dai.stream_name = "ALC655-analog"; + data->dai.codecs->dai_name = "alc655-hifi-analog"; + data->dai.codecs->of_node = codec_np; + data->dai.cpus->of_node = ssi_np; + data->dai.platforms->of_node = ssi_np; + data->dai.init = &imx_alc655_dai_init; + //data->dai.ops = &imx_alc655_audio_params; + + data->card.dev = &pdev->dev; + ret = snd_soc_of_parse_card_name(&data->card, "model"); + if (ret) { + dev_err(&pdev->dev, "unable to parse model\n"); + goto fail; + } + // ret = snd_soc_of_parse_audio_routing(&data->card, "audio-routing"); + // if (ret) + // goto fail; + data->card.num_links = 1; + data->card.owner = THIS_MODULE; + data->card.dai_link = &data->dai; + + + + platform_set_drvdata(pdev, &data->card); + snd_soc_card_set_drvdata(&data->card, data); + + ret = devm_snd_soc_register_card(&pdev->dev, &data->card); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto fail; + } + + of_node_put(ssi_np); + of_node_put(codec_np); + + return 0; + // imx_alc655_dai.codecs->dai_name = dev_name (&codec_pdev->dev); + // imx_alc655_dai.cpu_of_node = ssi_np; + // imx_alc655_dai.cpus->dai_name = dev_name (&ssi_pdev->dev); + // imx_alc655_dai.platforms->of_node = ssi_np; + // imx_alc655_dai.ops = &imx_alc655_audio_ops; + // imx_alc655_dai.dai_fmt = SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFS; + + // imx_alc655_card.dev = &pdev->dev; + + // platform_set_drvdata (pdev, &imx_alc655_card); + + // ret = snd_soc_register_card (&imx_alc655_card); + // if ( ret ) + // dev_err (&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); + + return 0; + +fail_comp: + kfree( data ); +fail_data: +fail: + if ( ssi_np ) + of_node_put (ssi_np); + if ( codec_np ) + of_node_put (codec_np); + + return ret; +} + + +static int imx_alc655_remove (struct platform_device *pdev) { + int ret; + struct snd_soc_card *card = platform_get_drvdata (pdev); + + ret = snd_soc_unregister_card (card); + + return ret; +} + + +static const struct of_device_id imx_alc655_audio_match[] = { + { .compatible = "seco,imx-alc655-audio", }, + {} +}; + + +MODULE_DEVICE_TABLE(of, imx_alc655_audio_match); + + +static struct platform_driver imx_alc655_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = imx_alc655_audio_match, + }, + .probe = imx_alc655_probe, + .remove = imx_alc655_remove, +}; +module_platform_driver(imx_alc655_driver); + + +MODULE_AUTHOR("Seco <info@seco.it>"); +MODULE_DESCRIPTION(DRV_NAME ": Freescale i.MX ALC655 AC97 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-alc655"); diff --git a/sound/soc/fsl/seco-ac97-standard.c b/sound/soc/fsl/seco-ac97-standard.c new file mode 100644 index 0000000000000000000000000000000000000000..7bb3fc515f6ca8478496ef42fa21cc394e6820d9 --- /dev/null +++ b/sound/soc/fsl/seco-ac97-standard.c @@ -0,0 +1,204 @@ +/* + * imx-ac97-ac97_standard.c -- SoC audio for i.MX Seco UDOO board with + * STANDARD AC'97 codec + * Copyright: Seco s.r.l. + + * 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/of_platform.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include "../codecs/ac97_standard.h" +#include "imx-audmux.h" +#include "fsl_ssi.h" + +#define DRV_NAME "imx-ac97-ac97_standard" + +static int imx_ac97_standard_audio_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; + int ret; + + ret = snd_soc_dai_set_fmt (cpu_dai, SND_SOC_DAIFMT_AC97 + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFS); + + if ( ret < 0 ) { + dev_err (cpu_dai->dev, + "Failed to set cpu dai format: %d\n", ret); + return ret; + } + + return 0; +} + + +static struct snd_soc_ops imx_ac97_standard_audio_ops = { + .hw_params = imx_ac97_standard_audio_params, +}; + + +static struct snd_soc_dai_link imx_ac97_standard_dai = { + .name = "ac97std_standard-AC97", + .stream_name = "AC97std-analog", +}; + + +static struct snd_soc_card imx_ac97_standard_card = { + .name = "imx-ac97_standard-audio", + .owner = THIS_MODULE, + .dai_link = &imx_ac97_standard_dai, + .num_links = 1, +}; + + +static int imx_audmux_ac97_config (struct platform_device *pdev, int intPort, int extPort) { + int ret; + unsigned int ptcr, pdcr; + + intPort = intPort - 1; + extPort = extPort - 1; + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | IMX_AUDMUX_V2_PTCR_TCLKDIR | IMX_AUDMUX_V2_PTCR_TCSEL(extPort); + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(extPort); + + ret = imx_audmux_v2_configure_port (intPort, ptcr, pdcr); + if ( ret ) { + dev_err (&pdev->dev, "Audmux internal port setup failed\n"); + return ret; + } + + ptcr = IMX_AUDMUX_V2_PTCR_SYN | IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TFSEL(intPort); + + pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL (intPort); + + ret = imx_audmux_v2_configure_port (extPort, ptcr, pdcr); + if ( ret ) { + dev_err (&pdev->dev, "Audmux external port setup failed\n"); + return ret; + } + + return 0; +} + + +static int imx_ac97_standard_probe (struct platform_device *pdev) { + struct device_node *ssi_np, *codec_np, *np = pdev->dev.of_node; + + struct platform_device *codec_pdev; + struct platform_device *ssi_pdev; + int int_port, ext_port; + int ret; + + ret = of_property_read_u32 (np, "mux-int-port", &int_port); + if ( ret ) { + dev_err (&pdev->dev, "mux-int-port property missing or invalid\n"); + return ret; + } + + ret = of_property_read_u32 (np, "mux-ext-port", &ext_port); + if ( ret ) { + dev_err (&pdev->dev, "mux-ext-port property missing or invalid\n"); + return ret; + } + + ret = imx_audmux_ac97_config (pdev, int_port, ext_port); + if ( ret ) { + dev_err (&pdev->dev, "Audmux port setup failed\n"); + return ret; + } + + ssi_np = of_parse_phandle (np, "ssi-controller", 0); + if ( !ssi_np ) { + dev_err (&pdev->dev, "ssi-controller phandle missing or invalid\n"); + return -EINVAL; + } + ssi_pdev = of_find_device_by_node (ssi_np); + if ( !ssi_pdev ) { + dev_err (&pdev->dev, "Failed to find SSI platform device\n"); + ret = -EINVAL; + goto fail; + } + + codec_np = of_parse_phandle (np, "audio-codec", 0); + if ( !codec_np ) { + dev_err (&pdev->dev, "audio-codec phandle missing or invalid\n"); + ret = -EINVAL; + goto fail; + } + codec_pdev = of_find_device_by_node (codec_np); + if ( !codec_pdev ) { + dev_err (&pdev->dev, "Failed to find codec device\n"); + ret = -EINVAL; + goto fail; + } + + imx_ac97_standard_dai.codec_name = dev_name (&codec_pdev->dev); + imx_ac97_standard_dai.cpu_of_node = ssi_np; + imx_ac97_standard_dai.cpu_dai_name = dev_name (&ssi_pdev->dev); + imx_ac97_standard_dai.platform_of_node = ssi_np; + imx_ac97_standard_dai.ops = &imx_ac97_standard_audio_ops; + imx_ac97_standard_dai.dai_fmt = SND_SOC_DAIFMT_AC97 | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFS; + + imx_ac97_standard_card.dev = &pdev->dev; + + platform_set_drvdata (pdev, &imx_ac97_standard_card); + + ret = snd_soc_register_card (&imx_ac97_standard_card); + if ( ret ) + dev_err (&pdev->dev, "snd_soc_register_card() failed: %d\n", ret); + +fail: + if ( ssi_np ) + of_node_put (ssi_np); + if ( codec_np ) + of_node_put (codec_np); + + return ret; +} + + +static int imx_ac97_standard_remove (struct platform_device *pdev) { + int ret; + struct snd_soc_card *card = platform_get_drvdata (pdev); + + ret = snd_soc_unregister_card (card); + + return ret; +} + + +static const struct of_device_id imx_ac97_standard_audio_match[] = { + { .compatible = "fsl,imx-ac97_standard-audio", }, + {} +}; + + +MODULE_DEVICE_TABLE(of, imx_ac97_standard_audio_match); + + +static struct platform_driver imx_ac97_standard_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = imx_ac97_standard_audio_match, + }, + .probe = imx_ac97_standard_probe, + .remove = imx_ac97_standard_remove, +}; +module_platform_driver(imx_ac97_standard_driver); + + +MODULE_AUTHOR("Seco <info@seco.it>"); +MODULE_DESCRIPTION(DRV_NAME ": Freescale i.MX standard AC97 ASoC machine driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-ac97_standard");