| /* |
| * Copyright (C) NXP Semiconductors (PLMA) |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #include <linux/cdev.h> |
| #include <linux/clk.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/init.h> |
| #include <linux/of.h> |
| #include <linux/of_gpio.h> |
| #include <linux/i2c.h> |
| #include <linux/gpio.h> |
| #include <linux/module.h> |
| #include <linux/moduleparam.h> |
| #include <linux/pm.h> |
| #include <linux/slab.h> |
| #include <linux/regmap.h> |
| #include <sound/tfa98xx.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/soc.h> |
| #include <sound/soc-dapm.h> |
| #include <sound/initval.h> |
| #include <sound/tlv.h> |
| #include <linux/version.h> |
| |
| #include "tfa98xx-core.h" |
| #include "tfa98xx-regs.h" |
| #include "tfa_container.h" |
| #include "tfa_dsp.h" |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) "%s(%s): " fmt, __func__, tfa98xx->fw.name |
| |
| #define I2C_RETRY_DELAY 5 /* ms */ |
| #define I2C_RETRIES 5 |
| |
| #define TFA98XX_REG_CACHE_SIZE 129 |
| |
| /* SNDRV_PCM_RATE_KNOT -> 12000, 24000 Hz, limit with constraint list */ |
| #define TFA98XX_RATES (SNDRV_PCM_RATE_8000_48000 | SNDRV_PCM_RATE_KNOT) |
| #define TFA98XX_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) |
| |
| #define TFA98XX_STATUS_UP_MASK (TFA98XX_STATUSREG_PLLS | \ |
| TFA98XX_STATUSREG_CLKS | \ |
| TFA98XX_STATUSREG_VDDS | \ |
| TFA98XX_STATUSREG_AREFS) |
| |
| /* table used by ALSA core while creating codec register |
| * access debug fs. |
| */ |
| static u8 tfa98xx_reg_readable[TFA98XX_REG_CACHE_SIZE] = { |
| [TFA98XX_STATUSREG] = 1, |
| [TFA98XX_BATTERYVOLTAGE] = 1, |
| [TFA98XX_TEMPERATURE] = 1, |
| [TFA98XX_REVISIONNUMBER] = 1, |
| [TFA98XX_I2SREG] = 1, |
| [TFA98XX_BAT_PROT] = 1, |
| [TFA98XX_AUDIO_CTR] = 1, |
| [TFA98XX_DCDCBOOST] = 1, |
| [TFA98XX_SPKR_CALIBRATION] = 1, |
| [TFA98XX_SYS_CTRL] = 1, |
| [TFA98XX_I2S_SEL_REG] = 1, |
| [TFA98XX_MTPKEY2_REG] = 1, |
| [TFA98XX_INTERRUPT_REG] = 1, |
| [TFA98XX_CURRENTSENSE3] = 1, |
| [TFA98XX_CURRENTSENSE4] = 1, |
| [TFA98XX_CF_CONTROLS] = 1, |
| [TFA98XX_CF_MAD] = 1, |
| [TFA98XX_CF_MEM] = 1, |
| [TFA98XX_CF_STATUS] = 1, |
| [TFA98XX_KEY2_PROTECTED_SPKR_CAL_MTP] = 1, |
| }; |
| |
| /* |
| * I2C Read/Write Functions |
| */ |
| |
| int tfa98xx_i2c_read(struct i2c_client *tfa98xx_client, u8 reg, u8 *value, |
| int len) |
| { |
| int err; |
| int tries = 0; |
| |
| struct i2c_msg msgs[] = { |
| { |
| .addr = tfa98xx_client->addr, |
| .flags = 0, |
| .len = 1, |
| .buf = ®, |
| }, |
| { |
| .addr = tfa98xx_client->addr, |
| .flags = I2C_M_RD, |
| .len = len, |
| .buf = value, |
| }, |
| }; |
| |
| do { |
| err = i2c_transfer(tfa98xx_client->adapter, msgs, |
| ARRAY_SIZE(msgs)); |
| if (err != ARRAY_SIZE(msgs)) |
| msleep_interruptible(I2C_RETRY_DELAY); |
| } while ((err != ARRAY_SIZE(msgs)) && (++tries < I2C_RETRIES)); |
| |
| if (err != ARRAY_SIZE(msgs)) { |
| dev_err(&tfa98xx_client->dev, "read transfer error %d\n" , err); |
| err = -EIO; |
| } else { |
| err = 0; |
| } |
| |
| return err; |
| } |
| |
| int tfa98xx_bulk_write_raw(struct snd_soc_codec *codec, const u8 *data, |
| u8 count) |
| { |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| int ret; |
| |
| ret = i2c_master_send(tfa98xx->i2c, data, count); |
| if (ret == count) { |
| return 0; |
| } else if (ret < 0) { |
| pr_err("Error I2C send %d\n", ret); |
| return ret; |
| } else { |
| pr_err("Error I2C send size mismatch %d\n", ret); |
| return -EIO; |
| } |
| } |
| |
| static void tfa98xx_monitor(struct work_struct *work) |
| { |
| struct tfa98xx *tfa98xx = container_of(work, struct tfa98xx, |
| delay_work.work); |
| u16 val; |
| u16 sysctrl; |
| |
| pr_debug("%s()\n", __func__); |
| |
| mutex_lock(&tfa98xx->dsp_init_lock); |
| |
| /* |
| * check IC status bits: cold start, amp switching, speaker error |
| * and DSP watch dog bit to re init |
| */ |
| val = snd_soc_read(tfa98xx->codec, TFA98XX_STATUSREG); |
| pr_debug("SYS_STATUS: 0x%04x\n", val); |
| sysctrl = snd_soc_read(tfa98xx->codec, TFA98XX_SYS_CTRL); |
| /* The following are the error conditions: |
| * 1. ACS error only if CFE is enabled. |
| * 2. WDS error with or without CFE enabled. |
| * 3. SPKS error only if CFE is enabled. |
| * 4. SWS error with or without CFE enabled. |
| * */ |
| if (((TFA98XX_SYS_CTRL_CFE_MSK & sysctrl) && (TFA98XX_STATUSREG_ACS & val)) || |
| (TFA98XX_STATUSREG_WDS & val) || |
| ((TFA98XX_SYS_CTRL_CFE_MSK & sysctrl) && (TFA98XX_STATUSREG_SPKS & val)) || |
| !(TFA98XX_STATUSREG_SWS & val)) { |
| if ((TFA98XX_SYS_CTRL_CFE_MSK & sysctrl) && (TFA98XX_STATUSREG_ACS & val)) |
| pr_err("ERROR: ACS\n"); |
| if (TFA98XX_STATUSREG_WDS & val) |
| pr_err("ERROR: WDS\n"); |
| if ((TFA98XX_SYS_CTRL_CFE_MSK & sysctrl) && (TFA98XX_STATUSREG_SPKS & val)) |
| pr_err("ERROR: SPKS\n"); |
| if (!(TFA98XX_STATUSREG_SWS & val)) |
| pr_err("ERROR: AMP_SWS\n"); |
| |
| tfa98xx->dsp_init = TFA98XX_DSP_INIT_RECOVER; |
| /* schedule init now if the clocks are up and stable */ |
| if ((val & TFA98XX_STATUS_UP_MASK) == TFA98XX_STATUS_UP_MASK) |
| queue_work(tfa98xx->tfa98xx_wq, &tfa98xx->init_work); |
| } |
| |
| /* else just reschedule */ |
| queue_delayed_work(tfa98xx->tfa98xx_wq, &tfa98xx->delay_work, 5*HZ); |
| mutex_unlock(&tfa98xx->dsp_init_lock); |
| } |
| |
| static void tfa98xx_dsp_init_power(struct tfa98xx *tfa98xx, int power) |
| { |
| |
| pr_debug("profile %d, vstep %d\n", tfa98xx->profile_ctl, |
| tfa98xx->vstep_ctl); |
| |
| mutex_lock(&tfa98xx->dsp_init_lock); |
| |
| /* start the DSP using the latest profile / vstep */ |
| if (!tfa98xx_dsp_start(tfa98xx, power, tfa98xx->profile_ctl, |
| tfa98xx->vstep_ctl)) |
| tfa98xx->dsp_init = TFA98XX_DSP_INIT_DONE; |
| |
| mutex_unlock(&tfa98xx->dsp_init_lock); |
| } |
| |
| static void tfa98xx_dsp_init(struct work_struct *work) |
| { |
| struct tfa98xx *tfa98xx = container_of(work, struct tfa98xx, init_work); |
| /* Initilaize and unmute */ |
| tfa98xx_dsp_init_power(tfa98xx, 1); |
| } |
| |
| /* |
| * ASOC OPS |
| */ |
| |
| static u32 tfa98xx_asrc_rates[] = { |
| 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 |
| }; |
| |
| static struct snd_pcm_hw_constraint_list constraints_12_24 = { |
| .list = tfa98xx_asrc_rates, |
| .count = ARRAY_SIZE(tfa98xx_asrc_rates), |
| }; |
| |
| static int tfa98xx_set_dai_sysclk(struct snd_soc_dai *codec_dai, |
| int clk_id, unsigned int freq, int dir) |
| { |
| struct tfa98xx *tfa98xx = |
| snd_soc_codec_get_drvdata(codec_dai->codec); |
| |
| pr_debug("%s() freq %d, dir %d\n", __func__, freq, dir); |
| |
| tfa98xx->sysclk = freq; |
| return 0; |
| } |
| |
| static int tfa98xx_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) |
| { |
| struct snd_soc_codec *codec = codec_dai->codec; |
| struct tfa98xx *tfa98xx = |
| snd_soc_codec_get_drvdata(codec_dai->codec); |
| u16 val; |
| |
| pr_debug("\n"); |
| |
| /* set master/slave audio interface */ |
| switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { |
| case SND_SOC_DAIFMT_CBS_CFS: |
| /* default value */ |
| break; |
| case SND_SOC_DAIFMT_CBM_CFM: |
| default: |
| /* only supports Slave mode */ |
| pr_err("tfa98xx: invalid DAI master/slave interface\n"); |
| return -EINVAL; |
| } |
| val = snd_soc_read(codec, TFA98XX_AUDIOREG); |
| switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { |
| case SND_SOC_DAIFMT_I2S: |
| /* default value */ |
| break; |
| case SND_SOC_DAIFMT_RIGHT_J: |
| val &= ~(TFA98XX_FORMAT_MASK); |
| val |= TFA98XX_FORMAT_LSB; |
| break; |
| case SND_SOC_DAIFMT_LEFT_J: |
| val &= ~(TFA98XX_FORMAT_MASK); |
| val |= TFA98XX_FORMAT_MSB; |
| break; |
| default: |
| pr_err("tfa98xx: invalid DAI interface format\n"); |
| return -EINVAL; |
| } |
| |
| snd_soc_write(codec, TFA98XX_AUDIOREG, val); |
| |
| return 0; |
| } |
| |
| static int tfa98xx_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 tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| pr_debug("Store rate: %d\n", params_rate(params)); |
| |
| /* Store rate for further use during DSP init */ |
| tfa98xx->rate = params_rate(params); |
| |
| return 0; |
| } |
| |
| static int tfa98xx_digital_mute(struct snd_soc_dai *dai, int mute) |
| { |
| struct snd_soc_codec *codec = dai->codec; |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| pr_debug("state: %d\n", mute); |
| |
| if (mute) { |
| cancel_delayed_work_sync(&tfa98xx->delay_work); |
| |
| /* |
| * need to wait for amp to stop switching, to minimize |
| * pop, else I2S clk is going away too soon interrupting |
| * the dsp from smothering the amp pop while turning it |
| * off, It shouldn't take more than 50 ms for the amp |
| * switching to stop. |
| */ |
| mutex_lock(&tfa98xx->dsp_init_lock); |
| tfa98xx_dsp_stop(tfa98xx); |
| mutex_unlock(&tfa98xx->dsp_init_lock); |
| } else { |
| /* |
| * start monitor thread to check IC status bit 5secs, and |
| * re-init IC to recover. |
| */ |
| queue_delayed_work(tfa98xx->tfa98xx_wq, &tfa98xx->delay_work, |
| HZ); |
| } |
| |
| return 0; |
| } |
| |
| static int tfa98xx_startup(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_codec *codec = dai->codec; |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| pr_debug("\n"); |
| |
| snd_pcm_hw_constraint_list(substream->runtime, 0, |
| SNDRV_PCM_HW_PARAM_RATE, |
| &constraints_12_24); |
| |
| return 0; |
| } |
| |
| static void tfa98xx_shutdown(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct snd_soc_codec *codec = dai->codec; |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| pr_debug("\n"); |
| } |
| |
| static int tfa98xx_prepare(struct snd_pcm_substream *substream, |
| struct snd_soc_dai *dai) |
| { |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(dai->codec); |
| /* Initialize DSP and leave the amplifier muted, DSP powered down. */ |
| tfa98xx_dsp_init_power(tfa98xx, 0); |
| return 0; |
| } |
| /* Trigger callback is atomic function, It gets called when pcm is started */ |
| |
| static int tfa98xx_trigger(struct snd_pcm_substream *substream, int cmd, |
| struct snd_soc_dai *dai) |
| { |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(dai->codec); |
| int ret = 0; |
| |
| pr_debug("cmd: %d\n", cmd); |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| /* |
| * To initialize dsp all the I2S clocks must be up and running. |
| * so that the DSP's internal PLL can sync up and memory becomes |
| * accessible. Trigger callback is called when pcm write starts, |
| * so this should be the place where DSP is initialized |
| */ |
| queue_work(tfa98xx->tfa98xx_wq, &tfa98xx->init_work); |
| break; |
| case SNDRV_PCM_TRIGGER_RESUME: |
| case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
| case SNDRV_PCM_TRIGGER_STOP: |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
| break; |
| default: |
| ret = -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| /* |
| * ASOC controls |
| */ |
| |
| static const struct snd_soc_dapm_widget tfa98xx_dapm_widgets[] = { |
| SND_SOC_DAPM_INPUT("I2S1"), |
| SND_SOC_DAPM_MIXER("NXP Output Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), |
| }; |
| |
| static const struct snd_soc_dapm_route tfa98xx_dapm_routes[] = { |
| {"NXP Output Mixer", NULL, "TFA Playback"}, |
| }; |
| |
| |
| /* |
| * Helpers for profile selection controls |
| */ |
| int tfa98xx_get_profile_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| ucontrol->value.integer.value[0] = tfa98xx->profile_current; |
| |
| /* pr_debug("%s: profile %d\n", tfa98xx->fw.name, tfa98xx->profile); */ |
| |
| return 0; |
| } |
| |
| int tfa98xx_set_profile_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| struct tfaprofile *profiles = |
| (struct tfaprofile *)kcontrol->private_value; |
| struct tfaprofile *prof_old = &profiles[tfa98xx->profile_current]; |
| struct tfaprofile *prof_new; |
| |
| if (profiles == NULL) { |
| pr_err("No profiles\n"); |
| return -EINVAL; |
| } |
| |
| if (tfa98xx->profile_ctl == ucontrol->value.integer.value[0]) |
| return 0; |
| |
| tfa98xx->profile_ctl = ucontrol->value.integer.value[0]; |
| prof_new = &profiles[tfa98xx->profile_ctl]; |
| |
| pr_debug("active profile %d, new profile %d\n", |
| tfa98xx->profile_current, tfa98xx->profile_ctl); |
| |
| /* |
| adjust the vstep value based on the number of volume steps of the |
| new profile. |
| */ |
| prof_new->vstep = (int)(tfa98xx->vstep_ctl * prof_new->vsteps |
| / prof_old->vsteps); |
| |
| pr_debug("active vstep %d, new vstep %d\n", prof_old->vstep, |
| prof_new->vstep); |
| |
| if (tfa98xx_is_amp_running(tfa98xx)) { |
| /* |
| When switching profile when the amplifier is running, |
| there might be pops because of the mute/unmute during |
| profile switch. |
| */ |
| pr_debug("Warning: switching profile while amplifier is running\n"); |
| tfa98xx_dsp_start(tfa98xx, 1, tfa98xx->profile_ctl, |
| prof_new->vstep); |
| } |
| |
| prof_old->vstep = tfa98xx->vstep_ctl; |
| tfa98xx->vstep_ctl = prof_new->vstep; |
| |
| return 0; |
| } |
| |
| int tfa98xx_info_profile_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| struct tfaprofile *profiles = |
| (struct tfaprofile *)kcontrol->private_value; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| if (profiles == NULL) { |
| pr_err("No profiles\n"); |
| return -EINVAL; |
| } |
| |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; |
| uinfo->count = 1; |
| uinfo->value.enumerated.items = tfa98xx->profile_count; |
| |
| if (uinfo->value.enumerated.item > tfa98xx->profile_count - 1) |
| uinfo->value.enumerated.item = tfa98xx->profile_count - 1; |
| |
| strcpy(uinfo->value.enumerated.name, |
| profiles[uinfo->value.enumerated.item].name); |
| |
| return 0; |
| } |
| |
| /* |
| * Helpers for volume through vstep controls |
| */ |
| int tfa98xx_get_vol_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct tfaprofile *profiles = |
| (struct tfaprofile *)kcontrol->private_value; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| int index = tfa98xx->profile_ctl; |
| struct tfaprofile *prof = &profiles[index]; |
| |
| |
| if (profiles == NULL) { |
| pr_err("No profiles\n"); |
| return -EINVAL; |
| } |
| |
| ucontrol->value.integer.value[0] = prof->vsteps - prof->vstep - 1; |
| |
| pr_debug("%s: %d/%d\n", prof->name, prof->vstep, prof->vsteps - 1); |
| |
| return 0; |
| } |
| |
| int tfa98xx_set_vol_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct tfaprofile *profiles = |
| (struct tfaprofile *)kcontrol->private_value; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| int index = tfa98xx->profile_ctl; |
| struct tfaprofile *prof = &profiles[index]; |
| |
| if (profiles == NULL) { |
| pr_err("No profiles\n"); |
| return -EINVAL; |
| } |
| |
| if (prof->vstep == prof->vsteps - ucontrol->value.integer.value[0] - 1) |
| return 0; |
| |
| |
| prof->vstep = prof->vsteps - ucontrol->value.integer.value[0] - 1; |
| |
| if (prof->vstep < 0) |
| prof->vstep = 0; |
| |
| pr_debug("%s: %d/%d\n", prof->name, prof->vstep, prof->vsteps - 1); |
| |
| if (tfa98xx_is_amp_running(tfa98xx)) |
| tfa98xx_dsp_start(tfa98xx, 1, tfa98xx->profile_ctl, prof->vstep); |
| |
| tfa98xx->vstep_ctl = prof->vstep; |
| |
| return 1; |
| } |
| |
| int tfa98xx_info_vol_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_info *uinfo) |
| { |
| struct tfaprofile *profiles = |
| (struct tfaprofile *)kcontrol->private_value; |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| struct tfaprofile *prof = &profiles[tfa98xx->profile_ctl]; |
| |
| if (profiles == NULL) { |
| pr_err("No profiles\n"); |
| return -EINVAL; |
| } |
| |
| pr_debug("%s [0..%d]\n", prof->name, prof->vsteps - 1); |
| |
| uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
| uinfo->count = 1; |
| uinfo->value.integer.min = 0; |
| uinfo->value.integer.max = prof->vsteps - 1; |
| |
| return 0; |
| } |
| |
| int tfa98xx_get_stop_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| ucontrol->value.integer.value[0] = 0; |
| return 0; |
| } |
| |
| int tfa98xx_set_stop_ctl(struct snd_kcontrol *kcontrol, |
| struct snd_ctl_elem_value *ucontrol) |
| { |
| struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| |
| pr_debug("%ld\n", ucontrol->value.integer.value[0]); |
| |
| if ((ucontrol->value.integer.value[0] != 0) && |
| !tfa98xx_is_pwdn(tfa98xx)) { |
| cancel_delayed_work_sync(&tfa98xx->delay_work); |
| tfa98xx_dsp_stop(tfa98xx); |
| } |
| |
| ucontrol->value.integer.value[0] = 0; |
| return 1; |
| } |
| |
| #define MAX_CONTROL_NAME 32 |
| |
| static char prof_name[MAX_CONTROL_NAME]; |
| static char vol_name[MAX_CONTROL_NAME]; |
| static char stop_name[MAX_CONTROL_NAME]; |
| |
| static struct snd_kcontrol_new tfa98xx_controls[] = { |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = prof_name, |
| .info = tfa98xx_info_profile_ctl, |
| .get = tfa98xx_get_profile_ctl, |
| .put = tfa98xx_set_profile_ctl, |
| }, |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = vol_name, |
| .info = tfa98xx_info_vol_ctl, |
| .get = tfa98xx_get_vol_ctl, |
| .put = tfa98xx_set_vol_ctl, |
| }, |
| { |
| .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
| .name = stop_name, |
| .info = snd_soc_info_bool_ext, |
| .get = tfa98xx_get_stop_ctl, |
| .put = tfa98xx_set_stop_ctl, |
| } |
| }; |
| |
| |
| static const struct snd_soc_dai_ops tfa98xx_ops = { |
| .hw_params = tfa98xx_hw_params, |
| .digital_mute = tfa98xx_digital_mute, |
| .set_fmt = tfa98xx_set_dai_fmt, |
| .set_sysclk = tfa98xx_set_dai_sysclk, |
| .startup = tfa98xx_startup, |
| .shutdown = tfa98xx_shutdown, |
| .prepare = tfa98xx_prepare, |
| .trigger = tfa98xx_trigger, |
| }; |
| |
| |
| static struct snd_soc_dai_driver tfa98xx_dai = { |
| .name = "tfa98xx_codec", |
| .playback = { |
| .stream_name = "TFA Playback", |
| .channels_min = 1, |
| .channels_max = 2, |
| .rates = TFA98XX_RATES, |
| .formats = TFA98XX_FORMATS,}, |
| .ops = &tfa98xx_ops, |
| .symmetric_rates = 1, |
| }; |
| |
| static int tfa98xx_probe(struct snd_soc_codec *codec) |
| { |
| struct tfa98xx *tfa98xx = snd_soc_codec_get_drvdata(codec); |
| int ret; |
| u16 rev; |
| |
| codec->control_data = tfa98xx->regmap; |
| tfa98xx->codec = codec; |
| codec->cache_bypass = true; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3,16,0) |
| ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_REGMAP); |
| if (ret != 0) { |
| dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); |
| return ret; |
| } |
| #endif |
| |
| /* |
| * some device require a dummy read in order to generate |
| * i2c clocks when accessing the device for the first time |
| */ |
| snd_soc_read(codec, TFA98XX_REVISIONNUMBER); |
| |
| rev = snd_soc_read(codec, TFA98XX_REVISIONNUMBER); |
| dev_info(codec->dev, "ID revision 0x%04x\n", rev); |
| tfa98xx->rev = rev & 0xff; |
| tfa98xx->subrev = (rev >> 8) & 0xff; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0) |
| snd_soc_dapm_new_controls(&codec->dapm, tfa98xx_dapm_widgets, |
| ARRAY_SIZE(tfa98xx_dapm_widgets)); |
| |
| snd_soc_dapm_add_routes(&codec->dapm, tfa98xx_dapm_routes, |
| ARRAY_SIZE(tfa98xx_dapm_routes)); |
| |
| snd_soc_dapm_new_widgets(&codec->dapm); |
| snd_soc_dapm_sync(&codec->dapm); |
| #endif |
| |
| ret = tfa98xx_cnt_loadfile(tfa98xx, 0); |
| if (ret) |
| return ret; |
| |
| tfa98xx->profile_current = 0; |
| tfa98xx->vstep_current = 0; |
| tfa98xx->profile_ctl = 0; |
| tfa98xx->vstep_ctl = 0; |
| |
| /* Overwrite kcontrol values that need container information */ |
| tfa98xx_controls[0].private_value = (unsigned long)tfa98xx->profiles, |
| tfa98xx_controls[1].private_value = (unsigned long)tfa98xx->profiles, |
| scnprintf(prof_name, MAX_CONTROL_NAME, "%s Profile", tfa98xx->fw.name); |
| scnprintf(vol_name, MAX_CONTROL_NAME, "%s Master Volume", |
| tfa98xx->fw.name); |
| scnprintf(stop_name, MAX_CONTROL_NAME, "%s Stop", tfa98xx->fw.name); |
| |
| snd_soc_add_codec_controls(codec, tfa98xx_controls, |
| ARRAY_SIZE(tfa98xx_controls)); |
| |
| dev_info(codec->dev, "tfa98xx codec registered"); |
| |
| return 0; |
| } |
| |
| static int tfa98xx_remove(struct snd_soc_codec *codec) |
| { |
| dev_info(codec->dev, "tfa98xx codec removed"); |
| return 0; |
| } |
| |
| static int tfa98xx_readable(struct snd_soc_codec *codec, unsigned int reg) |
| { |
| return tfa98xx_reg_readable[reg]; |
| } |
| |
| static struct snd_soc_codec_driver tfa98xx_soc_codec = { |
| .probe = tfa98xx_probe, |
| .remove = tfa98xx_remove, |
| .readable_register = tfa98xx_readable, |
| .reg_cache_size = TFA98XX_REG_CACHE_SIZE, |
| .reg_word_size = 2, |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(3,12,0) |
| .dapm_widgets = tfa98xx_dapm_widgets, |
| .num_dapm_widgets = ARRAY_SIZE(tfa98xx_dapm_widgets), |
| .dapm_routes = tfa98xx_dapm_routes, |
| .num_dapm_routes = ARRAY_SIZE(tfa98xx_dapm_routes), |
| #endif |
| }; |
| |
| static const struct regmap_config tfa98xx_regmap = { |
| .reg_bits = 8, |
| .val_bits = 16, |
| .max_register = TFA98XX_MAX_REGISTER, |
| .cache_type = REGCACHE_RBTREE, |
| }; |
| |
| static int tfa98xx_i2c_probe(struct i2c_client *i2c, |
| const struct i2c_device_id *id) |
| { |
| struct tfa98xx *tfa98xx; |
| int ret; |
| |
| if (!i2c_check_functionality(i2c->adapter, I2C_FUNC_I2C)) { |
| dev_err(&i2c->dev, "check_functionality failed\n"); |
| return -EIO; |
| } |
| |
| tfa98xx = devm_kzalloc(&i2c->dev, sizeof(struct tfa98xx), |
| GFP_KERNEL); |
| if (tfa98xx == NULL) |
| return -ENOMEM; |
| |
| tfa98xx->i2c = i2c; |
| tfa98xx->dsp_init = TFA98XX_DSP_INIT_PENDING; |
| |
| tfa98xx->regmap = devm_regmap_init_i2c(i2c, &tfa98xx_regmap); |
| if (IS_ERR(tfa98xx->regmap)) { |
| ret = PTR_ERR(tfa98xx->regmap); |
| dev_err(&i2c->dev, "Failed to allocate regmap: %d\n", ret); |
| return ret; |
| } |
| |
| i2c_set_clientdata(i2c, tfa98xx); |
| mutex_init(&tfa98xx->dsp_init_lock); |
| |
| /* work queue will be used to load DSP fw on first audio playback */ |
| tfa98xx->tfa98xx_wq = create_singlethread_workqueue("tfa98xx"); |
| if (tfa98xx->tfa98xx_wq == NULL) { |
| ret = -ENOMEM; |
| goto wq_fail; |
| } |
| |
| INIT_WORK(&tfa98xx->init_work, tfa98xx_dsp_init); |
| INIT_DELAYED_WORK(&tfa98xx->delay_work, tfa98xx_monitor); |
| |
| ret = gpio_request(tfa98xx->rst_gpio, "tfa reset gpio"); |
| if (ret < 0) { |
| pr_err("%s: tfa reset gpio_request failed: %d\n", |
| __func__, ret); |
| goto gpio_fail; |
| } |
| /* take IC out of reset, device registers are reset in codec probe |
| * through register write. |
| */ |
| gpio_direction_output(tfa98xx->rst_gpio, 0); |
| |
| /* register codec */ |
| ret = snd_soc_register_codec(&i2c->dev, &tfa98xx_soc_codec, |
| &tfa98xx_dai, 1); |
| if (ret < 0) { |
| pr_err("%s: Error registering tfa98xx codec", __func__); |
| goto codec_fail; |
| } |
| |
| pr_info("tfa98xx probed successfully!"); |
| |
| return ret; |
| |
| gpio_fail: |
| gpio_free(tfa98xx->rst_gpio); |
| codec_fail: |
| destroy_workqueue(tfa98xx->tfa98xx_wq); |
| wq_fail: |
| snd_soc_unregister_codec(&i2c->dev); |
| |
| return ret; |
| } |
| |
| static int tfa98xx_i2c_remove(struct i2c_client *client) |
| { |
| struct tfa98xx *tfa98xx = i2c_get_clientdata(client); |
| snd_soc_unregister_codec(&client->dev); |
| gpio_free(tfa98xx->rst_gpio); |
| destroy_workqueue(tfa98xx->tfa98xx_wq); |
| return 0; |
| } |
| |
| static const struct i2c_device_id tfa98xx_i2c_id[] = { |
| { "tfa98xx", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, tfa98xx_i2c_id); |
| |
| #ifdef CONFIG_OF |
| static struct of_device_id tfa98xx_match_tbl[] = { |
| { .compatible = "nxp,tfa98xx" }, |
| { }, |
| }; |
| MODULE_DEVICE_TABLE(of, tfa98xx_match_tbl); |
| #endif |
| |
| static struct i2c_driver tfa98xx_i2c_driver = { |
| .driver = { |
| .name = "tfa98xx", |
| .owner = THIS_MODULE, |
| .of_match_table = of_match_ptr(tfa98xx_match_tbl), |
| }, |
| .probe = tfa98xx_i2c_probe, |
| .remove = tfa98xx_i2c_remove, |
| .id_table = tfa98xx_i2c_id, |
| }; |
| |
| module_i2c_driver(tfa98xx_i2c_driver); |
| |
| MODULE_DESCRIPTION("ASoC tfa98xx codec driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("NXP"); |