diff --git a/sound/soc/codecs/Android.mk b/sound/soc/codecs/Android.mk index c848fe1517ab..1d5c80de02d6 100644 --- a/sound/soc/codecs/Android.mk +++ b/sound/soc/codecs/Android.mk @@ -65,3 +65,9 @@ LOCAL_MODULE := snd_soc_tfa9874.ko LOCAL_MODULE_TAGS := optional LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) include $(DLKM_DIR)/AndroidKernelModule.mk + +include $(CLEAR_VARS) +LOCAL_MODULE := tas2560.ko +LOCAL_MODULE_TAGS := optional +LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT) +include $(DLKM_DIR)/AndroidKernelModule.mk diff --git a/sound/soc/codecs/Kbuild b/sound/soc/codecs/Kbuild index b7eeb3165778..f5729e9591b4 100644 --- a/sound/soc/codecs/Kbuild +++ b/sound/soc/codecs/Kbuild @@ -15,6 +15,7 @@ EXTRA_CFLAGS += -I$(TOP)/kernel/techpack/audio/include/uapi endif EXTRA_CFLAGS += -DCONFIG_SND_SOC_AOV_TRIGGER -DCONFIG_SND_SOC_OPALUM +EXTRA_CFLAGS += -DCONFIG_TAS2560_REGMAP -DCONFIG_TAS2560_CODEC -DCONFIG_TAS2560_MISC ifeq ($(KDIR),) PLMA_HOSTSW_DIR=$(realpath -f $(srctree)/$(src)) @@ -67,3 +68,6 @@ EXTRA_CFLAGS += -DTFA_DEBUG -DDEBUG snd_soc_tfa9874-objs += tfa_debug.o endif obj-m += snd_soc_tfa9874.o + +tas2560-objs = tas2560-core.o tas2560-misc.o tas2560-codec.o tas2560-regmap.o +obj-m += tas2560.o diff --git a/sound/soc/codecs/tas2560-codec.c b/sound/soc/codecs/tas2560-codec.c new file mode 100644 index 000000000000..bbb51825ddfe --- /dev/null +++ b/sound/soc/codecs/tas2560-codec.c @@ -0,0 +1,510 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560-codec.c +** +** Description: +** ALSA SoC driver for Texas Instruments TAS2560 High Performance 4W Smart Amplifier +** +** ============================================================================= +*/ + +#ifdef CONFIG_TAS2560_CODEC + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas2560.h" +#include "tas2560-core.h" + +#define TAS2560_MDELAY 0xFFFFFFFE +#define KCONTROL_CODEC + +static unsigned int tas2560_codec_read(struct snd_soc_codec *codec, unsigned int reg) +{ + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + unsigned int value = 0; + int ret; + + ret = pTAS2560->read(pTAS2560, reg, &value); + if (ret >= 0) + return value; + else + return ret; +} + +static int tas2560_codec_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + return pTAS2560->write(pTAS2560, reg, value); +} + +static int tas2560_codec_suspend(struct snd_soc_codec *codec) +{ + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + mutex_lock(&pTAS2560->codec_lock); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + pTAS2560->runtime_suspend(pTAS2560); + + mutex_unlock(&pTAS2560->codec_lock); + return ret; +} + +static int tas2560_codec_resume(struct snd_soc_codec *codec) +{ + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + mutex_lock(&pTAS2560->codec_lock); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + pTAS2560->runtime_resume(pTAS2560); + + mutex_unlock(&pTAS2560->codec_lock); + return ret; +} + +static int tas2560_AIF_post_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + /* struct snd_soc_codec *codec = w->codec; */ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + dev_dbg(pTAS2560->dev, "SND_SOC_DAPM_POST_PMU"); + break; + case SND_SOC_DAPM_POST_PMD: + dev_dbg(pTAS2560->dev, "SND_SOC_DAPM_POST_PMD"); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget tas2560_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0, + tas2560_AIF_post_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_OUT_DRV("ClassD", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("OUT") +}; + +static const struct snd_soc_dapm_route tas2560_audio_map[] = { + {"DAC", NULL, "ASI1"}, + {"ClassD", NULL, "DAC"}, + {"OUT", NULL, "ClassD"}, + {"DAC", NULL, "PLL"}, +}; + +static int tas2560_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + return 0; +} + +static void tas2560_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); +} + +static int tas2560_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + mutex_lock(&pTAS2560->codec_lock); + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, mute); + tas2560_enable(pTAS2560, !mute); + mutex_unlock(&pTAS2560->codec_lock); + return 0; +} + +static int tas2560_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + return ret; +} + +static int tas2560_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + return 0; +} + +static int tas2560_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + return 0; +} + +static int tas2560_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + dev_dbg(pTAS2560->dev, "%s, format=0x%x\n", __func__, fmt); + + return ret; +} + +static struct snd_soc_dai_ops tas2560_dai_ops = { + .startup = tas2560_startup, + .shutdown = tas2560_shutdown, + .digital_mute = tas2560_mute, + .hw_params = tas2560_hw_params, + .prepare = tas2560_prepare, + .set_sysclk = tas2560_set_dai_sysclk, + .set_fmt = tas2560_set_dai_fmt, +}; + +#define TAS2560_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) +static struct snd_soc_dai_driver tas2560_dai_driver[] = { + { + .name = "tas2560 ASI1", + .id = 0, + .playback = { + .stream_name = "ASI1 Playback", + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = TAS2560_FORMATS, + }, + .ops = &tas2560_dai_ops, + .symmetric_rates = 1, + }, +}; + +static int tas2560_codec_probe(struct snd_soc_codec *codec) +{ + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + return 0; +} + +static int tas2560_codec_remove(struct snd_soc_codec *codec) +{ + return 0; +} + +static int tas2560_get_load(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pUcontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *pCodec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *pCodec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(pCodec); + + pUcontrol->value.integer.value[0] = pTAS2560->mnLoad; + + return 0; +} + +static int tas2560_set_load(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pUcontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *pCodec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *pCodec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(pCodec); + + mutex_lock(&pTAS2560->codec_lock); + pTAS2560->mnLoad = pUcontrol->value.integer.value[0]; + dev_dbg(pCodec->dev, "%s:load = 0x%x\n", __func__, + pTAS2560->mnLoad); + tas2560_setLoad(pTAS2560, pTAS2560->mnLoad); + mutex_unlock(&pTAS2560->codec_lock); + return 0; +} + +static int tas2560_get_Sampling_Rate(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pUcontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *pCodec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *pCodec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(pCodec); + + pUcontrol->value.integer.value[0] = pTAS2560->mnSamplingRate; + dev_dbg(pCodec->dev, "%s: %d\n", __func__, + pTAS2560->mnSamplingRate); + return 0; +} + +static int tas2560_set_Sampling_Rate(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pUcontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *pCodec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *pCodec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(pCodec); + int sampleRate = pUcontrol->value.integer.value[0]; + + mutex_lock(&pTAS2560->codec_lock); + dev_dbg(pCodec->dev, "%s: %d\n", __func__, sampleRate); + tas2560_set_SampleRate(pTAS2560, sampleRate); + mutex_unlock(&pTAS2560->codec_lock); + + return 0; +} + +static int tas2560_power_ctrl_get(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pValue) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *codec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + pValue->value.integer.value[0] = pTAS2560->mbPowerUp; + dev_dbg(codec->dev, "tas2560_power_ctrl_get = 0x%x\n", + pTAS2560->mbPowerUp); + + return 0; +} + +static int tas2560_power_ctrl_put(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pValue) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *codec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int bPowerUp = pValue->value.integer.value[0]; + + mutex_lock(&pTAS2560->codec_lock); + tas2560_enable(pTAS2560, bPowerUp); + mutex_unlock(&pTAS2560->codec_lock); + + return 0; +} + +static int tas2560_ear_switch_get(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *ucontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *pCodec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *pCodec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(pCodec); + + ucontrol->value.integer.value[0] = pTAS2560->enablePmicEarPath; + dev_dbg(pCodec->dev, "%s: ear_switch_get = %d\n", __func__, (int)(pTAS2560->enablePmicEarPath)); + + return 0; +} + +static int tas2560_ear_switch_set(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *ucontrol) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *codec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + dev_dbg(codec->dev, "%s: ucontrol->value.integer.value[0] = %ld\n", __func__, ucontrol->value.integer.value[0]); + + pTAS2560->enablePmicEarPath = (ucontrol->value.integer.value[0] ? true : false); + + if (!gpio_is_valid(pTAS2560->mnSwitchGPIO)) { + dev_dbg(codec->dev, "%s: Invalid gpio: %d\n", __func__, pTAS2560->mnSwitchGPIO); + return false; + } + + gpio_set_value_cansleep(pTAS2560->mnSwitchGPIO, pTAS2560->enablePmicEarPath); + + return 0; +} + + +static int tas2560_ppg_ctrl_get(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pValue) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *codec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + + pValue->value.integer.value[0] = tas2560_get_PPG(pTAS2560); + dev_dbg(codec->dev, "tas2560_ppg_ctrl_get = 0x%x\n", + pTAS2560->mnPPG); + + return 0; +} + +static int tas2560_ppg_ctrl_put(struct snd_kcontrol *pKcontrol, + struct snd_ctl_elem_value *pValue) +{ +#ifdef KCONTROL_CODEC + struct snd_soc_codec *codec = snd_soc_kcontrol_codec(pKcontrol); +#else + struct snd_soc_codec *codec = snd_kcontrol_chip(pKcontrol); +#endif + struct tas2560_priv *pTAS2560 = snd_soc_codec_get_drvdata(codec); + int mnPPG = pValue->value.integer.value[0]; + + mutex_lock(&pTAS2560->codec_lock); + tas2560_set_PPG(pTAS2560, mnPPG); + mutex_unlock(&pTAS2560->codec_lock); + + return 0; +} + +static const char *load_text[] = {"8_Ohm", "6_Ohm", "4_Ohm"}; + +static const struct soc_enum load_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(load_text), load_text), +}; + +static const char *Sampling_Rate_text[] = {"48_khz", "44.1_khz", "16_khz", "8_khz"}; + +static const struct soc_enum Sampling_Rate_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(Sampling_Rate_text), Sampling_Rate_text), +}; + +static const char * const tas2560_ear_switch_ctrl_text[] = {"DISABLE", "ENABLE"}; +static const struct soc_enum tas2560_ear_switch_ctl_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(tas2560_ear_switch_ctrl_text), tas2560_ear_switch_ctrl_text), +}; + +/* + * DAC digital volumes. From 0 to 15 dB in 1 dB steps + */ +static DECLARE_TLV_DB_SCALE(dac_tlv, 0, 100, 0); + +static const struct snd_kcontrol_new tas2560_snd_controls[] = { + SOC_SINGLE_TLV("DAC Playback Volume", TAS2560_SPK_CTRL_REG, 0, 0x0f, 0, + dac_tlv), + SOC_ENUM_EXT("TAS2560 Boost load", load_enum[0], + tas2560_get_load, tas2560_set_load), + SOC_ENUM_EXT("TAS2560 Sampling Rate", Sampling_Rate_enum[0], + tas2560_get_Sampling_Rate, tas2560_set_Sampling_Rate), + SOC_SINGLE_EXT("TAS2560 PowerCtrl", SND_SOC_NOPM, 0, 0x0001, 0, + tas2560_power_ctrl_get, tas2560_power_ctrl_put), + SOC_ENUM_EXT("TAS2560 EAR Switch", tas2560_ear_switch_ctl_enum[0], tas2560_ear_switch_get, tas2560_ear_switch_set), + SOC_SINGLE_EXT("TAS2560 PPG", SND_SOC_NOPM, 0, 0x006E, 0, + tas2560_ppg_ctrl_get, tas2560_ppg_ctrl_put), +}; + +static struct snd_soc_codec_driver soc_codec_driver_tas2560 = { + .probe = tas2560_codec_probe, + .remove = tas2560_codec_remove, + .read = tas2560_codec_read, + .write = tas2560_codec_write, + .suspend = tas2560_codec_suspend, + .resume = tas2560_codec_resume, + .component_driver = { + .controls = tas2560_snd_controls, + .num_controls = ARRAY_SIZE(tas2560_snd_controls), + .dapm_widgets = tas2560_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas2560_dapm_widgets), + .dapm_routes = tas2560_audio_map, + .num_dapm_routes = ARRAY_SIZE(tas2560_audio_map), + }, +}; + +int tas2560_register_codec(struct tas2560_priv *pTAS2560) +{ + int nResult = 0; + + dev_info(pTAS2560->dev, "%s, enter\n", __func__); + nResult = snd_soc_register_codec(pTAS2560->dev, + &soc_codec_driver_tas2560, + tas2560_dai_driver, ARRAY_SIZE(tas2560_dai_driver)); + + return nResult; +} + +int tas2560_deregister_codec(struct tas2560_priv *pTAS2560) +{ + snd_soc_unregister_codec(pTAS2560->dev); + + return 0; +} + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS2560 ALSA SOC Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); +#endif /* CONFIG_TAS2560_CODEC */ diff --git a/sound/soc/codecs/tas2560-codec.h b/sound/soc/codecs/tas2560-codec.h new file mode 100644 index 000000000000..069a7927bd90 --- /dev/null +++ b/sound/soc/codecs/tas2560-codec.h @@ -0,0 +1,30 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560-codec.h +** +** Description: +** header file for tas2560-codec.c +** +** ============================================================================= +*/ + +#ifndef _TAS2560_CODEC_H +#define _TAS2560_CODEC_H + +#include "tas2560.h" + +int tas2560_register_codec(struct tas2560_priv *pTAS2560); +int tas2560_deregister_codec(struct tas2560_priv *pTAS2560); + +#endif /* _TAS2560_CODEC_H */ diff --git a/sound/soc/codecs/tas2560-core.c b/sound/soc/codecs/tas2560-core.c new file mode 100644 index 000000000000..737af6abb444 --- /dev/null +++ b/sound/soc/codecs/tas2560-core.c @@ -0,0 +1,1053 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560-core.c +** +** Description: +** TAS2560 common functions for Android Linux +** +** ============================================================================= +*/ +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas2560.h" + +#define TAS2560_MDELAY 0xFFFFFFFE +#define TAS2560_MSLEEP 0xFFFFFFFD + +#define MAX_CLIENTS 8 + +static unsigned int p_tas2560_default_data[] = { + /* reg address size values */ + TAS2560_DR_BOOST_REG_1, 0x01, 0x0c, + TAS2560_DR_BOOST_REG_2, 0x01, 0x33, + /*rampDown 15dB/us, clock1 hysteresis, 0.34ms; clock2 hysteresis, 10.6us */ + TAS2560_CLK_ERR_CTRL2, 0x01, 0x21, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_irq_config[] = { + TAS2560_IRQ_PIN_REG, 0x01, 0x41, + TAS2560_INT_MODE_REG, 0x01, 0x80,/* active high until INT_STICKY_1 and INT_STICKY_2 are read to be cleared. */ + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_48kSR_BCLK_1P536_data[] = { + TAS2560_CLK_SEL, 0x01, 0x01, /* BCLK in, P = 1 */ + TAS2560_SET_FREQ, 0x01, 0x20, /* J = 32 */ + TAS2560_PLL_D_MSB, 0x01, 0x00, /* D = 0 */ + TAS2560_PLL_D_LSB, 0x01, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_48kSR_BCLK_3P072_data[] = { + TAS2560_CLK_SEL, 0x01, 0x01, /* BCLK in, P = 1 */ + TAS2560_SET_FREQ, 0x01, 0x10, /* J = 16 */ + TAS2560_PLL_D_MSB, 0x01, 0x00, /* D = 0 */ + TAS2560_PLL_D_LSB, 0x01, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_startup_data[] = { + /* reg address size values */ + TAS2560_DEV_MODE_REG, 0x01, 0x02, + TAS2560_MUTE_REG, 0x01, 0x41, + TAS2560_MDELAY, 0x01, 0x02, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_unmute_data[] = { + /* reg address size values */ + TAS2560_CLK_ERR_CTRL, 0x01, 0x0b, + TAS2560_INT_GEN_REG, 0x01, 0xff, + TAS2560_MUTE_REG, 0x01, 0x40, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_shutdown_data[] = { + /* reg address size values */ + TAS2560_INT_GEN_REG, 0x01, 0x00, + TAS2560_CLK_ERR_CTRL, 0x01, 0x00, + TAS2560_MUTE_REG, 0x01, 0x41, + TAS2560_MSLEEP, 0x01, 10, /* delay 10ms */ + TAS2560_MUTE_REG, 0x01, 0x01, + TAS2560_MSLEEP, 0x01, 20, /* delay 20ms */ + TAS2560_DEV_MODE_REG, 0x01, 0x01, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_boost_headroom_data[] = { + TAS2560_BOOST_HEAD, 0x04, 0x06, 0x66, 0x66, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_thermal_foldback[] = { + TAS2560_THERMAL_FOLDBACK, 0x04, 0x39, 0x80, 0x00, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +/* +* PPG = dec2hex(10^(/20)*2^31)) +*/ + +/*static unsigned int p_tas2560_PPG_data[] = { + TAS2560_PPG, 0x04, 0x65, 0xac, 0x8c, 0x2e, + 0xFFFFFFFF, 0xFFFFFFFF +};*/ + +static unsigned int p_tas2560_idle_chnl_detect[] = { + TAS2560_IDLE_CHNL_DETECT, 0x04, 0, 0, 0, 0, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_HPF_data[] = { + /* reg address size values */ + /*Isense path HPF cut off -> 2Hz*/ + TAS2560_ISENSE_PATH_CTL1, 0x04, 0x7F, 0xFB, 0xB5, 0x00, + TAS2560_ISENSE_PATH_CTL2, 0x04, 0x80, 0x04, 0x4C, 0x00, + TAS2560_ISENSE_PATH_CTL3, 0x04, 0x7F, 0xF7, 0x6A, 0x00, + /*all pass*/ + TAS2560_HPF_CUTOFF_CTL1, 0x04, 0x7F, 0xFF, 0xFF, 0xFF, + TAS2560_HPF_CUTOFF_CTL2, 0x04, 0x00, 0x00, 0x00, 0x00, + TAS2560_HPF_CUTOFF_CTL3, 0x04, 0x00, 0x00, 0x00, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_Vsense_biquad_data[] = { + /* vsense delay in biquad = 3/8 sample @48KHz */ + TAS2560_VSENSE_DEL_CTL1, 0x04, 0x3a, 0x46, 0x74, 0x00, + TAS2560_VSENSE_DEL_CTL2, 0x04, 0x22, 0xf3, 0x07, 0x00, + TAS2560_VSENSE_DEL_CTL3, 0x04, 0x80, 0x77, 0x61, 0x00, + TAS2560_VSENSE_DEL_CTL4, 0x04, 0x22, 0xa7, 0xcc, 0x00, + TAS2560_VSENSE_DEL_CTL5, 0x04, 0x3a, 0x0c, 0x93, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_48khz_data[] = { + /* reg address size values */ + TAS2560_SR_CTRL1, 0x01, 0x01, + TAS2560_SR_CTRL2, 0x01, 0x08, + TAS2560_SR_CTRL3, 0x01, 0x10, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_16khz_data[] = { + /* reg address size values */ + TAS2560_SR_CTRL1, 0x01, 0x01, + TAS2560_SR_CTRL2, 0x01, 0x18, + TAS2560_SR_CTRL3, 0x01, 0x20, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_8khz_data[] = { + /* reg address size values */ + TAS2560_SR_CTRL1, 0x01, 0x01, + TAS2560_SR_CTRL2, 0x01, 0x30, + TAS2560_SR_CTRL3, 0x01, 0x20, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_4Ohm_data[] = { + /* reg address size values */ + TAS2560_BOOST_ON, 0x04, 0x6f, 0x5c, 0x28, 0xf5, + TAS2560_BOOST_OFF, 0x04, 0x67, 0xae, 0x14, 0x7a, + TAS2560_BOOST_TABLE_CTRL1, 0x04, 0x1c, 0x00, 0x00, 0x00, + TAS2560_BOOST_TABLE_CTRL2, 0x04, 0x1f, 0x0a, 0x3d, 0x70, + TAS2560_BOOST_TABLE_CTRL3, 0x04, 0x22, 0x14, 0x7a, 0xe1, + TAS2560_BOOST_TABLE_CTRL4, 0x04, 0x25, 0x1e, 0xb8, 0x51, + TAS2560_BOOST_TABLE_CTRL5, 0x04, 0x28, 0x28, 0xf5, 0xc2, + TAS2560_BOOST_TABLE_CTRL6, 0x04, 0x2b, 0x33, 0x33, 0x33, + TAS2560_BOOST_TABLE_CTRL7, 0x04, 0x2e, 0x3d, 0x70, 0xa3, + TAS2560_BOOST_TABLE_CTRL8, 0x04, 0x31, 0x47, 0xae, 0x14, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_6Ohm_data[] = { + /* reg address size values */ + TAS2560_BOOST_ON, 0x04, 0x73, 0x33, 0x33, 0x33, + TAS2560_BOOST_OFF, 0x04, 0x6b, 0x85, 0x1e, 0xb8, + TAS2560_BOOST_TABLE_CTRL1, 0x04, 0x1d, 0x99, 0x99, 0x99, + TAS2560_BOOST_TABLE_CTRL2, 0x04, 0x20, 0xcc, 0xcc, 0xcc, + TAS2560_BOOST_TABLE_CTRL3, 0x04, 0x24, 0x00, 0x00, 0x00, + TAS2560_BOOST_TABLE_CTRL4, 0x04, 0x27, 0x33, 0x33, 0x33, + TAS2560_BOOST_TABLE_CTRL5, 0x04, 0x2a, 0x66, 0x66, 0x66, + TAS2560_BOOST_TABLE_CTRL6, 0x04, 0x2d, 0x99, 0x99, 0x99, + TAS2560_BOOST_TABLE_CTRL7, 0x04, 0x30, 0xcc, 0xcc, 0xcc, + TAS2560_BOOST_TABLE_CTRL8, 0x04, 0x34, 0x00, 0x00, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned int p_tas2560_8Ohm_data[] = { + /* reg address size values */ + TAS2560_BOOST_ON, 0x04, 0x75, 0xc2, 0x8e, 0x00, + TAS2560_BOOST_OFF, 0x04, 0x6e, 0x14, 0x79, 0x00, + TAS2560_BOOST_TABLE_CTRL1, 0x04, 0x1e, 0x00, 0x00, 0x00, + TAS2560_BOOST_TABLE_CTRL2, 0x04, 0x21, 0x3d, 0x71, 0x00, + TAS2560_BOOST_TABLE_CTRL3, 0x04, 0x24, 0x7a, 0xe1, 0x00, + TAS2560_BOOST_TABLE_CTRL4, 0x04, 0x27, 0xb8, 0x52, 0x00, + TAS2560_BOOST_TABLE_CTRL5, 0x04, 0x2a, 0xf5, 0xc3, 0x00, + TAS2560_BOOST_TABLE_CTRL6, 0x04, 0x2e, 0x33, 0x33, 0x00, + TAS2560_BOOST_TABLE_CTRL7, 0x04, 0x31, 0x70, 0xa4, 0x00, + TAS2560_BOOST_TABLE_CTRL8, 0x04, 0x34, 0xae, 0x14, 0x00, + 0xFFFFFFFF, 0xFFFFFFFF +}; + +static unsigned char ppg_gain[] = { + 0x00, 0x00, 0x1A, 0x87, + 0x00, 0x00, 0x1D, 0xC4, + 0x00, 0x00, 0x21, 0x65, + 0x00, 0x00, 0x25, 0x78, + 0x00, 0x00, 0x2A, 0x0B, + 0x00, 0x00, 0x2F, 0x2C, + 0x00, 0x00, 0x34, 0xEE, + 0x00, 0x00, 0x3B, 0x63, + 0x00, 0x00, 0x42, 0xA2, + 0x00, 0x00, 0x4A, 0xC3, + 0x00, 0x00, 0x53, 0xE3, + 0x00, 0x00, 0x5E, 0x1F, + 0x00, 0x00, 0x69, 0x9B, + 0x00, 0x00, 0x76, 0x7E, + 0x00, 0x00, 0x84, 0xF3, + 0x00, 0x00, 0x95, 0x2C, + 0x00, 0x00, 0xA7, 0x60, + 0x00, 0x00, 0xBB, 0xCC, + 0x00, 0x00, 0xD2, 0xB6, + 0x00, 0x00, 0xEC, 0x6C, + 0x00, 0x01, 0x09, 0x45, + 0x00, 0x01, 0x29, 0xA4, + 0x00, 0x01, 0x4D, 0xF5, + 0x00, 0x01, 0x76, 0xB5, + 0x00, 0x01, 0xA4, 0x6D, + 0x00, 0x01, 0xD7, 0xBA, + 0x00, 0x02, 0x11, 0x49, + 0x00, 0x02, 0x51, 0xDE, + 0x00, 0x02, 0x9A, 0x55, + 0x00, 0x02, 0xEB, 0xA3, + 0x00, 0x03, 0x46, 0xDC, + 0x00, 0x03, 0xAD, 0x38, + 0x00, 0x04, 0x20, 0x10, + 0x00, 0x04, 0xA0, 0xEC, + 0x00, 0x05, 0x31, 0x81, + 0x00, 0x05, 0xD3, 0xBB, + 0x00, 0x06, 0x89, 0xBF, + 0x00, 0x07, 0x55, 0xFA, + 0x00, 0x08, 0x3B, 0x20, + 0x00, 0x09, 0x3C, 0x3B, + 0x00, 0x0A, 0x5C, 0xB6, + 0x00, 0x0B, 0xA0, 0x64, + 0x00, 0x0D, 0x0B, 0x91, + 0x00, 0x0E, 0xA3, 0x0E, + 0x00, 0x10, 0x6C, 0x43, + 0x00, 0x12, 0x6D, 0x43, + 0x00, 0x14, 0xAC, 0xDB, + 0x00, 0x17, 0x32, 0xAE, + 0x00, 0x1A, 0x07, 0x4F, + 0x00, 0x1D, 0x34, 0x5B, + 0x00, 0x20, 0xC4, 0x9C, + 0x00, 0x24, 0xC4, 0x2C, + 0x00, 0x29, 0x40, 0xA2, + 0x00, 0x2E, 0x49, 0x39, + 0x00, 0x33, 0xEF, 0x0C, + 0x00, 0x3A, 0x45, 0x4A, + 0x00, 0x41, 0x61, 0x79, + 0x00, 0x49, 0x5B, 0xC1, + 0x00, 0x52, 0x4F, 0x3B, + 0x00, 0x5C, 0x5A, 0x4F, + 0x00, 0x67, 0x9F, 0x1C, + 0x00, 0x74, 0x43, 0xE8, + 0x00, 0x82, 0x73, 0xA6, + 0x00, 0x92, 0x5E, 0x89, + 0x00, 0xA4, 0x3A, 0xA2, + 0x00, 0xB8, 0x44, 0x9C, + 0x00, 0xCE, 0xC0, 0x8A, + 0x00, 0xE7, 0xFA, 0xCC, + 0x01, 0x04, 0x49, 0x15, + 0x01, 0x24, 0x0B, 0x8C, + 0x01, 0x47, 0xAE, 0x14, + 0x01, 0x6F, 0xA9, 0xBB, + 0x01, 0x9C, 0x86, 0x51, + 0x01, 0xCE, 0xDC, 0x3D, + 0x02, 0x07, 0x56, 0x7A, + 0x02, 0x46, 0xB4, 0xE4, + 0x02, 0x8D, 0xCE, 0xBC, + 0x02, 0xDD, 0x95, 0x8A, + 0x03, 0x37, 0x18, 0x4E, + 0x03, 0x9B, 0x87, 0x19, + 0x04, 0x0C, 0x37, 0x14, + 0x04, 0x8A, 0xA7, 0x0B, + 0x05, 0x18, 0x84, 0x80, + 0x05, 0xB7, 0xB1, 0x5B, + 0x06, 0x6A, 0x4A, 0x53, + 0x07, 0x32, 0xAE, 0x18, + 0x08, 0x13, 0x85, 0x62, + 0x09, 0x0F, 0xCB, 0xF8, + 0x0A, 0x2A, 0xDA, 0xD2, + 0x0B, 0x68, 0x73, 0x7A, + 0x0C, 0xCC, 0xCC, 0xCD, + 0x0E, 0x5C, 0xA1, 0x4C, + 0x10, 0x1D, 0x3F, 0x2E, + 0x12, 0x14, 0x9A, 0x60, + 0x14, 0x49, 0x60, 0xC5, + 0x16, 0xC3, 0x10, 0xE3, + 0x19, 0x8A, 0x13, 0x57, + 0x1C, 0xA7, 0xD7, 0x68, + 0x20, 0x26, 0xF3, 0x10, + 0x24, 0x13, 0x46, 0xF6, + 0x28, 0x7A, 0x26, 0xC5, + 0x2D, 0x6A, 0x86, 0x6F, + 0x32, 0xF5, 0x2C, 0xFF, + 0x39, 0x2C, 0xED, 0x8E, + 0x40, 0x26, 0xE7, 0x3D, + 0x47, 0xFA, 0xCC, 0xF0, + 0x50, 0xC3, 0x35, 0xD4, + 0x5A, 0x9D, 0xF7, 0xAC, + 0x65, 0xAC, 0x8C, 0x2F, + 0x72, 0x14, 0x82, 0xC0, + 0x7F, 0xFF, 0xFF, 0xFF +}; +static int tas2560_i2c_load_data(struct tas2560_priv *pTAS2560, unsigned int *pData) +{ + unsigned int nRegister; + unsigned int *nData; + unsigned char Buf[128]; + unsigned int nLength = 0; + unsigned int i = 0; + unsigned int nSize = 0; + int nResult = 0; + + do { + nRegister = pData[nLength]; + nSize = pData[nLength + 1]; + nData = &pData[nLength + 2]; + if (nRegister == TAS2560_MSLEEP) { + msleep(nData[0]); + dev_dbg(pTAS2560->dev, "%s, msleep = %d\n", + __func__, nData[0]); + } else if (nRegister == TAS2560_MDELAY) { + mdelay(nData[0]); + dev_dbg(pTAS2560->dev, "%s, mdelay = %d\n", + __func__, nData[0]); + } else { + if (nRegister != 0xFFFFFFFF) { + if (nSize > 128) { + dev_err(pTAS2560->dev, + "%s, Line=%d, invalid size, maximum is 128 bytes!\n", + __func__, __LINE__); + break; + } + + if (nSize > 1) { + for (i = 0; i < nSize; i++) + Buf[i] = (unsigned char)nData[i]; + nResult = pTAS2560->bulk_write(pTAS2560, nRegister, Buf, nSize); + if (nResult < 0) + break; + } else if (nSize == 1) { + nResult = pTAS2560->write(pTAS2560, nRegister, nData[0]); + if (nResult < 0) + break; + } else { + dev_err(pTAS2560->dev, + "%s, Line=%d,invalid size, minimum is 1 bytes!\n", + __func__, __LINE__); + } + } + } + nLength = nLength + 2 + pData[nLength + 1]; + } while (nRegister != 0xFFFFFFFF); + + return nResult; +} + +void tas2560_sw_shutdown(struct tas2560_priv *pTAS2560, int sw_shutdown) +{ + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, sw_shutdown); + + if (sw_shutdown) + pTAS2560->update_bits(pTAS2560, TAS2560_PWR_REG, + TAS2560_PWR_BIT_MASK, 0); + else + pTAS2560->update_bits(pTAS2560, TAS2560_PWR_REG, + TAS2560_PWR_BIT_MASK, TAS2560_PWR_BIT_MASK); +} + +int tas2560_set_SampleRate(struct tas2560_priv *pTAS2560, unsigned int nSamplingRate) +{ + int ret = 0; + + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, nSamplingRate); + + switch (nSamplingRate) { + case 48000: + tas2560_i2c_load_data(pTAS2560, p_tas2560_48khz_data); + break; + case 44100: + pTAS2560->write(pTAS2560, TAS2560_SR_CTRL1, 0x11); + break; + case 16000: + tas2560_i2c_load_data(pTAS2560, p_tas2560_16khz_data); + break; + case 8000: + tas2560_i2c_load_data(pTAS2560, p_tas2560_8khz_data); + break; + default: + dev_err(pTAS2560->dev, "Invalid Sampling rate, %d\n", nSamplingRate); + ret = -1; + break; + } + + if (ret >= 0) + pTAS2560->mnSamplingRate = nSamplingRate; + + return ret; +} + +int tas2560_set_bit_rate(struct tas2560_priv *pTAS2560, unsigned int nBitRate) +{ + int ret = 0, n = -1; + + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, nBitRate); + switch (nBitRate) { + case 16: + n = 0; + break; + case 20: + n = 1; + break; + case 24: + n = 2; + break; + case 32: + n = 3; + break; + } + + if (n >= 0) + ret = pTAS2560->update_bits(pTAS2560, + TAS2560_DAI_FMT, 0x03, n); + + return ret; +} + +int tas2560_get_bit_rate(struct tas2560_priv *pTAS2560) +{ + int nBitRate = -1, value = -1, ret = 0; + + ret = pTAS2560->read(pTAS2560, TAS2560_DAI_FMT, &value); + value &= 0x03; + + switch (value) { + case 0: + nBitRate = 16; + break; + case 1: + nBitRate = 20; + break; + case 2: + nBitRate = 24; + break; + case 3: + nBitRate = 32; + break; + default: + break; + } + + return nBitRate; +} + +int tas2560_set_ASI_fmt(struct tas2560_priv *pTAS2560, unsigned int fmt) +{ + u8 serial_format = 0, asi_cfg_1 = 0; + int ret = 0; + + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, fmt); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + asi_cfg_1 = 0x00; + break; + case SND_SOC_DAIFMT_CBS_CFM: + asi_cfg_1 = TAS2560_WCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFS: + asi_cfg_1 = TAS2560_BCLKDIR; + break; + case SND_SOC_DAIFMT_CBM_CFM: + asi_cfg_1 = (TAS2560_BCLKDIR | TAS2560_WCLKDIR); + break; + default: + dev_err(pTAS2560->dev, "ASI format master is not found\n"); + ret = -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + asi_cfg_1 |= 0x00; + break; + case SND_SOC_DAIFMT_NB_IF: + asi_cfg_1 |= TAS2560_WCLKINV; + break; + case SND_SOC_DAIFMT_IB_NF: + asi_cfg_1 |= TAS2560_BCLKINV; + break; + case SND_SOC_DAIFMT_IB_IF: + asi_cfg_1 = (TAS2560_WCLKINV | TAS2560_BCLKINV); + break; + default: + dev_err(pTAS2560->dev, "ASI format Inverse is not found\n"); + ret = -EINVAL; + } + + pTAS2560->update_bits(pTAS2560, TAS2560_ASI_CFG_1, TAS2560_DIRINV_MASK | 0x02, + asi_cfg_1 | 0x02); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case (SND_SOC_DAIFMT_I2S): + serial_format |= (TAS2560_DATAFORMAT_I2S << TAS2560_DATAFORMAT_SHIFT); + break; + case (SND_SOC_DAIFMT_DSP_A): + case (SND_SOC_DAIFMT_DSP_B): + serial_format |= (TAS2560_DATAFORMAT_DSP << TAS2560_DATAFORMAT_SHIFT); + break; + case (SND_SOC_DAIFMT_RIGHT_J): + serial_format |= (TAS2560_DATAFORMAT_RIGHT_J << TAS2560_DATAFORMAT_SHIFT); + break; + case (SND_SOC_DAIFMT_LEFT_J): + serial_format |= (TAS2560_DATAFORMAT_LEFT_J << TAS2560_DATAFORMAT_SHIFT); + break; + default: + dev_err(pTAS2560->dev, "DAI Format is not found, fmt=0x%x\n", fmt); + ret = -EINVAL; + break; + } + + pTAS2560->update_bits(pTAS2560, TAS2560_DAI_FMT, TAS2560_DAI_FMT_MASK, + serial_format); + + return ret; +} + +int tas2560_set_pll_clkin(struct tas2560_priv *pTAS2560, int clk_id, + unsigned int freq) +{ + int ret = 0; + unsigned char pll_in = 0; + + dev_dbg(pTAS2560->dev, "%s, clkid=%d, freq=%d\n", __func__, clk_id, freq); + + switch (clk_id) { + case TAS2560_PLL_CLKIN_BCLK: + pll_in = 0; + break; + case TAS2560_PLL_CLKIN_MCLK: + pll_in = 1; + break; + case TAS2560_PLL_CLKIN_PDMCLK: + pll_in = 2; + break; + default: + dev_err(pTAS2560->dev, "Invalid clk id: %d\n", clk_id); + ret = -EINVAL; + break; + } + + if (ret >= 0) { + pTAS2560->update_bits(pTAS2560, TAS2560_CLK_SEL, + TAS2560_PLL_SRC_MASK, pll_in << 6); + pTAS2560->mnClkid = clk_id; + pTAS2560->mnClkin = freq; + } + + return ret; +} + +int tas2560_setupPLL(struct tas2560_priv *pTAS2560, int pll_clkin) +{ + unsigned int pll_clk = pTAS2560->mnSamplingRate * 1024; + unsigned int power = 0, temp; + unsigned int d, pll_clkin_divide; + u8 j, p; + int ret = 0; + + dev_dbg(pTAS2560->dev, "%s, %d\n", __func__, pll_clkin); + + if (!pll_clkin || (pll_clkin < 0)) { + if (pTAS2560->mnClkid != TAS2560_PLL_CLKIN_BCLK) { + dev_err(pTAS2560->dev, + "pll_in %d, pll_clkin frequency err:%d\n", + pTAS2560->mnClkid, pll_clkin); + return -EINVAL; + } + + pll_clkin = pTAS2560->mnSamplingRate * pTAS2560->mnFrameSize; + } + + pTAS2560->read(pTAS2560, TAS2560_PWR_REG, &power); + if (power & TAS2560_PWR_BIT_MASK) { + dev_dbg(pTAS2560->dev, "power down to update PLL\n"); + pTAS2560->write(pTAS2560, TAS2560_PWR_REG, TAS2560_PWR_BIT_MASK|TAS2560_MUTE_MASK); + msleep(1); + } + + /* Fill in the PLL control registers for J & D + * pll_clk = (pll_clkin * J.D) / P + * Need to fill in J and D here based on incoming freq + */ + if (pll_clkin <= 40000000) + p = 1; + else if (pll_clkin <= 80000000) + p = 2; + else if (pll_clkin <= 160000000) + p = 3; + else { + dev_err(pTAS2560->dev, "PLL Clk In %d not covered here\n", pll_clkin); + ret = -EINVAL; + } + + if (ret >= 0) { + j = (pll_clk * p) / pll_clkin; + d = (pll_clk * p) % pll_clkin; + d /= (pll_clkin / 10000); + + pll_clkin_divide = pll_clkin / (1 << p); + + if ((d == 0) + && ((pll_clkin_divide < 512000) || (pll_clkin_divide > 20000000))) { + dev_err(pTAS2560->dev, "PLL cal ERROR!!!, pll_in=%d\n", pll_clkin); + ret = -EINVAL; + } + + if ((d != 0) + && ((pll_clkin_divide < 10000000) || (pll_clkin_divide > 20000000))) { + dev_err(pTAS2560->dev, "PLL cal ERROR!!!, pll_in=%d\n", pll_clkin); + ret = -EINVAL; + } + + if (j == 0) { + dev_err(pTAS2560->dev, "PLL cal ERROR!!!, j ZERO\n"); + ret = -EINVAL; + } + } + + if (ret >= 0) { + dev_info(pTAS2560->dev, + "PLL clk_in = %d, P=%d, J.D=%d.%d\n", pll_clkin, p, j, d); + /* update P */ + if (p == 64) + temp = 0; + else + temp = p; + pTAS2560->update_bits(pTAS2560, TAS2560_CLK_SEL, TAS2560_PLL_P_MASK, temp); + + /* Update J */ + temp = j; + if (pll_clkin < 1000000) + temp |= 0x80; + pTAS2560->write(pTAS2560, TAS2560_SET_FREQ, temp); + + /* Update D */ + temp = (d & 0x00ff); + pTAS2560->write(pTAS2560, TAS2560_PLL_D_LSB, temp); + temp = ((d & 0x3f00) >> 8); + pTAS2560->write(pTAS2560, TAS2560_PLL_D_MSB, temp); + } + + /* Restore PLL status */ + if (power & TAS2560_PWR_BIT_MASK) { + pTAS2560->write(pTAS2560, TAS2560_PWR_REG, power); + msleep(1); + } + + return ret; +} + +/* 0, 8Ohm; 1, 6Ohm; 2, 4Ohm */ +int tas2560_setLoad(struct tas2560_priv *pTAS2560, int load) +{ + int ret = 0; + int value = -1; + + dev_dbg(pTAS2560->dev, "%s: %d\n", __func__, load); + + switch (load) { + case LOAD_8OHM: + value = 0; + tas2560_i2c_load_data(pTAS2560, p_tas2560_8Ohm_data); + break; + case LOAD_6OHM: + value = 1; + tas2560_i2c_load_data(pTAS2560, p_tas2560_6Ohm_data); + break; + case LOAD_4OHM: + value = 2; + tas2560_i2c_load_data(pTAS2560, p_tas2560_4Ohm_data); + break; + default: + break; + } + + if (value >= 0) { + pTAS2560->update_bits(pTAS2560, TAS2560_LOAD, LOAD_MASK, value << 3); + } + return ret; +} + +int tas2560_getLoad(struct tas2560_priv *pTAS2560) +{ + int ret = -1; + int value = -1; + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + pTAS2560->read(pTAS2560, TAS2560_LOAD, &value); + + value = (value&0x18) >> 3; + + switch (value) { + case 0: + ret = LOAD_8OHM; + break; + case 1: + ret = LOAD_6OHM; + break; + case 2: + ret = LOAD_4OHM; + break; + default: + break; + } + + return ret; +} + +int tas2560_get_volume(struct tas2560_priv *pTAS2560) +{ + int ret = -1; + int value = -1; + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + ret = pTAS2560->read(pTAS2560, TAS2560_SPK_CTRL_REG, &value); + if (ret >= 0) + return (value & 0x0f); + + return ret; +} + +int tas2560_set_volume(struct tas2560_priv *pTAS2560, int volume) +{ + int ret = -1; + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + ret = pTAS2560->update_bits(pTAS2560, TAS2560_SPK_CTRL_REG, 0x0f, volume&0x0f); + + return ret; +} + +int tas2560_get_PPG(struct tas2560_priv *pTAS2560) +{ + + dev_dbg(pTAS2560->dev, "%s\n", __func__); + return pTAS2560->mnPPG; +} + +int tas2560_set_PPG(struct tas2560_priv *pTAS2560, int ppgvolume) +{ + int ret = -1; + unsigned char *ppg = ppg_gain; + + dev_dbg(pTAS2560->dev, "%s", __func__); + if ((ppgvolume > 110) || (ppgvolume < 0)) { + dev_err(pTAS2560->dev, "invalid ppg volume, %d", ppgvolume); + return ret; + } + pTAS2560->mnPPG = ppgvolume; + ppg += ppgvolume * 4; + dev_dbg(pTAS2560->dev, "PPG reg: 0x%x%x%x%x", + ppg[0], ppg[1], ppg[2], ppg[3]); + ret = pTAS2560->bulk_write(pTAS2560, TAS2560_PPG, ppg, 4); + return ret; +} +int tas2560_parse_dt(struct device *dev, struct tas2560_priv *pTAS2560) +{ + struct device_node *np = dev->of_node; + int rc = 0, ret = 0; + + rc = of_property_read_u32(np, "ti,load", &pTAS2560->mnLoad); + if (rc) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,load", np->full_name, rc); + } else { + dev_dbg(pTAS2560->dev, "ti,load=%d", pTAS2560->mnLoad); + } + + rc = of_property_read_u32(np, "ti,asi-format", &pTAS2560->mnASIFormat); + if (rc) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,asi-format", np->full_name, rc); + } else { + dev_dbg(pTAS2560->dev, "ti,asi-format=%d", pTAS2560->mnASIFormat); + } + + pTAS2560->mnResetGPIO = of_get_named_gpio(np, "ti,reset-gpio", 0); + if (!gpio_is_valid(pTAS2560->mnResetGPIO)) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,reset-gpio", np->full_name, pTAS2560->mnResetGPIO); + } else { + dev_dbg(pTAS2560->dev, "ti,reset-gpio=%d", pTAS2560->mnResetGPIO); + } + + pTAS2560->mnIRQGPIO = of_get_named_gpio(np, "ti,irq-gpio", 0); + if (!gpio_is_valid(pTAS2560->mnIRQGPIO)) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,irq-gpio", np->full_name, pTAS2560->mnIRQGPIO); + } else { + dev_dbg(pTAS2560->dev, "ti,irq-gpio=%d", pTAS2560->mnIRQGPIO); + } + + pTAS2560->mnSwitchGPIO = of_get_named_gpio(np, "ti,ear-switch-gpio", 0); + if (!gpio_is_valid(pTAS2560->mnSwitchGPIO)) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,ear-switch-gpio", np->full_name, pTAS2560->mnSwitchGPIO); + } else { + dev_err(pTAS2560->dev, "ti,ear-switch-gpio=%d", pTAS2560->mnSwitchGPIO); + } + + rc = of_property_read_u32(np, "ti,ppg", &pTAS2560->mnPPG); + if (rc) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,ppg", np->full_name, rc); + } else { + dev_dbg(pTAS2560->dev, "ti,ppg=%d", pTAS2560->mnPPG); + } + + rc = of_property_read_u32(np, "ti,pll", &pTAS2560->mnPLL); + if (rc) { + dev_err(pTAS2560->dev, "Looking up %s property in node %s failed %d\n", + "ti,pll", np->full_name, rc); + } else { + dev_dbg(pTAS2560->dev, "ti,pll=%d", pTAS2560->mnPLL); + } + + return ret; +} + +int tas2560_load_default(struct tas2560_priv *pTAS2560) +{ + return tas2560_i2c_load_data(pTAS2560, p_tas2560_default_data); +} + +int tas2560_load_platdata(struct tas2560_priv *pTAS2560) +{ + int nResult = 0; + + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) { + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_irq_config); + if (nResult < 0) + goto end; + } + + if (pTAS2560->mnPLL == PLL_BCLK_1P536_48KSR) + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_48kSR_BCLK_1P536_data); + else if (pTAS2560->mnPLL == PLL_BCLK_3P072_48KSR) + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_48kSR_BCLK_3P072_data); + if (nResult < 0) + goto end; + + if ((pTAS2560->mnPLL == PLL_BCLK_1P536_48KSR) + || (pTAS2560->mnPLL == PLL_BCLK_3P072_48KSR)) + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_48khz_data); + if (nResult < 0) + goto end; + + if (pTAS2560->mnLoad == LOAD_8OHM) { + nResult = pTAS2560->write(pTAS2560, TAS2560_LOAD, 0x83); + } else if (pTAS2560->mnLoad == LOAD_6OHM) { + nResult = pTAS2560->write(pTAS2560, TAS2560_LOAD, 0x8b); + } else { + /* 4 Ohm speaker */ + nResult = pTAS2560->write(pTAS2560, TAS2560_LOAD, 0x9b); + } + if (nResult < 0) + goto end; + + if (pTAS2560->mnASIFormat == TAS2560_DATAFORMAT_I2S) { + nResult = tas2560_set_ASI_fmt(pTAS2560, + SND_SOC_DAIFMT_CBS_CFS|SND_SOC_DAIFMT_NB_NF|SND_SOC_DAIFMT_I2S); + if (nResult < 0) + goto end; + nResult = pTAS2560->write(pTAS2560, TAS2560_ASI_OFFSET_1, 0x00); + } else if (pTAS2560->mnASIFormat == TAS2560_DATAFORMAT_DSP) { + nResult = tas2560_set_ASI_fmt(pTAS2560, + SND_SOC_DAIFMT_CBS_CFS|SND_SOC_DAIFMT_IB_IF|SND_SOC_DAIFMT_DSP_A); + if (nResult < 0) + goto end; + nResult = pTAS2560->write(pTAS2560, TAS2560_ASI_OFFSET_1, 0x01); + } else { + dev_err(pTAS2560->dev, "need to implement!!!\n"); + } + + if (nResult < 0) + goto end; + + nResult = tas2560_set_bit_rate(pTAS2560, 16); + +end: + + return nResult; +} + +static int tas2560_load_postpwrup(struct tas2560_priv *pTAS2560) +{ + int nResult = 0; + unsigned char *ppg = ppg_gain; + + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_idle_chnl_detect); + if (nResult < 0) + goto end; + + if ((pTAS2560->mnPPG >= 0) && (pTAS2560->mnPPG <= 110)) { + ppg += pTAS2560->mnPPG * 4; + pTAS2560->bulk_write(pTAS2560, TAS2560_PPG, ppg, 4); + } + + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_HPF_data); + if (nResult < 0) + goto end; + + if (pTAS2560->mnLoad == LOAD_8OHM) { + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_8Ohm_data); + } else if (pTAS2560->mnLoad == LOAD_6OHM) { + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_6Ohm_data); + } + if (nResult < 0) + goto end; + + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_boost_headroom_data); + if (nResult < 0) + goto end; + + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_thermal_foldback); + if (nResult < 0) + goto end; + + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_Vsense_biquad_data); + +end: + return nResult; +} + +int tas2560_LoadConfig(struct tas2560_priv *pTAS2560, bool bPowerOn) +{ + int nResult = 0; + + if (bPowerOn) { + dev_dbg(pTAS2560->dev, "%s power down to load config\n", __func__); + if (hrtimer_active(&pTAS2560->mtimer)) + hrtimer_cancel(&pTAS2560->mtimer); + pTAS2560->enableIRQ(pTAS2560, false); + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_shutdown_data); + if (nResult < 0) + goto end; + } + + pTAS2560->hw_reset(pTAS2560); + + nResult = pTAS2560->write(pTAS2560, TAS2560_SW_RESET_REG, 0x01); + if (nResult < 0) + goto end; + msleep(1); + + nResult = tas2560_load_default(pTAS2560); + if (nResult < 0) { + goto end; + } + + nResult = tas2560_load_platdata(pTAS2560); + if (nResult < 0) { + goto end; + } + + if (bPowerOn) { + dev_dbg(pTAS2560->dev, "%s power up\n", __func__); + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_startup_data); + if (nResult < 0) + goto end; + nResult = tas2560_load_postpwrup(pTAS2560); + if (nResult < 0) + goto end; + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_unmute_data); + if (nResult < 0) + goto end; + pTAS2560->enableIRQ(pTAS2560, true); + } +end: + return nResult; +} + +int tas2560_enable(struct tas2560_priv *pTAS2560, bool bEnable) +{ + int nResult = 0; + + if (bEnable) { + if (!pTAS2560->mbPowerUp) { + dev_dbg(pTAS2560->dev, "%s power up\n", __func__); + pTAS2560->clearIRQ(pTAS2560); + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_startup_data); + if (nResult < 0) + goto end; + nResult = tas2560_load_postpwrup(pTAS2560); + if (nResult < 0) + goto end; + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_unmute_data); + pTAS2560->mbPowerUp = true; + if (nResult < 0) + goto end; + pTAS2560->enableIRQ(pTAS2560, true); + } + } else { + if (pTAS2560->mbPowerUp) { + dev_dbg(pTAS2560->dev, "%s power down\n", __func__); + if (hrtimer_active(&pTAS2560->mtimer)) + hrtimer_cancel(&pTAS2560->mtimer); + pTAS2560->enableIRQ(pTAS2560, false); + nResult = tas2560_i2c_load_data(pTAS2560, p_tas2560_shutdown_data); + pTAS2560->mbPowerUp = false; + } + } + +end: + return nResult; +} + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS2560 common functions for Android Linux"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/tas2560-core.h b/sound/soc/codecs/tas2560-core.h new file mode 100644 index 000000000000..c97d6645274a --- /dev/null +++ b/sound/soc/codecs/tas2560-core.h @@ -0,0 +1,45 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560-core.h +** +** Description: +** header file for tas2560-core.c +** +** ============================================================================= +*/ + +#ifndef _TAS2560_CORE_H +#define _TAS2560_CORE_H + +#include "tas2560.h" + +int tas2560_set_bit_rate(struct tas2560_priv *pTAS2560, unsigned int nBitRate); +int tas2560_get_bit_rate(struct tas2560_priv *pTAS2560); +int tas2560_set_ASI_fmt(struct tas2560_priv *pTAS2560, unsigned int fmt); +int tas2560_setLoad(struct tas2560_priv *pTAS2560, int load); +int tas2560_getLoad(struct tas2560_priv *pTAS2560); +int tas2560_get_volume(struct tas2560_priv *pTAS2560); +int tas2560_set_volume(struct tas2560_priv *pTAS2560, int volume); +int tas2560_set_SampleRate(struct tas2560_priv *pTAS2560, unsigned int nSamplingRate); +int tas2560_setupPLL(struct tas2560_priv *pTAS2560, int pll_clkin); +int tas2560_set_pll_clkin(struct tas2560_priv *pTAS2560, int clk_id, unsigned int freq); +int tas2560_parse_dt(struct device *dev, struct tas2560_priv *pTAS2560); +void tas2560_sw_shutdown(struct tas2560_priv *pTAS2560, int sw_shutdown); +void tas2560_enable(struct tas2560_priv *pTAS2560, bool bEnable); +int tas2560_load_default(struct tas2560_priv *pTAS2560); +int tas2560_load_platdata(struct tas2560_priv *pTAS2560); +int tas2560_LoadConfig(struct tas2560_priv *pTAS2560, bool bPowerOn); +int tas2560_get_PPG(struct tas2560_priv *pTAS2560); +int tas2560_set_PPG(struct tas2560_priv *pTAS2560, int ppgvolume); +#endif /* _TAS2560_CORE_H */ diff --git a/sound/soc/codecs/tas2560-misc.c b/sound/soc/codecs/tas2560-misc.c new file mode 100644 index 000000000000..d5d3ba7a4ead --- /dev/null +++ b/sound/soc/codecs/tas2560-misc.c @@ -0,0 +1,438 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** File: +** tas2560-misc.c +** +** Description: +** misc driver for Texas Instruments TAS2560 High Performance 4W Smart Amplifier +** +** ============================================================================= +*/ + +#ifdef CONFIG_TAS2560_MISC + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas2560.h" +#include "tas2560-core.h" +#include "tas2560-misc.h" +#include + + +static int g_logEnable = 1; +static struct tas2560_priv *g_tas2560; + +static int tas2560_file_open(struct inode *inode, struct file *file) +{ + struct tas2560_priv *pTAS2560 = g_tas2560; + + if (!try_module_get(THIS_MODULE)) + return -ENODEV; + + file->private_data = (void *)pTAS2560; + + if (g_logEnable) + dev_info(pTAS2560->dev, "%s\n", __func__); + return 0; +} + +static int tas2560_file_release(struct inode *inode, struct file *file) +{ + struct tas2560_priv *pTAS2560 = (struct tas2560_priv *)file->private_data; + + if (g_logEnable) + dev_info(pTAS2560->dev, "%s\n", __func__); + + file->private_data = (void *)NULL; + module_put(THIS_MODULE); + + return 0; +} + +static ssize_t tas2560_file_read(struct file *file, char *buf, size_t count, loff_t *ppos) +{ + struct tas2560_priv *pTAS2560 = (struct tas2560_priv *)file->private_data; + int ret = 0; + unsigned int nValue = 0; + unsigned char value = 0; + unsigned char *p_kBuf = NULL; + + mutex_lock(&pTAS2560->file_lock); + + switch (pTAS2560->mnDBGCmd) { + case TIAUDIO_CMD_REG_READ: + { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_REG_READ: current_reg = 0x%x, count=%d\n", + pTAS2560->mnCurrentReg, (int)count); + if (count == 1) { + ret = pTAS2560->read(pTAS2560, pTAS2560->mnCurrentReg, &nValue); + if (ret < 0) { + dev_err(pTAS2560->dev, "dev read fail %d\n", ret); + break; + } + + value = (u8)nValue; + if (g_logEnable) + dev_info(pTAS2560->dev, "TIAUDIO_CMD_REG_READ: nValue=0x%x, value=0x%x\n", + nValue, value); + ret = copy_to_user(buf, &value, 1); + if (ret != 0) { + /* Failed to copy all the data, exit */ + dev_err(pTAS2560->dev, "copy to user fail %d\n", ret); + } + } else if (count > 1) { + p_kBuf = kzalloc(count, GFP_KERNEL); + if (p_kBuf != NULL) { + ret = pTAS2560->bulk_read(pTAS2560, pTAS2560->mnCurrentReg, p_kBuf, count); + if (ret < 0) { + dev_err(pTAS2560->dev, "dev bulk read fail %d\n", ret); + } else { + ret = copy_to_user(buf, p_kBuf, count); + if (ret != 0) { + /* Failed to copy all the data, exit */ + dev_err(pTAS2560->dev, "copy to user fail %d\n", ret); + } + } + + kfree(p_kBuf); + } else { + dev_err(pTAS2560->dev, "read no mem\n"); + } + } + } + break; + + case TIAUDIO_CMD_SAMPLERATE: + { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_SAMPLERATE: count = %d\n", + (int)count); + if (count == 4) { + p_kBuf = kzalloc(count, GFP_KERNEL); + if (p_kBuf != NULL) { + p_kBuf[0] = (pTAS2560->mnSamplingRate&0x000000ff); + p_kBuf[1] = ((pTAS2560->mnSamplingRate&0x0000ff00)>>8); + p_kBuf[2] = ((pTAS2560->mnSamplingRate&0x00ff0000)>>16); + p_kBuf[3] = ((pTAS2560->mnSamplingRate&0xff000000)>>24); + + ret = copy_to_user(buf, p_kBuf, count); + if (ret != 0) { + /* Failed to copy all the data, exit */ + dev_err(pTAS2560->dev, "copy to user fail %d\n", ret); + } + + kfree(p_kBuf); + } else { + dev_err(pTAS2560->dev, "read no mem\n"); + } + } + } + break; + + case TIAUDIO_CMD_BITRATE: + { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_BITRATE: count = %d\n", + (int)count); + + if (count == 1) { + int bitRate = tas2560_get_bit_rate(pTAS2560); + + if (bitRate >= 0) { + ret = copy_to_user(buf, &bitRate, 1); + if (ret != 0) { + /* Failed to copy all the data, exit */ + dev_err(pTAS2560->dev, "copy to user fail %d\n", ret); + } + } + } + } + break; + + case TIAUDIO_CMD_DACVOLUME: + { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_DACVOLUME: count = %d\n", + (int)count); + + if (count == 1) { + int volume = tas2560_get_volume(pTAS2560); + + if (volume >= 0) { + ret = copy_to_user(buf, &volume, 1); + if (ret != 0) { + /* Failed to copy all the data, exit */ + dev_err(pTAS2560->dev, "copy to user fail %d\n", ret); + } + } + } + } + break; + } + pTAS2560->mnDBGCmd = 0; + + mutex_unlock(&pTAS2560->file_lock); + return count; +} + +static ssize_t tas2560_file_write(struct file *file, const char *buf, size_t count, loff_t *ppos) +{ + struct tas2560_priv *pTAS2560 = (struct tas2560_priv *)file->private_data; + int ret = 0; + unsigned char *p_kBuf = NULL; + unsigned int reg = 0; + unsigned int len = 0; + + mutex_lock(&pTAS2560->file_lock); + + p_kBuf = kzalloc(count, GFP_KERNEL); + if (p_kBuf == NULL) { + dev_err(pTAS2560->dev, "write no mem\n"); + goto err; + } + + ret = copy_from_user(p_kBuf, buf, count); + if (ret != 0) { + dev_err(pTAS2560->dev, "copy_from_user failed.\n"); + goto err; + } + + pTAS2560->mnDBGCmd = p_kBuf[0]; + switch (pTAS2560->mnDBGCmd) { + case TIAUDIO_CMD_REG_WITE: + if (count > 5) { + reg = ((unsigned int)p_kBuf[1] << 24) + + ((unsigned int)p_kBuf[2] << 16) + + ((unsigned int)p_kBuf[3] << 8) + + (unsigned int)p_kBuf[4]; + len = count - 5; + if (len == 1) { + ret = pTAS2560->write(pTAS2560, reg, p_kBuf[5]); + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_REG_WITE, Reg=0x%x, Val=0x%x\n", + reg, p_kBuf[5]); + } else { + ret = pTAS2560->bulk_write(pTAS2560, reg, &p_kBuf[5], len); + } + } else { + dev_err(pTAS2560->dev, "%s, write len fail, count=%d.\n", + __func__, (int)count); + } + pTAS2560->mnDBGCmd = 0; + break; + + case TIAUDIO_CMD_REG_READ: + if (count == 5) { + pTAS2560->mnCurrentReg = ((unsigned int)p_kBuf[1] << 24) + + ((unsigned int)p_kBuf[2] << 16) + + ((unsigned int)p_kBuf[3] << 8) + + (unsigned int)p_kBuf[4]; + if (g_logEnable) { + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_REG_READ, whole=0x%x\n", + pTAS2560->mnCurrentReg); + } + } else { + dev_err(pTAS2560->dev, "read len fail.\n"); + } + break; + + case TIAUDIO_CMD_DEBUG_ON: + { + if (count == 2) { + g_logEnable = p_kBuf[1]; + } + pTAS2560->mnDBGCmd = 0; + } + break; + + case TIAUDIO_CMD_SAMPLERATE: + { + if (count == 5) { + unsigned int nSampleRate = ((unsigned int)p_kBuf[1] << 24) + + ((unsigned int)p_kBuf[2] << 16) + + ((unsigned int)p_kBuf[3] << 8) + + (unsigned int)p_kBuf[4]; + + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_SAMPLERATE, set to %d\n", + nSampleRate); + + tas2560_set_SampleRate(pTAS2560, nSampleRate); + } + } + break; + + case TIAUDIO_CMD_BITRATE: + { + if (count == 2) { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_BITRATE, set to %d\n", + p_kBuf[1]); + + tas2560_set_bit_rate(pTAS2560, p_kBuf[1]); + } + } + break; + + case TIAUDIO_CMD_DACVOLUME: + { + if (count == 2) { + unsigned char volume = (p_kBuf[1] & 0x0f); + + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_DACVOLUME, set to %d\n", + volume); + + tas2560_set_volume(pTAS2560, volume); + } + } + break; + + case TIAUDIO_CMD_SPEAKER: + { + if (count == 2) { + if (g_logEnable) + dev_info(pTAS2560->dev, + "TIAUDIO_CMD_SPEAKER, set to %d\n", + p_kBuf[1]); + tas2560_enable(pTAS2560, (p_kBuf[1] > 0)); + } + } + break; + + default: + pTAS2560->mnDBGCmd = 0; + break; + } + +err: + if (p_kBuf != NULL) + kfree(p_kBuf); + + mutex_unlock(&pTAS2560->file_lock); + + return count; +} + +static long tas2560_file_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct tas2560_priv *pTAS2560 = file->private_data; + int ret = 0; + + mutex_lock(&pTAS2560->file_lock); + + switch (cmd) { + case SMARTPA_SPK_DAC_VOLUME: + { + u8 volume = (arg & 0x0f); + + tas2560_set_volume(pTAS2560, volume); + } + break; + + case SMARTPA_SPK_POWER_ON: + { + tas2560_enable(pTAS2560, true); + } + break; + + case SMARTPA_SPK_POWER_OFF: + { + tas2560_enable(pTAS2560, false); + } + break; + + case SMARTPA_SPK_SET_SAMPLERATE: + { + tas2560_set_SampleRate(pTAS2560, arg); + } + break; + + case SMARTPA_SPK_SET_BITRATE: + { + tas2560_set_bit_rate(pTAS2560, arg); + } + break; + } + + mutex_unlock(&pTAS2560->file_lock); + return ret; +} + +static const struct file_operations fops = { + .owner = THIS_MODULE, + .read = tas2560_file_read, + .write = tas2560_file_write, + .unlocked_ioctl = tas2560_file_unlocked_ioctl, + .open = tas2560_file_open, + .release = tas2560_file_release, +}; + +#define MODULE_NAME "tas2560" +static struct miscdevice tas2560_misc = { + .minor = MISC_DYNAMIC_MINOR, + .name = MODULE_NAME, + .fops = &fops, +}; + +int tas2560_register_misc(struct tas2560_priv *pTAS2560) +{ + int ret = 0; + + g_tas2560 = pTAS2560; + ret = misc_register(&tas2560_misc); + if (ret) { + dev_err(pTAS2560->dev, "TAS2560 misc fail: %d\n", ret); + } + + dev_info(pTAS2560->dev, "%s, leave\n", __func__); + + return ret; +} + +int tas2560_deregister_misc(struct tas2560_priv *pTAS2560) +{ + misc_deregister(&tas2560_misc); + return 0; +} + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS2560 Misc Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); +#endif /* CONFIG_TAS2560_MISC */ diff --git a/sound/soc/codecs/tas2560-misc.h b/sound/soc/codecs/tas2560-misc.h new file mode 100644 index 000000000000..b40f11297ea4 --- /dev/null +++ b/sound/soc/codecs/tas2560-misc.h @@ -0,0 +1,45 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560-misc.h +** +** Description: +** header file for tas2560-misc.c +** +** ============================================================================= +*/ + +#ifndef _TAS2560_MISC_H +#define _TAS2560_MISC_H + +#define TIAUDIO_CMD_REG_WITE 1 +#define TIAUDIO_CMD_REG_READ 2 +#define TIAUDIO_CMD_DEBUG_ON 3 +#define TIAUDIO_CMD_CALIBRATION 7 +#define TIAUDIO_CMD_SAMPLERATE 8 +#define TIAUDIO_CMD_BITRATE 9 +#define TIAUDIO_CMD_DACVOLUME 10 +#define TIAUDIO_CMD_SPEAKER 11 + +#define TAS2560_MAGIC_NUMBER 'M' /* '2560' */ + +#define SMARTPA_SPK_DAC_VOLUME _IOWR(TAS2560_MAGIC_NUMBER, 1, unsigned long) +#define SMARTPA_SPK_POWER_ON _IOWR(TAS2560_MAGIC_NUMBER, 2, unsigned long) +#define SMARTPA_SPK_POWER_OFF _IOWR(TAS2560_MAGIC_NUMBER, 3, unsigned long) +#define SMARTPA_SPK_SET_SAMPLERATE _IOWR(TAS2560_MAGIC_NUMBER, 7, unsigned long) +#define SMARTPA_SPK_SET_BITRATE _IOWR(TAS2560_MAGIC_NUMBER, 8, unsigned long) + +int tas2560_register_misc(struct tas2560_priv *pTAS2560); +int tas2560_deregister_misc(struct tas2560_priv *pTAS2560); + +#endif /* _TAS2560_MISC_H */ diff --git a/sound/soc/codecs/tas2560-regmap.c b/sound/soc/codecs/tas2560-regmap.c new file mode 100644 index 000000000000..f4ae4be3aefb --- /dev/null +++ b/sound/soc/codecs/tas2560-regmap.c @@ -0,0 +1,662 @@ +/* + * ALSA SoC Texas Instruments TAS2560 High Performance 4W Smart Amplifier + * + * Copyright (C) 2016 Texas Instruments, Inc. + * + * Author: saiprasad + * + * This package 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 PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + */ +#ifdef CONFIG_TAS2560_REGMAP + +#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tas2560.h" +#include "tas2560-core.h" +#include "tas2560-misc.h" +#include "tas2560-codec.h" + +static int tas2560_change_book_page(struct tas2560_priv *pTAS2560, + int book, int page) +{ + int nResult = 0; + + if ((pTAS2560->mnCurrentBook == book) + && (pTAS2560->mnCurrentPage == page)) + goto end; + + if (pTAS2560->mnCurrentBook != book) { + nResult = regmap_write(pTAS2560->regmap, TAS2560_BOOKCTL_PAGE, 0); + if (nResult < 0) { + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + goto end; + } + pTAS2560->mnCurrentPage = 0; + nResult = regmap_write(pTAS2560->regmap, TAS2560_BOOKCTL_REG, book); + if (nResult < 0) { + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + goto end; + } + pTAS2560->mnCurrentBook = book; + } + + if (pTAS2560->mnCurrentPage != page) { + nResult = regmap_write(pTAS2560->regmap, TAS2560_BOOKCTL_PAGE, page); + if (nResult < 0) { + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + goto end; + } + pTAS2560->mnCurrentPage = page; + } + +end: + return nResult; +} + +static int tas2560_dev_read(struct tas2560_priv *pTAS2560, + unsigned int reg, unsigned int *pValue) +{ + int nResult = 0; + + mutex_lock(&pTAS2560->dev_lock); + + nResult = tas2560_change_book_page(pTAS2560, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg)); + if (nResult < 0) + goto end; + + nResult = regmap_read(pTAS2560->regmap, TAS2560_PAGE_REG(reg), pValue); + if (nResult < 0) + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + else + dev_dbg(pTAS2560->dev, "%s: BOOK:PAGE:REG %u:%u:%u\n", __func__, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg), + TAS2560_PAGE_REG(reg)); + +end: + mutex_unlock(&pTAS2560->dev_lock); + return nResult; +} + +static int tas2560_dev_write(struct tas2560_priv *pTAS2560, + unsigned int reg, unsigned int value) +{ + int nResult = 0; + + mutex_lock(&pTAS2560->dev_lock); + + nResult = tas2560_change_book_page(pTAS2560, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg)); + if (nResult < 0) + goto end; + + nResult = regmap_write(pTAS2560->regmap, TAS2560_PAGE_REG(reg), + value); + if (nResult < 0) + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + else + dev_dbg(pTAS2560->dev, "%s: BOOK:PAGE:REG %u:%u:%u, VAL: 0x%02x\n", + __func__, TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg), + TAS2560_PAGE_REG(reg), value); + +end: + mutex_unlock(&pTAS2560->dev_lock); + return nResult; +} + +static int tas2560_dev_bulk_write(struct tas2560_priv *pTAS2560, + unsigned int reg, unsigned char *pData, unsigned int nLength) +{ + int nResult = 0; + + mutex_lock(&pTAS2560->dev_lock); + + nResult = tas2560_change_book_page(pTAS2560, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg)); + if (nResult < 0) + goto end; + + nResult = regmap_bulk_write(pTAS2560->regmap, + TAS2560_PAGE_REG(reg), pData, nLength); + if (nResult < 0) + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + else + dev_dbg(pTAS2560->dev, "%s: BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n", + __func__, TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg), + TAS2560_PAGE_REG(reg), nLength); + +end: + mutex_unlock(&pTAS2560->dev_lock); + return nResult; +} + +static int tas2560_dev_bulk_read(struct tas2560_priv *pTAS2560, + unsigned int reg, unsigned char *pData, unsigned int nLength) +{ + int nResult = 0; + + mutex_lock(&pTAS2560->dev_lock); + + nResult = tas2560_change_book_page(pTAS2560, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg)); + if (nResult < 0) + goto end; + + nResult = regmap_bulk_read(pTAS2560->regmap, TAS2560_PAGE_REG(reg), pData, nLength); + if (nResult < 0) + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + else + dev_dbg(pTAS2560->dev, "%s: BOOK:PAGE:REG %u:%u:%u, len: 0x%02x\n", + __func__, TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg), + TAS2560_PAGE_REG(reg), nLength); +end: + mutex_unlock(&pTAS2560->dev_lock); + return nResult; +} + +static int tas2560_dev_update_bits(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned int mask, unsigned int value) +{ + int nResult = 0; + + mutex_lock(&pTAS2560->dev_lock); + nResult = tas2560_change_book_page(pTAS2560, + TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg)); + if (nResult < 0) + goto end; + + nResult = regmap_update_bits(pTAS2560->regmap, TAS2560_PAGE_REG(reg), mask, value); + if (nResult < 0) + dev_err(pTAS2560->dev, "%s, ERROR, L=%d, E=%d\n", + __func__, __LINE__, nResult); + else + dev_dbg(pTAS2560->dev, "%s: BOOK:PAGE:REG %u:%u:%u, mask: 0x%x, val=0x%x\n", + __func__, TAS2560_BOOK_ID(reg), TAS2560_PAGE_ID(reg), + TAS2560_PAGE_REG(reg), mask, value); +end: + mutex_unlock(&pTAS2560->dev_lock); + return nResult; +} + +static bool tas2560_volatile(struct device *dev, unsigned int reg) +{ + return false; +} + +static bool tas2560_writeable(struct device *dev, unsigned int reg) +{ + return true; +} + +static const struct regmap_config tas2560_i2c_regmap = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = tas2560_writeable, + .volatile_reg = tas2560_volatile, + .cache_type = REGCACHE_NONE, + .max_register = 128, +}; + +static void tas2560_hw_reset(struct tas2560_priv *pTAS2560) +{ + if (gpio_is_valid(pTAS2560->mnResetGPIO)) { + gpio_direction_output(pTAS2560->mnResetGPIO, 0); + msleep(5); + gpio_direction_output(pTAS2560->mnResetGPIO, 1); + msleep(2); + } + + pTAS2560->mnCurrentBook = -1; + pTAS2560->mnCurrentPage = -1; +} + +void tas2560_clearIRQ(struct tas2560_priv *pTAS2560) +{ + unsigned int nValue; + int nResult = 0; + + nResult = pTAS2560->read(pTAS2560, TAS2560_FLAGS_1, &nValue); + if (nResult >= 0) + pTAS2560->read(pTAS2560, TAS2560_FLAGS_2, &nValue); +} + +void tas2560_enableIRQ(struct tas2560_priv *pTAS2560, bool enable) +{ + if (enable) { + if (pTAS2560->mbIRQEnable) + return; + + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) + enable_irq(pTAS2560->mnIRQ); + + schedule_delayed_work(&pTAS2560->irq_work, msecs_to_jiffies(10)); + pTAS2560->mbIRQEnable = true; + } else { + if (!pTAS2560->mbIRQEnable) + return; + + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) + disable_irq_nosync(pTAS2560->mnIRQ); + pTAS2560->mbIRQEnable = false; + } +} + +static void irq_work_routine(struct work_struct *work) +{ + struct tas2560_priv *pTAS2560 = + container_of(work, struct tas2560_priv, irq_work.work); + unsigned int nDevInt1Status = 0, nDevInt2Status = 0; + int nCounter = 2; + int nResult = 0; + +#ifdef CONFIG_TAS2560_CODEC + mutex_lock(&pTAS2560->codec_lock); +#endif + +#ifdef CONFIG_TAS2560_MISC + mutex_lock(&pTAS2560->file_lock); +#endif + if (pTAS2560->mbRuntimeSuspend) { + dev_info(pTAS2560->dev, "%s, Runtime Suspended\n", __func__); + goto end; + } + + if (!pTAS2560->mbPowerUp) { + dev_info(pTAS2560->dev, "%s, device not powered\n", __func__); + goto end; + } + + nResult = tas2560_dev_write(pTAS2560, TAS2560_INT_GEN_REG, 0x00); + if (nResult < 0) + goto reload; + + nResult = tas2560_dev_read(pTAS2560, TAS2560_FLAGS_1, &nDevInt1Status); + if (nResult >= 0) + nResult = tas2560_dev_read(pTAS2560, TAS2560_FLAGS_2, &nDevInt2Status); + else + goto reload; + + if (((nDevInt1Status & 0xfc) != 0) || ((nDevInt2Status & 0xc0) != 0)) { + /* in case of INT_OC, INT_UV, INT_OT, INT_BO, INT_CL, INT_CLK1, INT_CLK2 */ + dev_dbg(pTAS2560->dev, "IRQ critical Error : 0x%x, 0x%x\n", + nDevInt1Status, nDevInt2Status); + + if (nDevInt1Status & 0x80) { + pTAS2560->mnErrCode |= ERROR_OVER_CURRENT; + dev_err(pTAS2560->dev, "SPK over current!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_OVER_CURRENT; + + if (nDevInt1Status & 0x40) { + pTAS2560->mnErrCode |= ERROR_UNDER_VOLTAGE; + dev_err(pTAS2560->dev, "SPK under voltage!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_UNDER_VOLTAGE; + + if (nDevInt1Status & 0x20) { + pTAS2560->mnErrCode |= ERROR_CLK_HALT; + dev_err(pTAS2560->dev, "clk halted!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_CLK_HALT; + + if (nDevInt1Status & 0x10) { + pTAS2560->mnErrCode |= ERROR_DIE_OVERTEMP; + dev_err(pTAS2560->dev, "die over temperature!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_DIE_OVERTEMP; + + if (nDevInt1Status & 0x08) { + pTAS2560->mnErrCode |= ERROR_BROWNOUT; + dev_err(pTAS2560->dev, "brownout!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_BROWNOUT; + + if (nDevInt1Status & 0x04) { + pTAS2560->mnErrCode |= ERROR_CLK_LOST; + } else + pTAS2560->mnErrCode &= ~ERROR_CLK_LOST; + + if (nDevInt2Status & 0x80) { + pTAS2560->mnErrCode |= ERROR_CLK_DET1; + dev_err(pTAS2560->dev, "clk detection 1!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_CLK_DET1; + + if (nDevInt2Status & 0x40) { + pTAS2560->mnErrCode |= ERROR_CLK_DET2; + dev_err(pTAS2560->dev, "clk detection 2!\n"); + } else + pTAS2560->mnErrCode &= ~ERROR_CLK_DET2; + + goto reload; + } else { + dev_dbg(pTAS2560->dev, "IRQ status : 0x%x, 0x%x\n", + nDevInt1Status, nDevInt2Status); + nCounter = 2; + + while (nCounter > 0) { + nResult = tas2560_dev_read(pTAS2560, TAS2560_POWER_UP_FLAG_REG, &nDevInt1Status); + if (nResult < 0) + goto reload; + + if ((nDevInt1Status & 0xc0) == 0xc0) + break; + + nCounter--; + if (nCounter > 0) { + /* in case check pow status just after power on TAS2560 */ + dev_dbg(pTAS2560->dev, "PowSts B: 0x%x, check again after 10ms\n", + nDevInt1Status); + msleep(10); + } + } + + if ((nDevInt1Status & 0xc0) != 0xc0) { + dev_err(pTAS2560->dev, "%s, Critical ERROR B[%d]_P[%d]_R[%d]= 0x%x\n", + __func__, + TAS2560_BOOK_ID(TAS2560_POWER_UP_FLAG_REG), + TAS2560_PAGE_ID(TAS2560_POWER_UP_FLAG_REG), + TAS2560_PAGE_REG(TAS2560_POWER_UP_FLAG_REG), + nDevInt1Status); + pTAS2560->mnErrCode |= ERROR_CLASSD_PWR; + goto reload; + } + pTAS2560->mnErrCode &= ~ERROR_CLASSD_PWR; + } + + nResult = tas2560_dev_write(pTAS2560, TAS2560_INT_GEN_REG, 0xff); + if (nResult < 0) + goto reload; + + goto end; + +reload: + /* hardware reset and reload */ + tas2560_LoadConfig(pTAS2560, true); + +end: + if (!hrtimer_active(&pTAS2560->mtimer)) { + dev_dbg(pTAS2560->dev, "%s, start timer\n", __func__); + hrtimer_start(&pTAS2560->mtimer, + ns_to_ktime((u64)CHECK_PERIOD * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } +#ifdef CONFIG_TAS2560_MISC + mutex_unlock(&pTAS2560->file_lock); +#endif + +#ifdef CONFIG_TAS2560_CODEC + mutex_unlock(&pTAS2560->codec_lock); +#endif +} + +static enum hrtimer_restart timer_func(struct hrtimer *timer) +{ + struct tas2560_priv *pTAS2560 = container_of(timer, struct tas2560_priv, mtimer); + + if (pTAS2560->mbPowerUp) { + if (!delayed_work_pending(&pTAS2560->irq_work)) + schedule_delayed_work(&pTAS2560->irq_work, msecs_to_jiffies(20)); + } + + return HRTIMER_NORESTART; +} + +static irqreturn_t tas2560_irq_handler(int irq, void *dev_id) +{ + struct tas2560_priv *pTAS2560 = (struct tas2560_priv *)dev_id; + + tas2560_enableIRQ(pTAS2560, false); + + /* get IRQ status after 100 ms */ + if (!delayed_work_pending(&pTAS2560->irq_work)) + schedule_delayed_work(&pTAS2560->irq_work, msecs_to_jiffies(100)); + + return IRQ_HANDLED; +} + +static int tas2560_runtime_suspend(struct tas2560_priv *pTAS2560) +{ + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + pTAS2560->mbRuntimeSuspend = true; + + if (hrtimer_active(&pTAS2560->mtimer)) { + dev_dbg(pTAS2560->dev, "cancel die temp timer\n"); + hrtimer_cancel(&pTAS2560->mtimer); + } + + if (delayed_work_pending(&pTAS2560->irq_work)) { + dev_dbg(pTAS2560->dev, "cancel IRQ work\n"); + cancel_delayed_work_sync(&pTAS2560->irq_work); + } + + return 0; +} + +static int tas2560_runtime_resume(struct tas2560_priv *pTAS2560) +{ + dev_dbg(pTAS2560->dev, "%s\n", __func__); + + if (pTAS2560->mbPowerUp) { + if (!hrtimer_active(&pTAS2560->mtimer)) { + dev_dbg(pTAS2560->dev, "%s, start check timer\n", __func__); + hrtimer_start(&pTAS2560->mtimer, + ns_to_ktime((u64)CHECK_PERIOD * NSEC_PER_MSEC), HRTIMER_MODE_REL); + } + } + + pTAS2560->mbRuntimeSuspend = false; + + return 0; +} + +static int tas2560_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tas2560_priv *pTAS2560; + int nResult; + + dev_info(&client->dev, "%s enter\n", __func__); + + pTAS2560 = devm_kzalloc(&client->dev, sizeof(struct tas2560_priv), GFP_KERNEL); + if (pTAS2560 == NULL) { + dev_err(&client->dev, "%s, -ENOMEM \n", __func__); + nResult = -ENOMEM; + goto end; + } + + pTAS2560->dev = &client->dev; + i2c_set_clientdata(client, pTAS2560); + dev_set_drvdata(&client->dev, pTAS2560); + + pTAS2560->regmap = devm_regmap_init_i2c(client, &tas2560_i2c_regmap); + if (IS_ERR(pTAS2560->regmap)) { + nResult = PTR_ERR(pTAS2560->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + nResult); + goto end; + } + + if (client->dev.of_node) + tas2560_parse_dt(&client->dev, pTAS2560); + + if (gpio_is_valid(pTAS2560->mnResetGPIO)) { + nResult = gpio_request(pTAS2560->mnResetGPIO, "TAS2560_RESET"); + if (nResult) { + dev_err(pTAS2560->dev, "%s: Failed to request gpio %d\n", __func__, + pTAS2560->mnResetGPIO); + nResult = -EINVAL; + goto free_gpio; + } + } + + if (gpio_is_valid(pTAS2560->mnSwitchGPIO)) { + nResult = gpio_request(pTAS2560->mnSwitchGPIO, "TAS2560_EAR_SWITH"); + if (nResult) { + dev_err(pTAS2560->dev, "%s: Failed to request gpio %d\n", __func__, + pTAS2560->mnSwitchGPIO); + nResult = -EINVAL; + goto free_gpio; + } + gpio_direction_output(pTAS2560->mnSwitchGPIO, 0); + } + + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) { + nResult = gpio_request(pTAS2560->mnIRQGPIO, "TAS2560-IRQ"); + if (nResult < 0) { + dev_err(pTAS2560->dev, "%s: GPIO %d request error\n", + __func__, pTAS2560->mnIRQGPIO); + goto free_gpio; + } + gpio_direction_input(pTAS2560->mnIRQGPIO); + pTAS2560->mnIRQ = gpio_to_irq(pTAS2560->mnIRQGPIO); + dev_dbg(pTAS2560->dev, "irq = %d\n", pTAS2560->mnIRQ); + nResult = request_threaded_irq(pTAS2560->mnIRQ, tas2560_irq_handler, + NULL, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + client->name, pTAS2560); + if (nResult < 0) { + dev_err(pTAS2560->dev, + "request_irq failed, %d\n", nResult); + goto free_gpio; + } + disable_irq_nosync(pTAS2560->mnIRQ); + INIT_DELAYED_WORK(&pTAS2560->irq_work, irq_work_routine); + } + + pTAS2560->read = tas2560_dev_read; + pTAS2560->write = tas2560_dev_write; + pTAS2560->bulk_read = tas2560_dev_bulk_read; + pTAS2560->bulk_write = tas2560_dev_bulk_write; + pTAS2560->update_bits = tas2560_dev_update_bits; + pTAS2560->hw_reset = tas2560_hw_reset; + pTAS2560->enableIRQ = tas2560_enableIRQ; + pTAS2560->clearIRQ = tas2560_clearIRQ; + pTAS2560->runtime_suspend = tas2560_runtime_suspend; + pTAS2560->runtime_resume = tas2560_runtime_resume; + mutex_init(&pTAS2560->dev_lock); + + nResult = tas2560_LoadConfig(pTAS2560, false); + if (nResult < 0) + goto destroy_mutex; + +#ifdef CONFIG_TAS2560_CODEC + mutex_init(&pTAS2560->codec_lock); + tas2560_register_codec(pTAS2560); +#endif + +#ifdef CONFIG_TAS2560_MISC + mutex_init(&pTAS2560->file_lock); + tas2560_register_misc(pTAS2560); +#endif + + hrtimer_init(&pTAS2560->mtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + pTAS2560->mtimer.function = timer_func; + +destroy_mutex: + if (nResult < 0) + mutex_destroy(&pTAS2560->dev_lock); + +free_gpio: + if (nResult < 0) { + if (gpio_is_valid(pTAS2560->mnResetGPIO)) + gpio_free(pTAS2560->mnResetGPIO); + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) + gpio_free(pTAS2560->mnIRQGPIO); + if (gpio_is_valid(pTAS2560->mnSwitchGPIO)) + gpio_free(pTAS2560->mnSwitchGPIO); + } + +end: + return nResult; +} + +static int tas2560_i2c_remove(struct i2c_client *client) +{ + struct tas2560_priv *pTAS2560 = i2c_get_clientdata(client); + + dev_info(pTAS2560->dev, "%s\n", __func__); + +#ifdef CONFIG_TAS2560_CODEC + tas2560_deregister_codec(pTAS2560); + mutex_destroy(&pTAS2560->codec_lock); +#endif + +#ifdef CONFIG_TAS2560_MISC + tas2560_deregister_misc(pTAS2560); + mutex_destroy(&pTAS2560->file_lock); +#endif + + if (gpio_is_valid(pTAS2560->mnResetGPIO)) + gpio_free(pTAS2560->mnResetGPIO); + if (gpio_is_valid(pTAS2560->mnIRQGPIO)) + gpio_free(pTAS2560->mnIRQGPIO); + if (gpio_is_valid(pTAS2560->mnSwitchGPIO)) + gpio_free(pTAS2560->mnSwitchGPIO); + + return 0; +} + + +static const struct i2c_device_id tas2560_i2c_id[] = { + { "tas2560", 0}, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas2560_i2c_id); + +#if defined(CONFIG_OF) +static const struct of_device_id tas2560_of_match[] = { + { .compatible = "ti,tas2560" }, + {}, +}; +MODULE_DEVICE_TABLE(of, tas2560_of_match); +#endif + + +static struct i2c_driver tas2560_i2c_driver = { + .driver = { + .name = "tas2560", + .owner = THIS_MODULE, +#if defined(CONFIG_OF) + .of_match_table = of_match_ptr(tas2560_of_match), +#endif + }, + .probe = tas2560_i2c_probe, + .remove = tas2560_i2c_remove, + .id_table = tas2560_i2c_id, +}; + +module_i2c_driver(tas2560_i2c_driver); + +MODULE_AUTHOR("Texas Instruments Inc."); +MODULE_DESCRIPTION("TAS2560 I2C Smart Amplifier driver"); +MODULE_LICENSE("GPL v2"); +#endif diff --git a/sound/soc/codecs/tas2560.h b/sound/soc/codecs/tas2560.h new file mode 100644 index 000000000000..2098985af7f7 --- /dev/null +++ b/sound/soc/codecs/tas2560.h @@ -0,0 +1,223 @@ +/* +** ============================================================================= +** Copyright (c) 2016 Texas Instruments Inc. +** +** 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; version 2. +** +** 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. +** +** File: +** tas2560.h +** +** Description: +** definitions and data structures for TAS2560 Android Linux driver +** +** ============================================================================= +*/ + +#ifndef _TAS2560_H +#define _TAS2560_H + +/* Page Control Register */ +#define TAS2560_PAGECTL_REG 0 + +/* Book Control Register (available in page0 of each book) */ +#define TAS2560_BOOKCTL_PAGE 0 +#define TAS2560_BOOKCTL_REG 127 + +#define TAS2560_REG(book, page, reg) (((book * 256 * 128) + \ + (page * 128)) + reg) + +#define TAS2560_BOOK_ID(reg) (reg / (256 * 128)) +#define TAS2560_PAGE_ID(reg) ((reg % (256 * 128)) / 128) +#define TAS2560_BOOK_REG(reg) (reg % (256 * 128)) +#define TAS2560_PAGE_REG(reg) ((reg % (256 * 128)) % 128) + +/* Book0, Page0 registers */ +#define TAS2560_SW_RESET_REG TAS2560_REG(0, 0, 1) +#define TAS2560_DEV_MODE_REG TAS2560_REG(0, 0, 2) +#define TAS2560_SPK_CTRL_REG TAS2560_REG(0, 0, 4) +#define TAS2560_MUTE_REG TAS2560_REG(0, 0, 7) +#define TAS2560_PWR_REG TAS2560_REG(0, 0, 7) +#define TAS2560_PWR_BIT_MASK (0x3 << 6) +#define TAS2560_MUTE_MASK (0x7) + +#define TAS2560_SR_CTRL1 TAS2560_REG(0, 0, 8) +#define TAS2560_LOAD TAS2560_REG(0, 0, 9) +#define TAS2560_SR_CTRL2 TAS2560_REG(0, 0, 13) /* B0_P0_R0x0d*/ +#define TAS2560_SR_CTRL3 TAS2560_REG(0, 0, 14) /* B0_P0_R0x0e*/ + +#define TAS2560_CLK_SEL TAS2560_REG(0, 0, 15) +#define TAS2560_PLL_SRC_MASK (0xc0) +#define TAS2560_PLL_CLKIN_BCLK (0) +#define TAS2560_PLL_CLKIN_MCLK (1) +#define TAS2560_PLL_CLKIN_PDMCLK (2) +#define TAS2560_PLL_P_MASK (0x3f) + +#define TAS2560_SET_FREQ TAS2560_REG(0, 0, 16) +#define TAS2560_PLL_J_MASK (0x7f) + +#define TAS2560_PLL_D_MSB TAS2560_REG(0, 0, 17) /*B0_P0_R0x11*/ +#define TAS2560_PLL_D_LSB TAS2560_REG(0, 0, 18) /*B0_P0_R0x12*/ + +#define TAS2560_DAI_FMT TAS2560_REG(0, 0, 20) /*B0_P0_R0x14*/ +#define TAS2560_ASI_OFFSET_1 TAS2560_REG(0, 0, 22) +#define TAS2560_ASI_CFG_1 TAS2560_REG(0, 0, 24) /*B0_P0_R0x18*/ +#define TAS2560_DIRINV_MASK 0x3c +#define TAS2560_BCLKINV (1 << 2) +#define TAS2560_WCLKINV (1 << 3) +#define TAS2560_BCLKDIR (1 << 4) +#define TAS2560_WCLKDIR (1 << 5) + +#define TAS2560_CLK_ERR_CTRL TAS2560_REG(0, 0, 33) /* B0_P0_R0x21 */ +#define TAS2560_IRQ_PIN_REG TAS2560_REG(0, 0, 35) /* B0_P0_R0x23 */ +#define TAS2560_INT_MODE_REG TAS2560_REG(0, 0, 36) /* B0_P0_R0x24 */ +#define TAS2560_INT_GEN_REG TAS2560_REG(0, 0, 37) /* B0_P0_R0x25 */ +#define TAS2560_FLAGS_1 TAS2560_REG(0, 0, 38) /* B0_P0_R0x26 */ +#define TAS2560_FLAGS_2 TAS2560_REG(0, 0, 39) /* B0_P0_R0x27 */ +#define TAS2560_POWER_UP_FLAG_REG TAS2560_REG(0, 0, 42) /* B0_P0_R0x2a */ + +#define TAS2560_DR_BOOST_REG_2 TAS2560_REG(0, 0, 60) /* B0_P0_R0x3c */ +#define TAS2560_DR_BOOST_REG_1 TAS2560_REG(0, 0, 73) /* B0_P0_R0x49 */ +#define TAS2560_CLK_ERR_CTRL2 TAS2560_REG(0, 0, 80) /* B0_P0_R0x50 */ +#define TAS2560_ID_REG TAS2560_REG(0, 0, 125) + +/* Book0, Page50 registers */ +#define TAS2560_PPG TAS2560_REG(0, 50, 12) /*B0_P0x32_R0x0c*/ + +#define TAS2560_HPF_CUTOFF_CTL1 TAS2560_REG(0, 50, 28) /* B0_P0x32_R0x1c */ +#define TAS2560_HPF_CUTOFF_CTL2 TAS2560_REG(0, 50, 32) /* B0_P0x32_R0x20 */ +#define TAS2560_HPF_CUTOFF_CTL3 TAS2560_REG(0, 50, 36) /* B0_P0x32_R0x24 */ + +#define TAS2560_ISENSE_PATH_CTL1 TAS2560_REG(0, 50, 40) /* B0_P0x32_R0x28 */ +#define TAS2560_ISENSE_PATH_CTL2 TAS2560_REG(0, 50, 44) /* B0_P0x32_R0x2c */ +#define TAS2560_ISENSE_PATH_CTL3 TAS2560_REG(0, 50, 48) /* B0_P0x32_R0x30 */ + +#define TAS2560_VLIMIT_THRESHOLD TAS2560_REG(0, 50, 60) +#define TAS2560_IDLE_CHNL_DETECT TAS2560_REG(0, 50, 108) /* B0_P0x32_R0x6c */ + +/* Book0, Page51 registers */ +#define TAS2560_BOOST_HEAD TAS2560_REG(0, 51, 24) /* B0_P0x33_R0x18 */ +#define TAS2560_BOOST_ON TAS2560_REG(0, 51, 16) /* B0_P0x33_R0x10 */ +#define TAS2560_BOOST_OFF TAS2560_REG(0, 51, 20) /* B0_P0x33_R0x14 */ +#define TAS2560_BOOST_TABLE_CTRL1 TAS2560_REG(0, 51, 32) /* B0_P0x33_R0x20 */ +#define TAS2560_BOOST_TABLE_CTRL2 TAS2560_REG(0, 51, 36) /* B0_P0x33_R0x24 */ +#define TAS2560_BOOST_TABLE_CTRL3 TAS2560_REG(0, 51, 40) /* B0_P0x33_R0x28 */ +#define TAS2560_BOOST_TABLE_CTRL4 TAS2560_REG(0, 51, 44) /* B0_P0x33_R0x2c */ +#define TAS2560_BOOST_TABLE_CTRL5 TAS2560_REG(0, 51, 48) /* B0_P0x33_R0x30 */ +#define TAS2560_BOOST_TABLE_CTRL6 TAS2560_REG(0, 51, 52) /* B0_P0x33_R0x34 */ +#define TAS2560_BOOST_TABLE_CTRL7 TAS2560_REG(0, 51, 56) /* B0_P0x33_R0x38 */ +#define TAS2560_BOOST_TABLE_CTRL8 TAS2560_REG(0, 51, 60) /* B0_P0x33_R0x3c */ +#define TAS2560_THERMAL_FOLDBACK TAS2560_REG(0, 51, 100) /* B0_P0x33_R0x64 */ + +/* Book0, Page52 registers */ +#define TAS2560_VSENSE_DEL_CTL1 TAS2560_REG(0, 52, 52) /* B0_P0x34_R0x34 */ +#define TAS2560_VSENSE_DEL_CTL2 TAS2560_REG(0, 52, 56) /* B0_P0x34_R0x38 */ +#define TAS2560_VSENSE_DEL_CTL3 TAS2560_REG(0, 52, 60) /* B0_P0x34_R0x3c */ +#define TAS2560_VSENSE_DEL_CTL4 TAS2560_REG(0, 52, 64) /* B0_P0x34_R0x40 */ +#define TAS2560_VSENSE_DEL_CTL5 TAS2560_REG(0, 52, 68) /* B0_P0x34_R0x44 */ + +#define TAS2560_DATAFORMAT_SHIFT 2 +#define TAS2560_DATAFORMAT_I2S 0x0 +#define TAS2560_DATAFORMAT_DSP 0x1 +#define TAS2560_DATAFORMAT_RIGHT_J 0x2 +#define TAS2560_DATAFORMAT_LEFT_J 0x3 + +#define TAS2560_DAI_FMT_MASK (0x7 << TAS2560_DATAFORMAT_SHIFT) + +#define LOAD_MASK 0x18 +#define LOAD_8OHM (0) +#define LOAD_6OHM (1) +#define LOAD_4OHM (2) + +#define PLL_BCLK_1P536_48KSR (0) +#define PLL_BCLK_3P072_48KSR (1) + +#define CHECK_PERIOD 5000 /* 5 second */ + +#define ERROR_NONE 0x00000000 +#define ERROR_PLL_ABSENT 0x00000001 +#define ERROR_DEVA_I2C_COMM 0x00000002 +#define ERROR_DEVB_I2C_COMM 0x00000004 +#define ERROR_PRAM_CRCCHK 0x00000008 +#define ERROR_YRAM_CRCCHK 0x00000010 +#define ERROR_CLK_DET2 0x00000020 +#define ERROR_CLK_DET1 0x00000040 +#define ERROR_CLK_LOST 0x00000080 +#define ERROR_BROWNOUT 0x00000100 +#define ERROR_DIE_OVERTEMP 0x00000200 +#define ERROR_CLK_HALT 0x00000400 +#define ERROR_UNDER_VOLTAGE 0x00000800 +#define ERROR_OVER_CURRENT 0x00001000 +#define ERROR_CLASSD_PWR 0x00002000 +#define ERROR_FAILSAFE 0x40000000 + +struct tas2560_register { + int book; + int page; + int reg; +}; + +struct tas2560_dai_cfg { + unsigned int dai_fmt; + unsigned int tdm_delay; +}; + +struct tas2560_priv { + struct device *dev; + struct regmap *regmap; + struct mutex dev_lock; + struct delayed_work irq_work; + struct hrtimer mtimer; + int mnClkin; + int mnClkid; + bool mbPowerUp; + int mnCurrentBook; + int mnCurrentPage; + int mnLoad; + int mnASIFormat; + int mnResetGPIO; + int mnIRQGPIO; + int mnSwitchGPIO; + bool enablePmicEarPath; + int mnIRQ; + bool mbIRQEnable; + int mnSamplingRate; + int mnFrameSize; + int mnPLL; + int mnPPG; + + int (*read)(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned int *pValue); + int (*write)(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned int Value); + int (*bulk_read)(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned char *pData, unsigned int len); + int (*bulk_write)(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned char *pData, unsigned int len); + int (*update_bits)(struct tas2560_priv *pTAS2560, unsigned int reg, + unsigned int mask, unsigned int value); + void (*hw_reset)(struct tas2560_priv *pTAS2560); + void (*clearIRQ)(struct tas2560_priv *pTAS2560); + void (*enableIRQ)(struct tas2560_priv *pTAS2560, bool enable); + /* device is working, but system is suspended */ + int (*runtime_suspend)(struct tas2560_priv *pTAS2560); + int (*runtime_resume)(struct tas2560_priv *pTAS2560); + bool mbRuntimeSuspend; + + unsigned int mnErrCode; +#ifdef CONFIG_TAS2560_CODEC + struct mutex codec_lock; +#endif + +#ifdef CONFIG_TAS2560_MISC + int mnDBGCmd; + int mnCurrentReg; + struct mutex file_lock; +#endif +}; +#endif