* From dagu-s-oss * Run clang-format on source files Change-Id: I04f86a89df0d3af0ecb3eecd7ec75fa522d44506
3843 lines
113 KiB
C
3843 lines
113 KiB
C
/*
|
|
* cs35l41.c -- CS35l41 ALSA SoC audio driver
|
|
*
|
|
* Copyright 2018 Cirrus Logic, Inc.
|
|
*
|
|
* Author: David Rhodes <david.rhodes@cirrus.com>
|
|
* Brian Austin <brian.austin@cirrus.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
*/
|
|
#define DEBUG
|
|
|
|
//#define BRINGUP_IRQ_VERIFY
|
|
#define FAST_SWITCH_WORKAROUND
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/version.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/regmap.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <linux/gpio.h>
|
|
#include <sound/initval.h>
|
|
#include <sound/tlv.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/err.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/timekeeping.h>
|
|
|
|
#include "wm_adsp.h"
|
|
#include "cs35l41.h"
|
|
#include <sound/cs35l41_k81.h>
|
|
|
|
static const char *const cs35l41_supplies[] = {
|
|
"VA",
|
|
"VP",
|
|
};
|
|
|
|
struct cs35l41_pll_sysclk_config {
|
|
int freq;
|
|
int clk_cfg;
|
|
};
|
|
|
|
static const struct cs35l41_pll_sysclk_config cs35l41_pll_sysclk[] = {
|
|
{ 32768, 0x00 }, { 8000, 0x01 }, { 11025, 0x02 },
|
|
{ 12000, 0x03 }, { 16000, 0x04 }, { 22050, 0x05 },
|
|
{ 24000, 0x06 }, { 32000, 0x07 }, { 44100, 0x08 },
|
|
{ 48000, 0x09 }, { 88200, 0x0A }, { 96000, 0x0B },
|
|
{ 128000, 0x0C }, { 176400, 0x0D }, { 192000, 0x0E },
|
|
{ 256000, 0x0F }, { 352800, 0x10 }, { 384000, 0x11 },
|
|
{ 512000, 0x12 }, { 705600, 0x13 }, { 750000, 0x14 },
|
|
{ 768000, 0x15 }, { 1000000, 0x16 }, { 1024000, 0x17 },
|
|
{ 1200000, 0x18 }, { 1411200, 0x19 }, { 1500000, 0x1A },
|
|
{ 1536000, 0x1B }, { 2000000, 0x1C }, { 2048000, 0x1D },
|
|
{ 2400000, 0x1E }, { 2822400, 0x1F }, { 3000000, 0x20 },
|
|
{ 3072000, 0x21 }, { 3200000, 0x22 }, { 4000000, 0x23 },
|
|
{ 4096000, 0x24 }, { 4800000, 0x25 }, { 5644800, 0x26 },
|
|
{ 6000000, 0x27 }, { 6144000, 0x28 }, { 6250000, 0x29 },
|
|
{ 6400000, 0x2A }, { 6500000, 0x2B }, { 6750000, 0x2C },
|
|
{ 7526400, 0x2D }, { 8000000, 0x2E }, { 8192000, 0x2F },
|
|
{ 9600000, 0x30 }, { 11289600, 0x31 }, { 12000000, 0x32 },
|
|
{ 12288000, 0x33 }, { 12500000, 0x34 }, { 12800000, 0x35 },
|
|
{ 13000000, 0x36 }, { 13500000, 0x37 }, { 19200000, 0x38 },
|
|
{ 22579200, 0x39 }, { 24000000, 0x3A }, { 24576000, 0x3B },
|
|
{ 25000000, 0x3C }, { 25600000, 0x3D }, { 26000000, 0x3E },
|
|
{ 27000000, 0x3F },
|
|
};
|
|
|
|
static const unsigned char cs35l41_bst_k1_table[4][5] = {
|
|
{ 0x24, 0x32, 0x32, 0x4F, 0x57 },
|
|
{ 0x24, 0x32, 0x32, 0x4F, 0x57 },
|
|
{ 0x40, 0x32, 0x32, 0x4F, 0x57 },
|
|
{ 0x40, 0x32, 0x32, 0x4F, 0x57 }
|
|
};
|
|
|
|
static const unsigned char cs35l41_bst_k2_table[4][5] = {
|
|
{ 0x24, 0x49, 0x66, 0xA3, 0xEA },
|
|
{ 0x24, 0x49, 0x66, 0xA3, 0xEA },
|
|
{ 0x48, 0x49, 0x66, 0xA3, 0xEA },
|
|
{ 0x48, 0x49, 0x66, 0xA3, 0xEA }
|
|
};
|
|
|
|
static const unsigned char cs35l41_bst_slope_table[4] = { 0x75, 0x6B, 0x3B,
|
|
0x28 };
|
|
|
|
static int cs35l41_enter_hibernate(struct cs35l41_private *cs35l41);
|
|
static int cs35l41_exit_hibernate(struct cs35l41_private *cs35l41);
|
|
static int cs35l41_restore(struct cs35l41_private *cs35l41);
|
|
static int cs35l41_component_set_sysclk(struct snd_soc_component *component,
|
|
int clk_id, int source,
|
|
unsigned int freq, int dir);
|
|
|
|
static int cs35l41_dsp_power_ev(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_dapm_to_component(w->dapm);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
dev_dbg(cs35l41->dev, "%s: event = 0x%x\n", __func__, event);
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
if (cs35l41->halo_booted == false)
|
|
wm_adsp_early_event(w, kcontrol, event);
|
|
else
|
|
cs35l41->dsp.booted = true;
|
|
|
|
return 0;
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
if (cs35l41->halo_booted == false) {
|
|
cancel_delayed_work(&cs35l41->hb_work);
|
|
mutex_lock(&cs35l41->hb_lock);
|
|
cs35l41_exit_hibernate(cs35l41);
|
|
mutex_unlock(&cs35l41->hb_lock);
|
|
|
|
if (cs35l41->amp_hibernate !=
|
|
CS35L41_HIBERNATE_INCOMPATIBLE)
|
|
cs35l41->amp_hibernate =
|
|
CS35L41_HIBERNATE_NOT_LOADED;
|
|
|
|
wm_adsp_early_event(w, kcontrol, event);
|
|
wm_adsp_event(w, kcontrol, event);
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int cs35l41_set_csplmboxcmd(struct cs35l41_private *cs35l41,
|
|
enum cs35l41_cspl_mboxcmd cmd);
|
|
|
|
static int cs35l41_dsp_load_ev(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_dapm_to_component(w->dapm);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
enum cs35l41_cspl_mboxcmd mboxcmd = CSPL_MBOX_CMD_NONE;
|
|
enum cs35l41_cspl_mboxstate fw_status = CSPL_MBOX_STS_RUNNING;
|
|
int ret = 0;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: event = 0x%x\n", __func__, event);
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
if (cs35l41->halo_booted == false) {
|
|
wm_adsp_event(w, kcontrol, event);
|
|
cs35l41->halo_booted = true;
|
|
}
|
|
if (cs35l41->dsp.running) {
|
|
regmap_read(cs35l41->regmap, CS35L41_DSP_MBOX_2,
|
|
(unsigned int *)&fw_status);
|
|
switch (fw_status) {
|
|
case CSPL_MBOX_STS_RDY_FOR_REINIT:
|
|
mboxcmd = CSPL_MBOX_CMD_REINIT;
|
|
break;
|
|
case CSPL_MBOX_STS_PAUSED:
|
|
mboxcmd = CSPL_MBOX_CMD_RESUME;
|
|
break;
|
|
case CSPL_MBOX_STS_RUNNING:
|
|
/*
|
|
* First time playing audio
|
|
* means fw_status is running
|
|
*/
|
|
mboxcmd = CSPL_MBOX_CMD_RESUME;
|
|
break;
|
|
default:
|
|
dev_err(cs35l41->dev,
|
|
"Firmware status is invalid(%u)\n",
|
|
fw_status);
|
|
break;
|
|
}
|
|
ret = cs35l41_set_csplmboxcmd(cs35l41, mboxcmd);
|
|
}
|
|
break;
|
|
case SND_SOC_DAPM_PRE_PMD:
|
|
/*
|
|
* Workaround for PB 5.41.6 FW power consumption issue.
|
|
* FW sets bit FILT_GLOBAL_OVR, so it should be cleared
|
|
* after FW boot up
|
|
*/
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_CTRL_OVRRIDE,
|
|
CS35L41_FILT_GLOBAL_OVR_MASK, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_halo_booted_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
dev_dbg(cs35l41->dev, "%s: get=%d\n", __func__, cs35l41->halo_booted);
|
|
|
|
ucontrol->value.integer.value[0] = cs35l41->halo_booted;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_halo_booted_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
dev_dbg(cs35l41->dev, "%s: put=%ld\n", __func__,
|
|
ucontrol->value.integer.value[0]);
|
|
|
|
cs35l41->halo_booted = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_force_int_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = cs35l41->force_int;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_force_int_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
bool force_int_changed =
|
|
(cs35l41->force_int != (bool)ucontrol->value.integer.value[0]);
|
|
|
|
mutex_lock(&cs35l41->force_int_lock);
|
|
|
|
cs35l41->force_int = ucontrol->value.integer.value[0];
|
|
|
|
if (force_int_changed) {
|
|
if (cs35l41->force_int) {
|
|
disable_irq(cs35l41->irq);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
|
|
CS35L41_INT1_MASK_FORCE);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_FRC1, 1);
|
|
} else {
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_FRC1, 0);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
|
|
CS35L41_INT1_MASK_DEFAULT);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1, 1);
|
|
enable_irq(cs35l41->irq);
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&cs35l41->force_int_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_ccm_reset_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_ccm_reset_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
unsigned int val = 0;
|
|
int ret = 0;
|
|
|
|
val = ucontrol->value.integer.value[0];
|
|
|
|
if (val) {
|
|
ret = regmap_update_bits(cs35l41->regmap, CS35L41_DSP_CLK_CTRL,
|
|
0x3, 0x2);
|
|
ret = regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_DSP1_CCM_CORE_CTRL,
|
|
CS35L41_HALO_CORE_RESET,
|
|
CS35L41_HALO_CORE_RESET);
|
|
ret = regmap_update_bits(cs35l41->regmap, CS35L41_DSP_CLK_CTRL,
|
|
0x3, 0x3);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_hibernate_force_wake_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = cs35l41->hibernate_force_wake;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_hibernate_force_wake_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
bool valid_transition = cs35l41->hibernate_force_wake !=
|
|
ucontrol->value.integer.value[0];
|
|
unsigned int amp_active;
|
|
|
|
regmap_read(cs35l41->regmap, CS35L41_PWR_CTRL1, &_active);
|
|
|
|
if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_AWAKE ||
|
|
(cs35l41->amp_hibernate == CS35L41_HIBERNATE_NOT_LOADED &&
|
|
cs35l41->dsp.running)) {
|
|
cs35l41->hibernate_force_wake =
|
|
ucontrol->value.integer.value[0];
|
|
if (!cs35l41->hibernate_force_wake && valid_transition &&
|
|
!(amp_active & CS35L41_GLOBAL_EN_MASK)) {
|
|
/* return to standby */
|
|
queue_delayed_work(cs35l41->wq, &cs35l41->hb_work,
|
|
msecs_to_jiffies(100));
|
|
}
|
|
} else if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_STANDBY) {
|
|
cs35l41->hibernate_force_wake =
|
|
ucontrol->value.integer.value[0];
|
|
if (cs35l41->hibernate_force_wake && valid_transition) {
|
|
/* wake from standby */
|
|
cancel_delayed_work(&cs35l41->hb_work);
|
|
mutex_lock(&cs35l41->hb_lock);
|
|
cs35l41_exit_hibernate(cs35l41);
|
|
mutex_unlock(&cs35l41->hb_lock);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *cs35l41_fast_switch_text[] = {
|
|
"fast_switch1.txt", "fast_switch2.txt", "fast_switch3.txt",
|
|
"fast_switch4.txt", "fast_switch5.txt",
|
|
};
|
|
|
|
static int cs35l41_fast_switch_en_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = cs35l41->fast_switch_en;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_do_fast_switch(struct cs35l41_private *cs35l41)
|
|
{
|
|
char val_str[CS35L41_BUFSIZE];
|
|
const char *fw_name;
|
|
const struct firmware *fw;
|
|
int ret;
|
|
unsigned int i, j, k;
|
|
s32 data_ctl_len, val;
|
|
__be32 *data_ctl_buf, cmd_ctl, st_ctl;
|
|
bool fw_running = false;
|
|
|
|
data_ctl_buf = NULL;
|
|
|
|
fw_name = cs35l41->fast_switch_names[cs35l41->fast_switch_file_idx];
|
|
dev_dbg(cs35l41->dev, "fw_name:%s\n", fw_name);
|
|
ret = request_firmware(&fw, fw_name, cs35l41->dev);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Failed to request firmware:%s\n",
|
|
fw_name);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Parse number of data in file */
|
|
for (i = 0, j = 0; (char)fw->data[i] != ','; i++) {
|
|
if ((char)fw->data[i] == ' ') {
|
|
/* Skip white space */
|
|
} else {
|
|
/* fw->data[i] must be numerical digit */
|
|
if (j < CS35L41_BUFSIZE - 1) {
|
|
val_str[j] = fw->data[i];
|
|
j++;
|
|
} else {
|
|
dev_err(cs35l41->dev, "Invalid input\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
i++; /* points to beginning of next number */
|
|
val_str[j] = '\0';
|
|
ret = kstrtos32(val_str, 10, &data_ctl_len);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "kstrtos32 failed (%d) val_str:%s\n", ret,
|
|
val_str);
|
|
goto exit;
|
|
}
|
|
|
|
dev_dbg(cs35l41->dev, "data_ctl_len:%u\n", data_ctl_len);
|
|
|
|
data_ctl_buf = kcalloc(1, data_ctl_len * sizeof(s32), GFP_KERNEL);
|
|
if (!data_ctl_buf) {
|
|
ret = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
data_ctl_buf[0] = cpu_to_be32(data_ctl_len);
|
|
|
|
/* i continues from end of previous loop */
|
|
for (j = 0, k = 1; i <= fw->size; i++) {
|
|
if (i == fw->size || (char)fw->data[i] == ',') {
|
|
/*
|
|
* Reached end of parameter
|
|
* delimited either by ',' or end of file
|
|
* Parse number and write parameter
|
|
*/
|
|
val_str[j] = '\0';
|
|
ret = kstrtos32(val_str, 10, &val);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"kstrtos32 failed (%d) val_str:%s\n",
|
|
ret, val_str);
|
|
goto exit;
|
|
}
|
|
data_ctl_buf[k] = cpu_to_be32(val);
|
|
j = 0;
|
|
k++;
|
|
} else if ((char)fw->data[i] == ' ') {
|
|
/* Skip white space */
|
|
} else {
|
|
/* fw->data[i] must be numerical digit */
|
|
if (j < CS35L41_BUFSIZE - 1) {
|
|
val_str[j] = fw->data[i];
|
|
j++;
|
|
} else {
|
|
dev_err(cs35l41->dev, "Invalid input\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
wm_adsp_write_ctl(&cs35l41->dsp, "CSPL_UPDATE_PARAMS_CONFIG",
|
|
data_ctl_buf, data_ctl_len * sizeof(s32));
|
|
|
|
dev_dbg(cs35l41->dev, "Wrote %u reg for CSPL_UPDATE_PARAMS_CONFIG\n",
|
|
data_ctl_len);
|
|
|
|
#ifdef DEBUG
|
|
wm_adsp_read_ctl(&cs35l41->dsp, "CSPL_UPDATE_PARAMS_CONFIG",
|
|
data_ctl_buf, data_ctl_len * sizeof(s32));
|
|
dev_dbg(cs35l41->dev, "read CSPL_UPDATE_PARAMS_CONFIG:\n");
|
|
for (i = 0; i < data_ctl_len; i++)
|
|
dev_dbg(cs35l41->dev, "%u\n", be32_to_cpu(data_ctl_buf[i]));
|
|
#endif
|
|
cmd_ctl = cpu_to_be32(CSPL_CMD_UPDATE_PARAM);
|
|
wm_adsp_write_ctl(&cs35l41->dsp, "CSPL_COMMAND", &cmd_ctl, sizeof(s32));
|
|
|
|
/* Verify CSPL COMMAND */
|
|
for (i = 0; i < 5; i++) {
|
|
wm_adsp_read_ctl(&cs35l41->dsp, "CSPL_STATE", &st_ctl,
|
|
sizeof(s32));
|
|
if (be32_to_cpu(st_ctl) == CSPL_ST_RUNNING) {
|
|
dev_dbg(cs35l41->dev,
|
|
"CSPL STATE == RUNNING (%u attempt)\n", i);
|
|
fw_running = true;
|
|
break;
|
|
}
|
|
|
|
usleep_range(100, 110);
|
|
}
|
|
|
|
if (!fw_running) {
|
|
dev_err(cs35l41->dev, "CSPL_STATE (%d) is not running\n",
|
|
st_ctl);
|
|
ret = -1;
|
|
goto exit;
|
|
}
|
|
exit:
|
|
kfree(data_ctl_buf);
|
|
release_firmware(fw);
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_fast_switch_en_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
int ret = 0;
|
|
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
#if !defined(FAST_SWITCH_WORKAROUND)
|
|
if (!cs35l41->fast_switch_en && ucontrol->value.integer.value[0])
|
|
/*
|
|
* Rising on fast switch enable
|
|
* Perform fast use case switching
|
|
*/
|
|
ret = cs35l41_do_fast_switch(cs35l41);
|
|
#endif
|
|
|
|
cs35l41->fast_switch_en = ucontrol->value.integer.value[0];
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_fast_switch_file_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
struct soc_enum *soc_enum;
|
|
unsigned int i = ucontrol->value.enumerated.item[0];
|
|
|
|
soc_enum = (struct soc_enum *)kcontrol->private_value;
|
|
|
|
if (i >= soc_enum->items) {
|
|
dev_err(cs35l41->dev, "Invalid mixer input (%u)\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(FAST_SWITCH_WORKAROUND)
|
|
if (!cs35l41->dsp.running) {
|
|
dev_err(cs35l41->dev, "DSP not running\n");
|
|
return 0;
|
|
}
|
|
|
|
if ((i != cs35l41->fast_switch_file_idx) && cs35l41->fast_switch_en) {
|
|
int ret;
|
|
|
|
cs35l41->fast_switch_file_idx = i;
|
|
ret = cs35l41_do_fast_switch(cs35l41);
|
|
dev_dbg(cs35l41->dev, "%s: fast switch %s\n", __func__,
|
|
ret ? "fail" : "success");
|
|
}
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_fast_switch_file_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.enumerated.item[0] = cs35l41->fast_switch_file_idx;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const DECLARE_TLV_DB_RANGE(dig_vol_tlv, 0, 0,
|
|
TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 1), 1,
|
|
CS35L41_MAX_PCM_VOL,
|
|
TLV_DB_MINMAX_ITEM(-10200, 1200));
|
|
static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1);
|
|
|
|
static const struct snd_kcontrol_new dre_ctrl =
|
|
SOC_DAPM_SINGLE("DRE Switch", CS35L41_PWR_CTRL3, 20, 1, 0);
|
|
|
|
static const struct snd_kcontrol_new vbstmon_out_ctrl =
|
|
SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0);
|
|
|
|
static const char *const cs35l41_pcm_sftramp_text[] = { "Off", ".5ms", "1ms",
|
|
"2ms", "4ms", "8ms",
|
|
"15ms", "30ms" };
|
|
|
|
static SOC_ENUM_SINGLE_DECL(pcm_sft_ramp, CS35L41_AMP_DIG_VOL_CTRL, 0,
|
|
cs35l41_pcm_sftramp_text);
|
|
|
|
static int cs35l41_reload_tuning_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = cs35l41->reload_tuning;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_reload_tuning_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
cs35l41->reload_tuning = ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool cs35l41_is_csplmboxsts_correct(enum cs35l41_cspl_mboxcmd cmd,
|
|
enum cs35l41_cspl_mboxstate sts)
|
|
{
|
|
switch (cmd) {
|
|
case CSPL_MBOX_CMD_NONE:
|
|
case CSPL_MBOX_CMD_UNKNOWN_CMD:
|
|
return true;
|
|
case CSPL_MBOX_CMD_PAUSE:
|
|
return (sts == CSPL_MBOX_STS_PAUSED);
|
|
case CSPL_MBOX_CMD_RESUME:
|
|
return (sts == CSPL_MBOX_STS_RUNNING);
|
|
case CSPL_MBOX_CMD_REINIT:
|
|
return (sts == CSPL_MBOX_STS_RUNNING);
|
|
case CSPL_MBOX_CMD_STOP_PRE_REINIT:
|
|
return (sts == CSPL_MBOX_STS_RDY_FOR_REINIT);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static int cs35l41_set_csplmboxcmd(struct cs35l41_private *cs35l41,
|
|
enum cs35l41_cspl_mboxcmd cmd)
|
|
{
|
|
int ret = 0;
|
|
unsigned int sts, i;
|
|
bool ack = false;
|
|
|
|
/* Reset DSP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ2_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT);
|
|
|
|
/* Reset AP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_FW_SHIFT);
|
|
|
|
/*
|
|
* Set mailbox cmd
|
|
*/
|
|
/* Unmask DSP INT */
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ2_MASK2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT, 0);
|
|
regmap_write(cs35l41->regmap, CS35L41_CSPL_MBOX_CMD_DRV, cmd);
|
|
|
|
/* Poll for DSP ACK */
|
|
for (i = 0; i < 5; i++) {
|
|
usleep_range(1000, 1010);
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS2, &sts);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "regmap_read failed (%d)\n", ret);
|
|
continue;
|
|
}
|
|
if (sts & (1 << CS35L41_CSPL_MBOX_CMD_FW_SHIFT)) {
|
|
dev_dbg(cs35l41->dev,
|
|
"%u: Received ACK in EINT for mbox cmd (%d)\n",
|
|
i, cmd);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_FW_SHIFT);
|
|
ack = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ack) {
|
|
dev_err(cs35l41->dev,
|
|
"Timeout waiting for DSP to set mbox cmd\n");
|
|
ret = -ETIMEDOUT;
|
|
}
|
|
|
|
/* Mask DSP INT */
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ2_MASK2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT);
|
|
|
|
if (regmap_read(cs35l41->regmap, CS35L41_CSPL_MBOX_STS, &sts) < 0) {
|
|
dev_err(cs35l41->dev, "Failed to read %u\n",
|
|
CS35L41_CSPL_MBOX_STS);
|
|
ret = -EACCES;
|
|
}
|
|
|
|
if (!cs35l41_is_csplmboxsts_correct(cmd,
|
|
(enum cs35l41_cspl_mboxstate)sts)) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to set mailbox(cmd: %u, sts: %u)\n", cmd, sts);
|
|
ret = -ENOMSG;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void cs35l41_abort_ramp(struct cs35l41_private *cs35l41)
|
|
{
|
|
if (!work_busy(&cs35l41->vol_ctl.ramp_work))
|
|
return;
|
|
atomic_set(&cs35l41->vol_ctl.ramp_abort, 1);
|
|
cancel_work_sync(&cs35l41->vol_ctl.ramp_work);
|
|
flush_workqueue(cs35l41->vol_ctl.ramp_wq);
|
|
atomic_set(&cs35l41->vol_ctl.ramp_abort, 0);
|
|
}
|
|
|
|
static int cs35l41_put_output_dev(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
struct soc_enum *soc_enum;
|
|
unsigned int i = ucontrol->value.enumerated.item[0];
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
dev_dbg(cs35l41->dev, "%s: output_dev = %u\n", __func__, i);
|
|
|
|
soc_enum = (struct soc_enum *)kcontrol->private_value;
|
|
|
|
if (i >= soc_enum->items) {
|
|
dev_err(component->dev, "Invalid mixer input (%u)\n", i);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.playback) &&
|
|
cs35l41->vol_ctl.auto_ramp_timeout > 0 &&
|
|
cs35l41->vol_ctl.output_dev == CS35L41_OUTPUT_DEV_RCV &&
|
|
soc_enum->values[i] == CS35L41_OUTPUT_DEV_SPK) {
|
|
/*
|
|
* While audio is playing,
|
|
* auto volume ramp is enabled,
|
|
* output device changes from RCV to SPK.
|
|
* In this case, perform volume ramp.
|
|
*/
|
|
cs35l41_abort_ramp(cs35l41);
|
|
queue_work(cs35l41->vol_ctl.ramp_wq,
|
|
&cs35l41->vol_ctl.ramp_work);
|
|
}
|
|
|
|
cs35l41->vol_ctl.output_dev = soc_enum->values[i];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_output_dev(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
int ret = 0;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.enumerated.item[0] = cs35l41->vol_ctl.output_dev;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const char *const channel_swap_texts[] = { "Off", "On" };
|
|
static SOC_ENUM_SINGLE_DECL(channel_swap_enum, SND_SOC_NOPM, 0,
|
|
channel_swap_texts);
|
|
|
|
static const char *virt_text[] = { "None", "Ref" };
|
|
static SOC_ENUM_SINGLE_DECL(virt_enum, SND_SOC_NOPM, 2, virt_text);
|
|
|
|
static const struct snd_kcontrol_new virt_mux =
|
|
SOC_DAPM_ENUM("Virt Connect", virt_enum);
|
|
|
|
static const char *const cs35l41_pcm_source_texts[] = { "None", "ASP", "DSP" };
|
|
static const unsigned int cs35l41_pcm_source_values[] = { 0x00, 0x08, 0x32 };
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_pcm_source_enum, CS35L41_DAC_PCM1_SRC,
|
|
0, CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_pcm_source_texts,
|
|
cs35l41_pcm_source_values);
|
|
|
|
static const struct snd_kcontrol_new pcm_source_mux =
|
|
SOC_DAPM_ENUM("PCM Source", cs35l41_pcm_source_enum);
|
|
|
|
static const char *const cs35l41_tx_input_texts[] = {
|
|
"Zero", "ASPRX1", "ASPRX2", "VMON", "IMON",
|
|
"VPMON", "VBSTMON", "DSPTX1", "DSPTX2"
|
|
};
|
|
static const unsigned int cs35l41_tx_input_values[] = {
|
|
0x00,
|
|
CS35L41_INPUT_SRC_ASPRX1,
|
|
CS35L41_INPUT_SRC_ASPRX2,
|
|
CS35L41_INPUT_SRC_VMON,
|
|
CS35L41_INPUT_SRC_IMON,
|
|
CS35L41_INPUT_SRC_VPMON,
|
|
CS35L41_INPUT_SRC_VBSTMON,
|
|
CS35L41_INPUT_DSP_TX1,
|
|
CS35L41_INPUT_DSP_TX2
|
|
};
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx1_enum, CS35L41_ASP_TX1_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new asp_tx1_mux =
|
|
SOC_DAPM_ENUM("ASPTX1 SRC", cs35l41_asptx1_enum);
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx2_enum, CS35L41_ASP_TX2_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new asp_tx2_mux =
|
|
SOC_DAPM_ENUM("ASPTX2 SRC", cs35l41_asptx2_enum);
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx3_enum, CS35L41_ASP_TX3_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new asp_tx3_mux =
|
|
SOC_DAPM_ENUM("ASPTX3 SRC", cs35l41_asptx3_enum);
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_asptx4_enum, CS35L41_ASP_TX4_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new asp_tx4_mux =
|
|
SOC_DAPM_ENUM("ASPTX4 SRC", cs35l41_asptx4_enum);
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_dsprx1_enum, CS35L41_DSP1_RX1_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new dsp_rx1_mux =
|
|
SOC_DAPM_ENUM("DSPRX1 SRC", cs35l41_dsprx1_enum);
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_dsprx2_enum, CS35L41_DSP1_RX2_SRC, 0,
|
|
CS35L41_ASP_SOURCE_MASK,
|
|
cs35l41_tx_input_texts,
|
|
cs35l41_tx_input_values);
|
|
|
|
static const struct snd_kcontrol_new dsp_rx2_mux =
|
|
SOC_DAPM_ENUM("DSPRX2 SRC", cs35l41_dsprx2_enum);
|
|
|
|
static void cs35l41_set_vol(int vol, struct cs35l41_private *cs35l41)
|
|
{
|
|
unsigned int val;
|
|
int ret;
|
|
|
|
mutex_lock(&cs35l41->vol_ctl.vol_mutex);
|
|
|
|
if (vol < 0 || vol > CS35L41_MAX_PCM_VOL) {
|
|
dev_err(cs35l41->dev, "Invalid PCM VOLUME %d\n", vol);
|
|
goto exit;
|
|
}
|
|
|
|
if (vol < CS35L41_ZERO_PCM_VOL)
|
|
/* PCM Volume is attenuation */
|
|
val = (unsigned int)(vol + CS35L41_AMP_PCM_VOL_MUTE);
|
|
else
|
|
/* CS35L41_ZERO_PCM_VOL <= dig_vol <= CS35L41_MAX_PCM_VOL */
|
|
val = (unsigned int)(vol - CS35L41_ZERO_PCM_VOL);
|
|
ret = regmap_update_bits(cs35l41->regmap, CS35L41_AMP_DIG_VOL_CTRL,
|
|
CS35L41_AMP_PCM_VOL_MASK,
|
|
val << CS35L41_AMP_PCM_VOL_SHIFT);
|
|
if (ret < 0)
|
|
dev_err(cs35l41->dev, "Failed to set PCM VOLUME %d\n", ret);
|
|
|
|
exit:
|
|
mutex_unlock(&cs35l41->vol_ctl.vol_mutex);
|
|
}
|
|
|
|
static int cs35l41_vol_ramp0(struct cs35l41_private *cs35l41, long final_x,
|
|
long init_y, long final_y)
|
|
{
|
|
long curr_x = 0;
|
|
long curr_y = init_y;
|
|
long delta_x = final_x;
|
|
long delta_y = final_y - init_y;
|
|
long step_x, step_y;
|
|
int ret = 0;
|
|
|
|
if (final_x < 0 || final_y < 0 || init_y < 0) {
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
|
|
if (final_y <= init_y) {
|
|
dev_info(cs35l41->dev, "Vol ramp slope is not positive\n");
|
|
cs35l41_set_vol((int)init_y, cs35l41);
|
|
usleep_range(final_x, final_x + 1);
|
|
cs35l41_set_vol((int)final_y, cs35l41);
|
|
goto exit;
|
|
}
|
|
|
|
step_y = 1; /* 1/8 dB, min IC supported step */
|
|
step_x = delta_x / delta_y; /* in micro-second */
|
|
if (step_x == 0)
|
|
/* Take care of case where delta_x < delta_y */
|
|
step_x = 1;
|
|
|
|
dev_dbg(cs35l41->dev, "vol ramp delta x:%ld delta y:%ld step x:%ld\n",
|
|
delta_x, delta_y, step_x);
|
|
while (1) {
|
|
if (atomic_read(&cs35l41->vol_ctl.ramp_abort)) {
|
|
ret = -EINTR;
|
|
goto exit;
|
|
}
|
|
if (curr_x >= final_x) {
|
|
/* Delay is complete */
|
|
cs35l41_set_vol((int)final_y, cs35l41);
|
|
break;
|
|
}
|
|
if (curr_y == final_y) {
|
|
/* Volume ramp is completed */
|
|
usleep_range(final_x - curr_x, final_x - curr_x + 1);
|
|
break;
|
|
}
|
|
cs35l41_set_vol((int)curr_y, cs35l41);
|
|
curr_y += step_y;
|
|
curr_x += step_x;
|
|
usleep_range(step_x, step_x + 1);
|
|
}
|
|
exit:
|
|
return ret;
|
|
}
|
|
|
|
static void cs35l41_vol_ramp(struct work_struct *wk)
|
|
{
|
|
struct cs35l41_vol_ctl *vol_ctl;
|
|
struct cs35l41_private *cs35l41;
|
|
long final_x_knee, final_x_end;
|
|
long init_y_knee, init_y_end, final_y_end;
|
|
int ret = 0;
|
|
|
|
vol_ctl = container_of(wk, struct cs35l41_vol_ctl, ramp_work);
|
|
cs35l41 = container_of(vol_ctl, struct cs35l41_private, vol_ctl);
|
|
dev_dbg(cs35l41->dev, "%s: dig_vol = %d\n", __func__,
|
|
cs35l41->vol_ctl.dig_vol);
|
|
|
|
atomic_set(&cs35l41->vol_ctl.vol_ramp, 1);
|
|
/*
|
|
* vol_ramp must be true at this point,
|
|
* which guarantee ramp_init_att, ramp_knee_att,
|
|
* ramp_knee_time, ramp_end_time cannot be changed
|
|
*/
|
|
final_x_knee = (long)(cs35l41->vol_ctl.ramp_knee_time) * 1000; /* us */
|
|
final_x_end = (long)(cs35l41->vol_ctl.ramp_end_time) * 1000;
|
|
/* 1/8 dB minimum step */
|
|
init_y_knee = (long)(cs35l41->vol_ctl.dig_vol -
|
|
cs35l41->vol_ctl.ramp_init_att * 8);
|
|
if (init_y_knee < 0)
|
|
/* Hit floor */
|
|
init_y_knee = 0;
|
|
init_y_end = (long)(cs35l41->vol_ctl.dig_vol -
|
|
cs35l41->vol_ctl.ramp_knee_att * 8);
|
|
if (init_y_end < 0)
|
|
/* Hit floor */
|
|
init_y_end = 0;
|
|
final_y_end = (long)cs35l41->vol_ctl.dig_vol;
|
|
if (final_y_end < 0)
|
|
/* Hit floor */
|
|
final_y_end = 0;
|
|
ret = cs35l41_vol_ramp0(cs35l41, final_x_knee, init_y_knee, init_y_end);
|
|
if (ret == -EINTR) {
|
|
/* Abort ramp */
|
|
cs35l41_set_vol(cs35l41->vol_ctl.dig_vol, cs35l41);
|
|
goto exit;
|
|
}
|
|
ret = cs35l41_vol_ramp0(cs35l41, final_x_end, init_y_end, final_y_end);
|
|
if (ret == -EINTR) {
|
|
/* Abort ramp */
|
|
cs35l41_set_vol(cs35l41->vol_ctl.dig_vol, cs35l41);
|
|
goto exit;
|
|
}
|
|
exit:
|
|
atomic_set(&cs35l41->vol_ctl.vol_ramp, 0);
|
|
/* Make manual ramp one-shot */
|
|
atomic_set(&cs35l41->vol_ctl.manual_ramp, 0);
|
|
}
|
|
|
|
static int cs35l41_get_vol(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = (long)cs35l41->vol_ctl.dig_vol;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_vol(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
int ret = 0;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_PCM_VOL)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.vol_ramp) == 0) {
|
|
cs35l41->vol_ctl.dig_vol =
|
|
(int)ucontrol->value.integer.value[0];
|
|
cs35l41_set_vol(cs35l41->vol_ctl.dig_vol, cs35l41);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_get_ramp_status(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
(long)atomic_read(&cs35l41->vol_ctl.vol_ramp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_ramp_status(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
dev_info(cs35l41->dev, "Volume ramp status cannot be set\n");
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_manual_ramp(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
(long)atomic_read(&cs35l41->vol_ctl.manual_ramp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_manual_ramp(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > 1)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.manual_ramp) == 0 &&
|
|
ucontrol->value.integer.value[0] == 1) {
|
|
/* Rising edge */
|
|
if (atomic_read(&cs35l41->vol_ctl.playback)) {
|
|
/* Stop existing ramp and start new ramp */
|
|
cs35l41_abort_ramp(cs35l41);
|
|
queue_work(cs35l41->vol_ctl.ramp_wq,
|
|
&cs35l41->vol_ctl.ramp_work);
|
|
}
|
|
/*
|
|
* In else case,
|
|
* let DAPM event handle ramp on playback start.
|
|
*/
|
|
} else if (atomic_read(&cs35l41->vol_ctl.manual_ramp) == 1 &&
|
|
ucontrol->value.integer.value[0] == 0) {
|
|
/* Falling edge */
|
|
if (atomic_read(&cs35l41->vol_ctl.playback))
|
|
/* Stop existing ramp */
|
|
cs35l41_abort_ramp(cs35l41);
|
|
}
|
|
atomic_set(&cs35l41->vol_ctl.manual_ramp,
|
|
ucontrol->value.integer.value[0]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_init_attenuation(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = (long)cs35l41->vol_ctl.ramp_init_att;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_init_attenuation(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_VOL_ATT)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.vol_ramp) == 0)
|
|
cs35l41->vol_ctl.ramp_init_att =
|
|
(int)ucontrol->value.integer.value[0];
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_knee_attenuation(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = (long)cs35l41->vol_ctl.ramp_knee_att;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_knee_attenuation(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_VOL_ATT)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.vol_ramp) == 0)
|
|
cs35l41->vol_ctl.ramp_knee_att =
|
|
(int)ucontrol->value.integer.value[0];
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_ramp_end_time(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] = (long)cs35l41->vol_ctl.ramp_end_time;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_ramp_end_time(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_AUTO_RAMP_TIMEOUT)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.vol_ramp) == 0)
|
|
cs35l41->vol_ctl.ramp_end_time =
|
|
(unsigned int)ucontrol->value.integer.value[0];
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_auto_ramp_timeout(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
(long)cs35l41->vol_ctl.auto_ramp_timeout;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_auto_ramp_timeout(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_AUTO_RAMP_TIMEOUT)
|
|
return -EINVAL;
|
|
|
|
cs35l41->vol_ctl.auto_ramp_timeout =
|
|
(unsigned int)ucontrol->value.integer.value[0];
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_ramp_knee_time(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
ucontrol->value.integer.value[0] =
|
|
(long)cs35l41->vol_ctl.ramp_knee_time;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_put_ramp_knee_time(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct cs35l41_private *cs35l41;
|
|
|
|
component = snd_soc_kcontrol_component(kcontrol);
|
|
cs35l41 = snd_soc_component_get_drvdata(component);
|
|
|
|
if (ucontrol->value.integer.value[0] < 0 ||
|
|
ucontrol->value.integer.value[0] > CS35L41_MAX_AUTO_RAMP_TIMEOUT)
|
|
return -EINVAL;
|
|
|
|
if (atomic_read(&cs35l41->vol_ctl.vol_ramp) == 0)
|
|
cs35l41->vol_ctl.ramp_knee_time =
|
|
(unsigned int)ucontrol->value.integer.value[0];
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_channel_swap_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
int swapped = 0;
|
|
int val;
|
|
|
|
if (cs35l41->dsp.booted) {
|
|
wm_adsp_read_ctl(&cs35l41->dsp, "CH_BAL", (void *)&val,
|
|
sizeof(__be32));
|
|
|
|
dev_dbg(cs35l41->dev, "%s: CH_BAL = 0x%08x\n", __func__,
|
|
be32_to_cpu(val));
|
|
swapped = (0x400000 == be32_to_cpu(val)) ? 1 : 0;
|
|
}
|
|
|
|
ucontrol->value.integer.value[0] = swapped;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_channel_swap_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_kcontrol_component(kcontrol);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
int val;
|
|
|
|
if (cs35l41->dsp.booted) {
|
|
if (ucontrol->value.integer.value[0])
|
|
val = 0x400000;
|
|
else
|
|
val = 0;
|
|
|
|
val = cpu_to_be32(val);
|
|
wm_adsp_write_ctl(&cs35l41->dsp, "CH_BAL", (void *)&val,
|
|
sizeof(__be32));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const char *const cs35l41_output_dev_text[] = {
|
|
"Speaker",
|
|
"Receiver",
|
|
};
|
|
|
|
/* Ensure SPK and RCV defined values match array index */
|
|
static const unsigned int cs35l41_output_dev_val[] = {
|
|
CS35L41_OUTPUT_DEV_SPK,
|
|
CS35L41_OUTPUT_DEV_RCV,
|
|
};
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(cs35l41_output_dev, SND_SOC_NOPM, 0, 0,
|
|
cs35l41_output_dev_text,
|
|
cs35l41_output_dev_val);
|
|
|
|
static const struct snd_kcontrol_new cs35l41_aud_controls[] = {
|
|
SOC_SINGLE_RANGE_EXT_TLV("Digital PCM Volume", SND_SOC_NOPM, 0, 0,
|
|
CS35L41_MAX_PCM_VOL, 0, cs35l41_get_vol,
|
|
cs35l41_put_vol, dig_vol_tlv),
|
|
SOC_SINGLE_TLV("AMP PCM Gain", CS35L41_AMP_GAIN_CTRL, 5, 0x14, 0,
|
|
amp_gain_tlv),
|
|
SOC_SINGLE_RANGE("ASPTX1 Slot Position", CS35L41_SP_FRAME_TX_SLOT, 0, 0,
|
|
7, 0),
|
|
SOC_SINGLE_RANGE("ASPTX2 Slot Position", CS35L41_SP_FRAME_TX_SLOT, 8, 0,
|
|
7, 0),
|
|
SOC_SINGLE_RANGE("ASPTX3 Slot Position", CS35L41_SP_FRAME_TX_SLOT, 16,
|
|
0, 7, 0),
|
|
SOC_SINGLE_RANGE("ASPTX4 Slot Position", CS35L41_SP_FRAME_TX_SLOT, 24,
|
|
0, 7, 0),
|
|
SOC_SINGLE_RANGE("ASPRX1 Slot Position", CS35L41_SP_FRAME_RX_SLOT, 0, 0,
|
|
7, 0),
|
|
SOC_SINGLE_RANGE("ASPRX2 Slot Position", CS35L41_SP_FRAME_RX_SLOT, 8, 0,
|
|
7, 0),
|
|
SOC_ENUM("PCM Soft Ramp", pcm_sft_ramp),
|
|
SOC_SINGLE_EXT("DSP Booted", SND_SOC_NOPM, 0, 1, 0,
|
|
cs35l41_halo_booted_get, cs35l41_halo_booted_put),
|
|
SOC_SINGLE_EXT("CCM Reset", CS35L41_DSP1_CCM_CORE_CTRL, 0, 1, 0,
|
|
cs35l41_ccm_reset_get, cs35l41_ccm_reset_put),
|
|
SOC_SINGLE_EXT("Force Interrupt", SND_SOC_NOPM, 0, 1, 0,
|
|
cs35l41_force_int_get, cs35l41_force_int_put),
|
|
SOC_SINGLE_EXT("Fast Use Case Switch Enable", SND_SOC_NOPM, 0, 1, 0,
|
|
cs35l41_fast_switch_en_get, cs35l41_fast_switch_en_put),
|
|
SOC_SINGLE_EXT("Firmware Reload Tuning", SND_SOC_NOPM, 0, 1, 0,
|
|
cs35l41_reload_tuning_get, cs35l41_reload_tuning_put),
|
|
SOC_ENUM_EXT("Channel Swap", channel_swap_enum,
|
|
cs35l41_channel_swap_get, cs35l41_channel_swap_put),
|
|
SOC_SINGLE("VPBR Config", CS35L41_VPBR_CFG, 0, 0x7FFFFFF, 0),
|
|
SOC_SINGLE("Noise Gate Config", CS35L41_NG_CFG, 0, 0x3FFF, 0),
|
|
SOC_SINGLE("GLOBAL_EN from GPIO Control", CS35L41_PWR_CTRL1, 8, 1, 0),
|
|
#if defined(AUDIO_SMART_PA_STANDBY_SUPPORT)
|
|
SOC_SINGLE("GLOBAL_EN Control", CS35L41_PWR_CTRL1, 0, 1,
|
|
0), // for pa standby
|
|
#endif
|
|
SOC_SINGLE("Boost Converter Enable", CS35L41_PWR_CTRL2, 4, 3, 0),
|
|
SOC_SINGLE("AMP Enable", CS35L41_PWR_CTRL2, 0, 1, 0),
|
|
WM_ADSP_FW_CONTROL("DSP1", 0),
|
|
SOC_SINGLE_BOOL_EXT("Safety Volume Ramp Status", 0,
|
|
cs35l41_get_ramp_status, cs35l41_put_ramp_status),
|
|
SOC_SINGLE_BOOL_EXT("Manual Ramp Control", 0, cs35l41_get_manual_ramp,
|
|
cs35l41_put_manual_ramp),
|
|
SOC_SINGLE_EXT("Initial Ramp Volume Attenuation", SND_SOC_NOPM, 0,
|
|
CS35L41_MAX_VOL_ATT, 0, cs35l41_get_init_attenuation,
|
|
cs35l41_put_init_attenuation),
|
|
SOC_SINGLE_EXT("Knee Ramp Volume Attenuation", SND_SOC_NOPM, 0,
|
|
CS35L41_MAX_VOL_ATT, 0, cs35l41_get_knee_attenuation,
|
|
cs35l41_put_knee_attenuation),
|
|
SOC_SINGLE_EXT("Ramp Knee Time", SND_SOC_NOPM, 0,
|
|
CS35L41_MAX_AUTO_RAMP_TIMEOUT, 0,
|
|
cs35l41_get_ramp_knee_time, cs35l41_put_ramp_knee_time),
|
|
SOC_SINGLE_EXT("Ramp End Time", SND_SOC_NOPM, 0,
|
|
CS35L41_MAX_AUTO_RAMP_TIMEOUT, 0,
|
|
cs35l41_get_ramp_end_time, cs35l41_put_ramp_end_time),
|
|
SOC_SINGLE_EXT("Auto Ramp Safety Timeout", SND_SOC_NOPM, 0,
|
|
CS35L41_MAX_AUTO_RAMP_TIMEOUT, 0,
|
|
cs35l41_get_auto_ramp_timeout,
|
|
cs35l41_put_auto_ramp_timeout),
|
|
SOC_VALUE_ENUM_EXT("Audio Output Device", cs35l41_output_dev,
|
|
cs35l41_get_output_dev, cs35l41_put_output_dev),
|
|
WM_ADSP2_PRELOAD_SWITCH("DSP1", 1),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new cs35l41_bst_ctl[] = {
|
|
SOC_SINGLE("Boost Class-H Tracking Enable", CS35L41_BSTCVRT_VCTRL2, 0,
|
|
1, 0),
|
|
SOC_SINGLE("Boost Target Voltage", CS35L41_BSTCVRT_VCTRL1, 0, 0xAA, 0),
|
|
SOC_SINGLE_EXT("Hibernate Force Wake", SND_SOC_NOPM, 0, 1, 0,
|
|
cs35l41_hibernate_force_wake_get,
|
|
cs35l41_hibernate_force_wake_put),
|
|
};
|
|
|
|
static const struct snd_kcontrol_new cs35l41_lv_ctl[] = {
|
|
SOC_SINGLE("Boost Target Voltage", CS35L41_BSTCVRT_VCTRL1, 0, 0x8C, 0),
|
|
};
|
|
|
|
static const struct cs35l41_otp_map_element_t *
|
|
cs35l41_find_otp_map(u32 otp_id, u32 devid_otp)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < cs35l41_otp_maps.len; i++) {
|
|
if (cs35l41_otp_maps.map[i].id == otp_id &&
|
|
cs35l41_otp_maps.map[i].devid_otp == devid_otp)
|
|
return &cs35l41_otp_maps.map[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int cs35l41_otp_unpack(void *data)
|
|
{
|
|
struct cs35l41_private *cs35l41 = data;
|
|
u32 *otp_mem;
|
|
int i;
|
|
int bit_offset, word_offset;
|
|
unsigned int bit_sum = 8;
|
|
u32 otp_val, otp_id_reg;
|
|
const struct cs35l41_otp_map_element_t *otp_map_match;
|
|
const struct cs35l41_otp_packed_element_t *otp_map;
|
|
int ret;
|
|
struct spi_device *spi = NULL;
|
|
u32 orig_spi_freq = 0;
|
|
u32 devid_otp;
|
|
|
|
otp_mem = kmalloc_array(32, sizeof(*otp_mem), GFP_KERNEL);
|
|
if (!otp_mem)
|
|
return -ENOMEM;
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_OTPID, &otp_id_reg);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read OTP ID failed\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID_OTP, &devid_otp);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read DEVID OTP failed\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
|
|
otp_map_match = cs35l41_find_otp_map(otp_id_reg, devid_otp);
|
|
|
|
if (otp_map_match == NULL) {
|
|
dev_err(cs35l41->dev, "OTP Map matching ID %d not found\n",
|
|
otp_id_reg);
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
|
|
if (cs35l41->bus_spi) {
|
|
spi = to_spi_device(cs35l41->dev);
|
|
orig_spi_freq = spi->max_speed_hz;
|
|
spi->max_speed_hz = CS35L41_SPI_MAX_FREQ_OTP;
|
|
spi_setup(spi);
|
|
}
|
|
|
|
ret = regmap_bulk_read(cs35l41->regmap, CS35L41_OTP_MEM0, otp_mem,
|
|
CS35L41_OTP_SIZE_WORDS);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read OTP Mem failed\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
|
|
if (cs35l41->bus_spi) {
|
|
spi->max_speed_hz = orig_spi_freq;
|
|
spi_setup(spi);
|
|
}
|
|
|
|
otp_map = otp_map_match->map;
|
|
|
|
bit_offset = otp_map_match->bit_offset;
|
|
word_offset = otp_map_match->word_offset;
|
|
|
|
ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000055);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Write Unlock key failed 1/2\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000AA);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Write Unlock key failed 2/2\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
|
|
for (i = 0; i < otp_map_match->num_elements; i++) {
|
|
dev_dbg(cs35l41->dev,
|
|
"bitoffset= %d, word_offset=%d, bit_sum mod 32=%d\n",
|
|
bit_offset, word_offset, bit_sum % 32);
|
|
if (bit_offset + otp_map[i].size - 1 >= 32) {
|
|
otp_val = (otp_mem[word_offset] &
|
|
GENMASK(31, bit_offset)) >>
|
|
bit_offset;
|
|
otp_val |=
|
|
(otp_mem[++word_offset] &
|
|
GENMASK(bit_offset + otp_map[i].size - 33, 0))
|
|
<< (32 - bit_offset);
|
|
bit_offset += otp_map[i].size - 32;
|
|
} else {
|
|
otp_val = (otp_mem[word_offset] &
|
|
GENMASK(bit_offset + otp_map[i].size - 1,
|
|
bit_offset)) >>
|
|
bit_offset;
|
|
bit_offset += otp_map[i].size;
|
|
}
|
|
bit_sum += otp_map[i].size;
|
|
|
|
if (bit_offset == 32) {
|
|
bit_offset = 0;
|
|
word_offset++;
|
|
}
|
|
|
|
if (otp_map[i].reg != 0) {
|
|
ret = regmap_update_bits(
|
|
cs35l41->regmap, otp_map[i].reg,
|
|
GENMASK(otp_map[i].shift + otp_map[i].size - 1,
|
|
otp_map[i].shift),
|
|
otp_val << otp_map[i].shift);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Write OTP val failed\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000CC);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Write Lock key failed 1/2\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
ret = regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000033);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Write Lock key failed 2/2\n");
|
|
ret = -EINVAL;
|
|
goto err_otp_unpack;
|
|
}
|
|
ret = 0;
|
|
|
|
err_otp_unpack:
|
|
kfree(otp_mem);
|
|
return ret;
|
|
}
|
|
|
|
static irqreturn_t cs35l41_irq(int irq, void *data)
|
|
{
|
|
struct cs35l41_private *cs35l41 = data;
|
|
unsigned int status[4];
|
|
unsigned int masks[4];
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(status); i++) {
|
|
regmap_read(cs35l41->regmap,
|
|
CS35L41_IRQ1_STATUS1 + (i * CS35L41_REGSTRIDE),
|
|
&status[i]);
|
|
regmap_read(cs35l41->regmap,
|
|
CS35L41_IRQ1_MASK1 + (i * CS35L41_REGSTRIDE),
|
|
&masks[i]);
|
|
}
|
|
|
|
/* Check to see if unmasked bits are active */
|
|
if (!(status[0] & ~masks[0]) && !(status[1] & ~masks[1]) &&
|
|
!(status[2] & ~masks[2]) && !(status[3] & ~masks[3]))
|
|
return IRQ_NONE;
|
|
|
|
/*
|
|
* The following interrupts require a
|
|
* protection release cycle to get the
|
|
* speaker out of Safe-Mode.
|
|
*/
|
|
if (status[0] & CS35L41_AMP_SHORT_ERR) {
|
|
dev_crit(cs35l41->dev, "Amp short error\n");
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_AMP_SHORT_ERR);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_AMP_SHORT_ERR_RLS,
|
|
CS35L41_AMP_SHORT_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_AMP_SHORT_ERR_RLS, 0);
|
|
}
|
|
|
|
if (status[0] & CS35L41_TEMP_WARN) {
|
|
dev_crit(cs35l41->dev, "Over temperature warning\n");
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_TEMP_WARN);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_TEMP_WARN_ERR_RLS,
|
|
CS35L41_TEMP_WARN_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_TEMP_WARN_ERR_RLS, 0);
|
|
}
|
|
|
|
if (status[0] & CS35L41_TEMP_ERR) {
|
|
dev_crit(cs35l41->dev, "Over temperature error\n");
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_TEMP_ERR);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_TEMP_ERR_RLS, CS35L41_TEMP_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_TEMP_ERR_RLS, 0);
|
|
}
|
|
|
|
if (status[0] & CS35L41_BST_OVP_ERR) {
|
|
dev_crit(cs35l41->dev, "VBST Over Voltage error\n");
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
|
|
CS35L41_BST_EN_MASK, 0);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_BST_OVP_ERR);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_OVP_ERR_RLS,
|
|
CS35L41_BST_OVP_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_OVP_ERR_RLS, 0);
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
|
|
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
|
|
}
|
|
|
|
if (status[0] & CS35L41_BST_DCM_UVP_ERR) {
|
|
dev_crit(cs35l41->dev, "DCM VBST Under Voltage Error\n");
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
|
|
CS35L41_BST_EN_MASK, 0);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_BST_DCM_UVP_ERR);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_UVP_ERR_RLS,
|
|
CS35L41_BST_UVP_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_UVP_ERR_RLS, 0);
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
|
|
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
|
|
}
|
|
|
|
if (status[0] & CS35L41_BST_SHORT_ERR) {
|
|
dev_crit(cs35l41->dev, "LBST error: powering off!\n");
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL2,
|
|
CS35L41_BST_EN_MASK, 0);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_BST_SHORT_ERR);
|
|
regmap_write(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN, 0);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_SHORT_ERR_RLS,
|
|
CS35L41_BST_SHORT_ERR_RLS);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PROTECT_REL_ERR_IGN,
|
|
CS35L41_BST_SHORT_ERR_RLS, 0);
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_PWR_CTRL2, CS35L41_BST_EN_MASK,
|
|
CS35L41_BST_EN_DEFAULT << CS35L41_BST_EN_SHIFT);
|
|
}
|
|
|
|
if (status[3] & CS35L41_OTP_BOOT_DONE) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK4,
|
|
CS35L41_OTP_BOOT_DONE,
|
|
CS35L41_OTP_BOOT_DONE);
|
|
}
|
|
|
|
#if defined(BRINGUP_IRQ_VERIFY)
|
|
if (status[0] & CS35L41_PUP_DONE_MASK) {
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_PUP_DONE_MASK);
|
|
dev_dbg(cs35l41->dev, "AMP powered up\n");
|
|
}
|
|
#endif
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const struct reg_sequence cs35l41_pup_patch[] = {
|
|
{ 0x00000040, 0x00000055 }, { 0x00000040, 0x000000AA },
|
|
{ 0x00002084, 0x002F1AA0 }, { 0x00000040, 0x000000CC },
|
|
{ 0x00000040, 0x00000033 },
|
|
};
|
|
|
|
static const struct reg_sequence cs35l41_pdn_patch[] = {
|
|
{ 0x00000040, 0x00000055 }, { 0x00000040, 0x000000AA },
|
|
{ 0x00002084, 0x002F1AA3 }, { 0x00000040, 0x000000CC },
|
|
{ 0x00000040, 0x00000033 },
|
|
};
|
|
|
|
static void cs35l41_hibernate_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
struct cs35l41_private *cs35l41 =
|
|
container_of(dwork, struct cs35l41_private, hb_work);
|
|
|
|
mutex_lock(&cs35l41->hb_lock);
|
|
cs35l41_enter_hibernate(cs35l41);
|
|
mutex_unlock(&cs35l41->hb_lock);
|
|
}
|
|
|
|
static int cs35l41_hibernate(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_dapm_to_component(w->dapm);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
int ret = 0;
|
|
|
|
if (!cs35l41->dsp.running ||
|
|
cs35l41->amp_hibernate == CS35L41_HIBERNATE_INCOMPATIBLE ||
|
|
cs35l41->hibernate_force_wake)
|
|
return 0;
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_PRE_PMU:
|
|
cancel_delayed_work(&cs35l41->hb_work);
|
|
mutex_lock(&cs35l41->hb_lock);
|
|
ret = cs35l41_exit_hibernate(cs35l41);
|
|
mutex_unlock(&cs35l41->hb_lock);
|
|
break;
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
queue_delayed_work(cs35l41->wq, &cs35l41->hb_work,
|
|
msecs_to_jiffies(2000));
|
|
break;
|
|
default:
|
|
dev_err(cs35l41->dev, "Invalid event = 0x%x\n", event);
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static bool cs35l41_need_auto_vol_ramp(struct cs35l41_private *cs35l41)
|
|
{
|
|
bool ramp = false;
|
|
ktime_t curr_timestamp;
|
|
s64 dev_timeout = (s64)cs35l41->vol_ctl.auto_ramp_timeout * 1000000;
|
|
s64 elapsed_time;
|
|
|
|
if (cs35l41->vol_ctl.prev_active_dev == CS35L41_OUTPUT_DEV_RCV &&
|
|
cs35l41->vol_ctl.output_dev == CS35L41_OUTPUT_DEV_SPK) {
|
|
if (cs35l41->vol_ctl.auto_ramp_timeout == 0) {
|
|
/* Never ramp */
|
|
} else if (cs35l41->vol_ctl.auto_ramp_timeout ==
|
|
CS35L41_MAX_AUTO_RAMP_TIMEOUT) {
|
|
/* Always ramp */
|
|
ramp = true;
|
|
} else {
|
|
/*
|
|
* Guaranteed:
|
|
* 0 < auto_ramp_timeout < CS35L41_MAX_AUTO_RAMP_TIMEOUT
|
|
*/
|
|
curr_timestamp = ktime_get();
|
|
elapsed_time =
|
|
ktime_to_ns(curr_timestamp) -
|
|
ktime_to_ns(cs35l41->vol_ctl.dev_timestamp);
|
|
if (elapsed_time < dev_timeout)
|
|
ramp = true;
|
|
dev_dbg(cs35l41->dev,
|
|
"elapsed_time:%lld dev_timeout:%lld\n",
|
|
elapsed_time, dev_timeout);
|
|
}
|
|
}
|
|
|
|
return ramp;
|
|
}
|
|
|
|
static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_component *component =
|
|
snd_soc_dapm_to_component(w->dapm);
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
enum cs35l41_cspl_mboxcmd mboxcmd = CSPL_MBOX_CMD_NONE;
|
|
int ret = 0;
|
|
int i;
|
|
bool pdn;
|
|
unsigned int val;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: event = 0x%x\n", __func__, event);
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
regmap_multi_reg_write_bypassed(cs35l41->regmap,
|
|
cs35l41_pup_patch,
|
|
ARRAY_SIZE(cs35l41_pup_patch));
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL1,
|
|
CS35L41_GLOBAL_EN_MASK,
|
|
1 << CS35L41_GLOBAL_EN_SHIFT);
|
|
|
|
usleep_range(1000, 1100);
|
|
|
|
atomic_set(&cs35l41->vol_ctl.playback, 1);
|
|
if (atomic_read(&cs35l41->vol_ctl.manual_ramp) ||
|
|
cs35l41_need_auto_vol_ramp(cs35l41))
|
|
/* Enable volume ramp */
|
|
queue_work(cs35l41->vol_ctl.ramp_wq,
|
|
&cs35l41->vol_ctl.ramp_work);
|
|
break;
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
if (cs35l41->dsp.running) {
|
|
if (cs35l41->reload_tuning) {
|
|
mboxcmd = CSPL_MBOX_CMD_STOP_PRE_REINIT;
|
|
/*
|
|
* Reset reload_tuning, so driver does not
|
|
* continuously reload tuning file
|
|
*/
|
|
cs35l41->reload_tuning = false;
|
|
} else {
|
|
mboxcmd = CSPL_MBOX_CMD_PAUSE;
|
|
}
|
|
ret = cs35l41_set_csplmboxcmd(cs35l41, mboxcmd);
|
|
}
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PWR_CTRL1,
|
|
CS35L41_GLOBAL_EN_MASK, 0);
|
|
|
|
pdn = false;
|
|
for (i = 0; i < 100; i++) {
|
|
regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
&val);
|
|
if (val & CS35L41_PDN_DONE_MASK) {
|
|
pdn = true;
|
|
break;
|
|
}
|
|
usleep_range(1000, 1010);
|
|
}
|
|
|
|
if (!pdn)
|
|
dev_warn(cs35l41->dev, "PDN failed\n");
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS1,
|
|
CS35L41_PDN_DONE_MASK);
|
|
|
|
regmap_multi_reg_write_bypassed(cs35l41->regmap,
|
|
cs35l41_pdn_patch,
|
|
ARRAY_SIZE(cs35l41_pdn_patch));
|
|
atomic_set(&cs35l41->vol_ctl.playback, 0);
|
|
cs35l41_abort_ramp(cs35l41);
|
|
cs35l41->vol_ctl.prev_active_dev = cs35l41->vol_ctl.output_dev;
|
|
if (cs35l41->vol_ctl.output_dev == CS35L41_OUTPUT_DEV_RCV &&
|
|
cs35l41->vol_ctl.auto_ramp_timeout > 0 &&
|
|
cs35l41->vol_ctl.auto_ramp_timeout <
|
|
CS35L41_MAX_AUTO_RAMP_TIMEOUT)
|
|
/* Auto Receiver Timeout is used */
|
|
cs35l41->vol_ctl.dev_timestamp = ktime_get();
|
|
break;
|
|
default:
|
|
dev_err(cs35l41->dev, "Invalid event = 0x%x\n", event);
|
|
ret = -EINVAL;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct snd_soc_dapm_widget cs35l41_dapm_widgets[] = {
|
|
|
|
SND_SOC_DAPM_SPK("DSP1 Preload", NULL),
|
|
SND_SOC_DAPM_SUPPLY_S("DSP1 Preloader", 100, SND_SOC_NOPM, 0, 0,
|
|
cs35l41_dsp_power_ev,
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
SND_SOC_DAPM_OUT_DRV_E("DSP1", SND_SOC_NOPM, 0, 0, NULL, 0,
|
|
cs35l41_dsp_load_ev,
|
|
SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
|
|
SND_SOC_DAPM_OUTPUT("SPK"),
|
|
|
|
SND_SOC_DAPM_AIF_IN("ASPRX1", NULL, 0, CS35L41_SP_ENABLES, 16, 0),
|
|
SND_SOC_DAPM_AIF_IN("ASPRX2", NULL, 0, CS35L41_SP_ENABLES, 17, 0),
|
|
SND_SOC_DAPM_AIF_OUT("ASPTX1", NULL, 0, CS35L41_SP_ENABLES, 0, 0),
|
|
SND_SOC_DAPM_AIF_OUT("ASPTX2", NULL, 0, CS35L41_SP_ENABLES, 1, 0),
|
|
SND_SOC_DAPM_AIF_OUT("ASPTX3", NULL, 0, CS35L41_SP_ENABLES, 2, 0),
|
|
SND_SOC_DAPM_AIF_OUT("ASPTX4", NULL, 0, CS35L41_SP_ENABLES, 3, 0),
|
|
|
|
SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L41_PWR_CTRL2, 12, 0),
|
|
SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L41_PWR_CTRL2, 13, 0),
|
|
SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L41_PWR_CTRL2, 8, 0),
|
|
SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L41_PWR_CTRL2, 9, 0),
|
|
SND_SOC_DAPM_ADC("TEMPMON ADC", NULL, CS35L41_PWR_CTRL2, 10, 0),
|
|
SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L41_PWR_CTRL3, 4, 0),
|
|
|
|
SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L41_PWR_CTRL2, 0, 0, NULL, 0,
|
|
cs35l41_main_amp_event,
|
|
SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU),
|
|
SND_SOC_DAPM_INPUT("VP"),
|
|
SND_SOC_DAPM_INPUT("VBST"),
|
|
SND_SOC_DAPM_INPUT("ISENSE"),
|
|
SND_SOC_DAPM_INPUT("VSENSE"),
|
|
SND_SOC_DAPM_INPUT("TEMP"),
|
|
|
|
SND_SOC_DAPM_MUX("ASPTX Ref", SND_SOC_NOPM, 0, 0, &virt_mux),
|
|
SND_SOC_DAPM_MUX("ASP TX1 Source", SND_SOC_NOPM, 0, 0, &asp_tx1_mux),
|
|
SND_SOC_DAPM_MUX("ASP TX2 Source", SND_SOC_NOPM, 0, 0, &asp_tx2_mux),
|
|
SND_SOC_DAPM_MUX("ASP TX3 Source", SND_SOC_NOPM, 0, 0, &asp_tx3_mux),
|
|
SND_SOC_DAPM_MUX("ASP TX4 Source", SND_SOC_NOPM, 0, 0, &asp_tx4_mux),
|
|
SND_SOC_DAPM_MUX("DSP RX1 Source", SND_SOC_NOPM, 0, 0, &dsp_rx1_mux),
|
|
SND_SOC_DAPM_MUX("DSP RX2 Source", SND_SOC_NOPM, 0, 0, &dsp_rx2_mux),
|
|
SND_SOC_DAPM_MUX("PCM Source", SND_SOC_NOPM, 0, 0, &pcm_source_mux),
|
|
SND_SOC_DAPM_SWITCH("DRE", SND_SOC_NOPM, 0, 0, &dre_ctrl),
|
|
SND_SOC_DAPM_SWITCH("VBSTMON Output", SND_SOC_NOPM, 0, 0,
|
|
&vbstmon_out_ctrl),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget cs35l41_hib_widgets[] = {
|
|
SND_SOC_DAPM_SUPPLY("Hibernate", SND_SOC_NOPM, 0, 0, cs35l41_hibernate,
|
|
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route cs35l41_audio_map[] = {
|
|
|
|
{ "DSP1", NULL, "DSP1 Preloader" },
|
|
{ "DSP1 Preload", NULL, "DSP1 Preloader" },
|
|
|
|
{ "DSP RX1 Source", "VMON", "VMON ADC" },
|
|
{ "DSP RX1 Source", "IMON", "IMON ADC" },
|
|
{ "DSP RX1 Source", "VPMON", "VPMON ADC" },
|
|
{ "DSP RX1 Source", "DSPTX1", "DSP1" },
|
|
{ "DSP RX1 Source", "DSPTX2", "DSP1" },
|
|
{ "DSP RX1 Source", "ASPRX1", "ASPRX1" },
|
|
{ "DSP RX1 Source", "ASPRX2", "ASPRX2" },
|
|
{ "DSP RX1 Source", "Zero", "ASPRX1" },
|
|
{ "DSP1", NULL, "DSP RX1 Source" },
|
|
|
|
{ "DSP RX2 Source", "VMON", "VMON ADC" },
|
|
{ "DSP RX2 Source", "IMON", "IMON ADC" },
|
|
{ "DSP RX2 Source", "VPMON", "VPMON ADC" },
|
|
{ "DSP RX2 Source", "DSPTX1", "DSP1" },
|
|
{ "DSP RX2 Source", "DSPTX2", "DSP1" },
|
|
{ "DSP RX2 Source", "ASPRX1", "ASPRX1" },
|
|
{ "DSP RX2 Source", "ASPRX2", "ASPRX2" },
|
|
{ "DSP RX2 Source", "Zero", "ASPRX1" },
|
|
{ "DSP1", NULL, "DSP RX2 Source" },
|
|
|
|
{ "ASP TX1 Source", "VMON", "VMON ADC" },
|
|
{ "ASP TX1 Source", "IMON", "IMON ADC" },
|
|
{ "ASP TX1 Source", "VPMON", "VPMON ADC" },
|
|
{ "ASP TX1 Source", "VBSTMON", "VBSTMON ADC" },
|
|
{ "ASP TX1 Source", "DSPTX1", "DSP1" },
|
|
{ "ASP TX1 Source", "DSPTX2", "DSP1" },
|
|
{ "ASP TX1 Source", "ASPRX1", "ASPRX1" },
|
|
{ "ASP TX1 Source", "ASPRX2", "ASPRX2" },
|
|
{ "ASP TX2 Source", "VMON", "VMON ADC" },
|
|
{ "ASP TX2 Source", "IMON", "IMON ADC" },
|
|
{ "ASP TX2 Source", "VPMON", "VPMON ADC" },
|
|
{ "ASP TX2 Source", "VBSTMON", "VBSTMON ADC" },
|
|
{ "ASP TX2 Source", "DSPTX1", "DSP1" },
|
|
{ "ASP TX2 Source", "DSPTX2", "DSP1" },
|
|
{ "ASP TX2 Source", "ASPRX1", "ASPRX1" },
|
|
{ "ASP TX2 Source", "ASPRX2", "ASPRX2" },
|
|
{ "ASP TX3 Source", "VMON", "VMON ADC" },
|
|
{ "ASP TX3 Source", "IMON", "IMON ADC" },
|
|
{ "ASP TX3 Source", "VPMON", "VPMON ADC" },
|
|
{ "ASP TX3 Source", "VBSTMON", "VBSTMON ADC" },
|
|
{ "ASP TX3 Source", "DSPTX1", "DSP1" },
|
|
{ "ASP TX3 Source", "DSPTX2", "DSP1" },
|
|
{ "ASP TX3 Source", "ASPRX1", "ASPRX1" },
|
|
{ "ASP TX3 Source", "ASPRX2", "ASPRX2" },
|
|
{ "ASP TX4 Source", "VMON", "VMON ADC" },
|
|
{ "ASP TX4 Source", "IMON", "IMON ADC" },
|
|
{ "ASP TX4 Source", "VPMON", "VPMON ADC" },
|
|
{ "ASP TX4 Source", "VBSTMON", "VBSTMON ADC" },
|
|
{ "ASP TX4 Source", "DSPTX1", "DSP1" },
|
|
{ "ASP TX4 Source", "DSPTX2", "DSP1" },
|
|
{ "ASP TX4 Source", "ASPRX1", "ASPRX1" },
|
|
{ "ASP TX4 Source", "ASPRX2", "ASPRX2" },
|
|
{ "ASPTX1", NULL, "ASP TX1 Source" },
|
|
{ "ASPTX2", NULL, "ASP TX2 Source" },
|
|
{ "ASPTX3", NULL, "ASP TX3 Source" },
|
|
{ "ASPTX4", NULL, "ASP TX4 Source" },
|
|
{ "AMP Capture", NULL, "ASPTX1" },
|
|
{ "AMP Capture", NULL, "ASPTX2" },
|
|
{ "AMP Capture", NULL, "ASPTX3" },
|
|
{ "AMP Capture", NULL, "ASPTX4" },
|
|
|
|
{ "VMON ADC", NULL, "ASPRX1" },
|
|
{ "IMON ADC", NULL, "ASPRX1" },
|
|
{ "VPMON ADC", NULL, "ASPRX1" },
|
|
{ "TEMPMON ADC", NULL, "ASPRX1" },
|
|
{ "VBSTMON ADC", NULL, "ASPRX1" },
|
|
|
|
{ "VBSTMON Output", "Switch", "VBST" },
|
|
{ "CLASS H", NULL, "VBSTMON Output" },
|
|
{ "VBSTMON ADC", NULL, "VBSTMON Output" },
|
|
|
|
{ "DSP1", NULL, "IMON ADC" },
|
|
{ "DSP1", NULL, "VMON ADC" },
|
|
{ "DSP1", NULL, "VBSTMON ADC" },
|
|
{ "DSP1", NULL, "VPMON ADC" },
|
|
{ "DSP1", NULL, "TEMPMON ADC" },
|
|
|
|
{ "ASPRX1", NULL, "AMP Playback" },
|
|
{ "ASPRX2", NULL, "AMP Playback" },
|
|
{ "DRE", "DRE Switch", "CLASS H" },
|
|
{ "ASPTX Ref", "Ref", "ASPTX1" },
|
|
{ "ASPTX Ref", "Ref", "ASPTX2" },
|
|
{ "Main AMP", NULL, "ASPTX Ref" },
|
|
{ "Main AMP", NULL, "CLASS H" },
|
|
{ "Main AMP", NULL, "DRE" },
|
|
{ "SPK", NULL, "Main AMP" },
|
|
|
|
{ "PCM Source", "ASP", "ASPRX1" },
|
|
{ "PCM Source", "DSP", "DSP1" },
|
|
{ "CLASS H", NULL, "PCM Source" },
|
|
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route cs35l41_hib_map[] = {
|
|
{ "SPK", NULL, "Hibernate" },
|
|
};
|
|
|
|
static const struct wm_adsp_region cs35l41_dsp1_regions[] = {
|
|
{ .type = WMFW_HALO_PM_PACKED, .base = CS35L41_DSP1_PMEM_0 },
|
|
{ .type = WMFW_HALO_XM_PACKED, .base = CS35L41_DSP1_XMEM_PACK_0 },
|
|
{ .type = WMFW_HALO_YM_PACKED, .base = CS35L41_DSP1_YMEM_PACK_0 },
|
|
{ .type = WMFW_ADSP2_XM, .base = CS35L41_DSP1_XMEM_UNPACK24_0 },
|
|
{ .type = WMFW_ADSP2_YM, .base = CS35L41_DSP1_YMEM_UNPACK24_0 },
|
|
};
|
|
|
|
static int cs35l41_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(codec_dai->component);
|
|
unsigned int asp_fmt, lrclk_fmt, sclk_fmt, slave_mode;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: fmt = 0x%x\n", __func__, fmt);
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
|
slave_mode = 1;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
slave_mode = 0;
|
|
break;
|
|
default:
|
|
dev_warn(cs35l41->dev, "%s: Mixed master mode unsupported\n",
|
|
__func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_DSP_A:
|
|
asp_fmt = 0;
|
|
cs35l41->i2s_mode = false;
|
|
cs35l41->dspa_mode = true;
|
|
break;
|
|
case SND_SOC_DAIFMT_I2S:
|
|
asp_fmt = 2;
|
|
cs35l41->i2s_mode = true;
|
|
cs35l41->dspa_mode = false;
|
|
break;
|
|
default:
|
|
dev_warn(cs35l41->dev,
|
|
"%s: Invalid or unsupported DAI format\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
|
|
case SND_SOC_DAIFMT_NB_IF:
|
|
lrclk_fmt = 1;
|
|
sclk_fmt = 0;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_NF:
|
|
lrclk_fmt = 0;
|
|
sclk_fmt = 1;
|
|
break;
|
|
case SND_SOC_DAIFMT_IB_IF:
|
|
lrclk_fmt = 1;
|
|
sclk_fmt = 1;
|
|
break;
|
|
case SND_SOC_DAIFMT_NB_NF:
|
|
lrclk_fmt = 0;
|
|
sclk_fmt = 0;
|
|
break;
|
|
default:
|
|
dev_warn(cs35l41->dev, "%s: Invalid DAI clock INV\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cs35l41->reset_cache.slave_mode = slave_mode;
|
|
cs35l41->reset_cache.asp_fmt = asp_fmt;
|
|
cs35l41->reset_cache.lrclk_fmt = lrclk_fmt;
|
|
cs35l41->reset_cache.sclk_fmt = sclk_fmt;
|
|
/* Amp is in reset. Cache values to be applied later. */
|
|
if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_STANDBY)
|
|
return 0;
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_FMT_MASK,
|
|
asp_fmt << CS35L41_ASP_FMT_SHIFT);
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_SCLK_MSTR_MASK,
|
|
slave_mode << CS35L41_SCLK_MSTR_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_LRCLK_MSTR_MASK,
|
|
slave_mode << CS35L41_LRCLK_MSTR_SHIFT);
|
|
|
|
cs35l41->lrclk_fmt = lrclk_fmt;
|
|
cs35l41->sclk_fmt = sclk_fmt;
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_LRCLK_INV_MASK,
|
|
lrclk_fmt << CS35L41_LRCLK_INV_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_SCLK_INV_MASK,
|
|
sclk_fmt << CS35L41_SCLK_INV_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct cs35l41_global_fs_config {
|
|
int rate;
|
|
int fs_cfg;
|
|
};
|
|
|
|
static const struct cs35l41_global_fs_config cs35l41_fs_rates[] = {
|
|
{ 12000, 0x01 }, { 24000, 0x02 }, { 48000, 0x03 }, { 96000, 0x04 },
|
|
{ 192000, 0x05 }, { 11025, 0x09 }, { 22050, 0x0A }, { 44100, 0x0B },
|
|
{ 88200, 0x0C }, { 176400, 0x0D }, { 8000, 0x11 }, { 16000, 0x12 },
|
|
{ 32000, 0x13 },
|
|
};
|
|
|
|
static int cs35l41_pcm_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(dai->component);
|
|
int i;
|
|
unsigned int rate = params_rate(params);
|
|
u8 asp_width, asp_wl;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs35l41_fs_rates); i++) {
|
|
if (rate == cs35l41_fs_rates[i].rate)
|
|
break;
|
|
}
|
|
|
|
asp_wl = params_width(params);
|
|
asp_width = params_physical_width(params);
|
|
|
|
dev_dbg(cs35l41->dev, "%s: rate = %u, asp_wl = %u, asp_width = %u\n",
|
|
__func__, rate, asp_wl, asp_width);
|
|
|
|
cs35l41->reset_cache.asp_wl = asp_wl;
|
|
cs35l41->reset_cache.asp_width = asp_width;
|
|
if (i < ARRAY_SIZE(cs35l41_fs_rates))
|
|
cs35l41->reset_cache.fs_cfg = cs35l41_fs_rates[i].fs_cfg;
|
|
|
|
/* Amp is in reset. Cache values to be applied later */
|
|
if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_STANDBY)
|
|
return 0;
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GLOBAL_CLK_CTRL,
|
|
CS35L41_GLOBAL_FS_MASK,
|
|
cs35l41_fs_rates[i].fs_cfg
|
|
<< CS35L41_GLOBAL_FS_SHIFT);
|
|
|
|
#if defined(CONFIG_MACH_XIAOMI_ENUMA) || defined(CONFIG_MACH_XIAOMI_ELISH)
|
|
cs35l41_component_set_sysclk(dai->component, 0, 0, 8 * rate * asp_width,
|
|
0);
|
|
#elif defined(CONFIG_MACH_XIAOMI_DAGU)
|
|
cs35l41_component_set_sysclk(dai->component, 0, 0, 4 * rate * asp_width,
|
|
0);
|
|
#else
|
|
cs35l41_component_set_sysclk(dai->component, 0, 0, 2 * rate * asp_width,
|
|
0);
|
|
#endif
|
|
|
|
/* Note: To keep RX and TX using same data width */
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK ||
|
|
substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
|
|
//if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_WIDTH_RX_MASK,
|
|
asp_width << CS35L41_ASP_WIDTH_RX_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_RX_WL,
|
|
CS35L41_ASP_RX_WL_MASK,
|
|
asp_wl << CS35L41_ASP_RX_WL_SHIFT);
|
|
if (cs35l41->i2s_mode) {
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_SP_FRAME_RX_SLOT,
|
|
CS35L41_ASP_RX1_SLOT_MASK,
|
|
((cs35l41->pdata.right_channel) ? 1 : 0)
|
|
<< CS35L41_ASP_RX1_SLOT_SHIFT);
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_SP_FRAME_RX_SLOT,
|
|
CS35L41_ASP_RX2_SLOT_MASK,
|
|
((cs35l41->pdata.right_channel) ? 0 : 1)
|
|
<< CS35L41_ASP_RX2_SLOT_SHIFT);
|
|
}
|
|
//} else {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_WIDTH_TX_MASK,
|
|
asp_width << CS35L41_ASP_WIDTH_TX_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_TX_WL,
|
|
CS35L41_ASP_TX_WL_MASK,
|
|
asp_wl << CS35L41_ASP_TX_WL_SHIFT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_get_clk_config(int freq)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs35l41_pll_sysclk); i++) {
|
|
if (cs35l41_pll_sysclk[i].freq == freq)
|
|
return cs35l41_pll_sysclk[i].clk_cfg;
|
|
}
|
|
|
|
return -EINVAL;
|
|
}
|
|
|
|
static const unsigned int cs35l41_src_rates[] = { 8000, 12000, 11025, 16000,
|
|
22050, 24000, 32000, 44100,
|
|
48000, 88200, 96000, 176400,
|
|
192000 };
|
|
|
|
static const struct snd_pcm_hw_constraint_list cs35l41_constraints = {
|
|
.count = ARRAY_SIZE(cs35l41_src_rates),
|
|
.list = cs35l41_src_rates,
|
|
};
|
|
|
|
static int cs35l41_pcm_startup(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(dai->component);
|
|
|
|
dev_dbg(cs35l41->dev, "%s\n", __func__);
|
|
#if defined(CONFIG_MACH_XIAOMI_ENUMA) || defined(CONFIG_MACH_XIAOMI_ELISH) || \
|
|
defined(CONFIG_MACH_XIAOMI_DAGU)
|
|
cs35l41_set_dai_fmt(dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_DSP_A);
|
|
#else
|
|
cs35l41_set_dai_fmt(dai, SND_SOC_DAIFMT_CBS_CFS | SND_SOC_DAIFMT_I2S);
|
|
#endif
|
|
|
|
if (substream->runtime)
|
|
return snd_pcm_hw_constraint_list(substream->runtime, 0,
|
|
SNDRV_PCM_HW_PARAM_RATE,
|
|
&cs35l41_constraints);
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_component_set_sysclk(struct snd_soc_component *component,
|
|
int clk_id, int source,
|
|
unsigned int freq, int dir)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
dev_dbg(cs35l41->dev,
|
|
"%s: clk_id = %d, source = %d, freq = %u, dir = %d\n", __func__,
|
|
clk_id, source, freq, dir);
|
|
|
|
cs35l41->extclk_freq = freq;
|
|
|
|
switch (clk_id) {
|
|
case 0:
|
|
cs35l41->clksrc = CS35L41_PLLSRC_SCLK;
|
|
break;
|
|
case 1:
|
|
cs35l41->clksrc = CS35L41_PLLSRC_LRCLK;
|
|
break;
|
|
case 2:
|
|
cs35l41->clksrc = CS35L41_PLLSRC_PDMCLK;
|
|
break;
|
|
case 3:
|
|
cs35l41->clksrc = CS35L41_PLLSRC_SELF;
|
|
break;
|
|
case 4:
|
|
cs35l41->clksrc = CS35L41_PLLSRC_MCLK;
|
|
break;
|
|
default:
|
|
dev_err(cs35l41->dev, "Invalid CLK Config\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
cs35l41->extclk_cfg = cs35l41_get_clk_config(freq);
|
|
|
|
if (cs35l41->extclk_cfg < 0) {
|
|
dev_err(cs35l41->dev, "Invalid CLK Config: %d, freq: %u\n",
|
|
cs35l41->extclk_cfg, freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cs35l41->reset_cache.extclk_cfg = true;
|
|
/* Amp is in reset. Set flag to restore clock config */
|
|
if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_STANDBY)
|
|
return 0;
|
|
|
|
regmap_read(cs35l41->regmap, CS35L41_PLL_CLK_CTRL, &freq);
|
|
freq &= (CS35L41_REFCLK_FREQ_MASK | CS35L41_PLL_CLK_EN_MASK |
|
|
CS35L41_PLL_CLK_SEL_MASK);
|
|
if ((cs35l41->extclk_cfg << CS35L41_REFCLK_FREQ_SHIFT |
|
|
CS35L41_PLL_CLK_EN_MASK | cs35l41->clksrc) == freq) {
|
|
dev_dbg(cs35l41->dev, "%s: Ignore duplicate PLL setting\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_OPENLOOP_MASK,
|
|
1 << CS35L41_PLL_OPENLOOP_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_REFCLK_FREQ_MASK,
|
|
cs35l41->extclk_cfg << CS35L41_REFCLK_FREQ_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_EN_MASK,
|
|
0 << CS35L41_PLL_CLK_EN_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_SEL_MASK, cs35l41->clksrc);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_OPENLOOP_MASK,
|
|
0 << CS35L41_PLL_OPENLOOP_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_EN_MASK,
|
|
1 << CS35L41_PLL_CLK_EN_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
unsigned int freq, int dir)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(dai->component);
|
|
unsigned int fs1_val = 0;
|
|
unsigned int fs2_val = 0;
|
|
unsigned int val;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: clk_id = %d, freq = %u, dir = %d\n",
|
|
__func__, clk_id, freq, dir);
|
|
|
|
if (cs35l41_get_clk_config(freq) < 0) {
|
|
dev_err(cs35l41->dev, "Invalid CLK Config freq: %u\n", freq);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Need the SCLK Frequency regardless of sysclk source */
|
|
cs35l41->sclk = freq;
|
|
|
|
dev_dbg(cs35l41->dev, "Set DAI sysclk %d\n", freq);
|
|
if (cs35l41->sclk > 6000000) {
|
|
fs1_val = 3 * 4 + 4;
|
|
fs2_val = 8 * 4 + 4;
|
|
}
|
|
|
|
if (cs35l41->sclk <= 6000000) {
|
|
fs1_val = 3 * ((24000000 + cs35l41->sclk - 1) / cs35l41->sclk) +
|
|
4;
|
|
fs2_val = 5 * ((24000000 + cs35l41->sclk - 1) / cs35l41->sclk) +
|
|
4;
|
|
}
|
|
|
|
val = fs1_val;
|
|
val |= (fs2_val << CS35L41_FS2_WINDOW_SHIFT) & CS35L41_FS2_WINDOW_MASK;
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000055);
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000AA);
|
|
regmap_write(cs35l41->regmap, CS35L41_TST_FS_MON0, val);
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000CC);
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000033);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_boost_config(struct cs35l41_private *cs35l41, int boost_ind,
|
|
int boost_cap, int boost_ipk)
|
|
{
|
|
int ret;
|
|
unsigned char bst_lbst_val, bst_cbst_range, bst_ipk_scaled;
|
|
struct regmap *regmap = cs35l41->regmap;
|
|
struct device *dev = cs35l41->dev;
|
|
|
|
switch (boost_ind) {
|
|
case 1000: /* 1.0 uH */
|
|
bst_lbst_val = 0;
|
|
break;
|
|
case 1200: /* 1.2 uH */
|
|
bst_lbst_val = 1;
|
|
break;
|
|
case 1500: /* 1.5 uH */
|
|
bst_lbst_val = 2;
|
|
break;
|
|
case 2200: /* 2.2 uH */
|
|
bst_lbst_val = 3;
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid boost inductor value: %d nH\n",
|
|
boost_ind);
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (boost_cap) {
|
|
case 0 ... 19:
|
|
bst_cbst_range = 0;
|
|
break;
|
|
case 20 ... 50:
|
|
bst_cbst_range = 1;
|
|
break;
|
|
case 51 ... 100:
|
|
bst_cbst_range = 2;
|
|
break;
|
|
case 101 ... 200:
|
|
bst_cbst_range = 3;
|
|
break;
|
|
default: /* 201 uF and greater */
|
|
bst_cbst_range = 4;
|
|
}
|
|
|
|
ret = regmap_update_bits(
|
|
regmap, CS35L41_BSTCVRT_COEFF, CS35L41_BST_K1_MASK,
|
|
cs35l41_bst_k1_table[bst_lbst_val][bst_cbst_range]
|
|
<< CS35L41_BST_K1_SHIFT);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write boost K1 coefficient\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_update_bits(
|
|
regmap, CS35L41_BSTCVRT_COEFF, CS35L41_BST_K2_MASK,
|
|
cs35l41_bst_k2_table[bst_lbst_val][bst_cbst_range]
|
|
<< CS35L41_BST_K2_SHIFT);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write boost K2 coefficient\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_SLOPE_LBST,
|
|
CS35L41_BST_SLOPE_MASK,
|
|
cs35l41_bst_slope_table[bst_lbst_val]
|
|
<< CS35L41_BST_SLOPE_SHIFT);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write boost slope coefficient\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_SLOPE_LBST,
|
|
CS35L41_BST_LBST_VAL_MASK,
|
|
bst_lbst_val << CS35L41_BST_LBST_VAL_SHIFT);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write boost inductor value\n");
|
|
return ret;
|
|
}
|
|
|
|
if ((boost_ipk < 1600) || (boost_ipk > 4500)) {
|
|
dev_err(dev, "Invalid boost inductor peak current: %d mA\n",
|
|
boost_ipk);
|
|
return -EINVAL;
|
|
}
|
|
bst_ipk_scaled = ((boost_ipk - 1600) / 50) + 0x10;
|
|
|
|
ret = regmap_update_bits(regmap, CS35L41_BSTCVRT_PEAK_CUR,
|
|
CS35L41_BST_IPK_MASK,
|
|
bst_ipk_scaled << CS35L41_BST_IPK_SHIFT);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to write boost inductor peak current\n");
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_set_pdata(struct cs35l41_private *cs35l41)
|
|
{
|
|
struct classh_cfg *classh = &cs35l41->pdata.classh_config;
|
|
int ret;
|
|
u32 devid_otp;
|
|
|
|
/* Set Platform Data */
|
|
/* Required */
|
|
if (cs35l41->pdata.bst_ipk && cs35l41->pdata.bst_ind &&
|
|
cs35l41->pdata.bst_cap) {
|
|
ret = cs35l41_boost_config(cs35l41, cs35l41->pdata.bst_ind,
|
|
cs35l41->pdata.bst_cap,
|
|
cs35l41->pdata.bst_ipk);
|
|
if (ret) {
|
|
dev_err(cs35l41->dev, "Error in Boost DT config\n");
|
|
return ret;
|
|
}
|
|
} else {
|
|
dev_err(cs35l41->dev, "Incomplete Boost component DT config\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Optional */
|
|
if (cs35l41->pdata.sclk_frc)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_SCLK_FRC_MASK,
|
|
cs35l41->pdata.sclk_frc
|
|
<< CS35L41_SCLK_FRC_SHIFT);
|
|
|
|
if (cs35l41->pdata.lrclk_frc)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_LRCLK_FRC_MASK,
|
|
cs35l41->pdata.lrclk_frc
|
|
<< CS35L41_LRCLK_FRC_SHIFT);
|
|
|
|
if (cs35l41->pdata.amp_gain_zc)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_AMP_GAIN_CTRL,
|
|
CS35L41_AMP_GAIN_ZC_MASK,
|
|
cs35l41->pdata.amp_gain_zc
|
|
<< CS35L41_AMP_GAIN_ZC_SHIFT);
|
|
|
|
if (cs35l41->pdata.bst_vctrl)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_BSTCVRT_VCTRL1,
|
|
CS35L41_BST_CTL_MASK,
|
|
cs35l41->pdata.bst_vctrl);
|
|
|
|
if (cs35l41->pdata.temp_warn_thld)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_DTEMP_WARN_THLD,
|
|
CS35L41_TEMP_THLD_MASK,
|
|
cs35l41->pdata.temp_warn_thld);
|
|
|
|
if (cs35l41->pdata.dout_hiz <= CS35L41_ASP_DOUT_HIZ_MASK &&
|
|
cs35l41->pdata.dout_hiz >= 0)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_HIZ_CTRL,
|
|
CS35L41_ASP_DOUT_HIZ_MASK,
|
|
cs35l41->pdata.dout_hiz);
|
|
|
|
if (cs35l41->pdata.invert_pcm)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_AMP_DIG_VOL_CTRL,
|
|
CS35L41_AMP_INV_PCM_MASK,
|
|
cs35l41->pdata.invert_pcm
|
|
<< CS35L41_AMP_INV_PCM_SHIFT);
|
|
|
|
if (cs35l41->pdata.dsp_ng_enable) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_MIXER_NGATE_CH1_CFG,
|
|
CS35L41_DSP_NG_ENABLE_MASK,
|
|
CS35L41_DSP_NG_ENABLE_MASK);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_MIXER_NGATE_CH2_CFG,
|
|
CS35L41_DSP_NG_ENABLE_MASK,
|
|
CS35L41_DSP_NG_ENABLE_MASK);
|
|
|
|
if (cs35l41->pdata.dsp_ng_pcm_thld) {
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_MIXER_NGATE_CH1_CFG,
|
|
CS35L41_DSP_NG_THLD_MASK,
|
|
cs35l41->pdata.dsp_ng_pcm_thld);
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_MIXER_NGATE_CH2_CFG,
|
|
CS35L41_DSP_NG_THLD_MASK,
|
|
cs35l41->pdata.dsp_ng_pcm_thld);
|
|
}
|
|
|
|
if (cs35l41->pdata.dsp_ng_delay) {
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_MIXER_NGATE_CH1_CFG,
|
|
CS35L41_DSP_NG_DELAY_MASK,
|
|
cs35l41->pdata.dsp_ng_delay
|
|
<< CS35L41_DSP_NG_DELAY_SHIFT);
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_MIXER_NGATE_CH2_CFG,
|
|
CS35L41_DSP_NG_DELAY_MASK,
|
|
cs35l41->pdata.dsp_ng_delay
|
|
<< CS35L41_DSP_NG_DELAY_SHIFT);
|
|
}
|
|
}
|
|
|
|
if (cs35l41->pdata.hw_ng_sel)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_NG_CFG, CS35L41_HW_NG_SEL_MASK,
|
|
cs35l41->pdata.hw_ng_sel << CS35L41_HW_NG_SEL_SHIFT);
|
|
|
|
if (cs35l41->pdata.hw_ng_thld)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_NG_CFG,
|
|
CS35L41_HW_NG_THLD_MASK,
|
|
cs35l41->pdata.hw_ng_thld
|
|
<< CS35L41_HW_NG_THLD_SHIFT);
|
|
|
|
if (cs35l41->pdata.hw_ng_delay)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_NG_CFG, CS35L41_HW_NG_DLY_MASK,
|
|
cs35l41->pdata.hw_ng_delay << CS35L41_HW_NG_DLY_SHIFT);
|
|
|
|
if (regmap_read(cs35l41->regmap, CS35L41_DEVID_OTP, &devid_otp) < 0) {
|
|
dev_err(cs35l41->dev, "Read DEVID OTP failed\n");
|
|
return -EINVAL;
|
|
}
|
|
if (classh->classh_algo_enable) {
|
|
if (classh->classh_bst_override &&
|
|
devid_otp != CS35L41LV_CHIP_ID)
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_BSTCVRT_VCTRL2,
|
|
CS35L41_BST_CTL_SEL_MASK,
|
|
CS35L41_BST_CTL_SEL_REG);
|
|
if (classh->classh_bst_max_limit &&
|
|
devid_otp != CS35L41LV_CHIP_ID)
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_BSTCVRT_VCTRL2,
|
|
CS35L41_BST_LIM_MASK,
|
|
classh->classh_bst_max_limit
|
|
<< CS35L41_BST_LIM_SHIFT);
|
|
if (classh->classh_mem_depth)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_CLASSH_CFG,
|
|
CS35L41_CH_MEM_DEPTH_MASK,
|
|
classh->classh_mem_depth
|
|
<< CS35L41_CH_MEM_DEPTH_SHIFT);
|
|
if (classh->classh_headroom)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_CLASSH_CFG,
|
|
CS35L41_CH_HDRM_CTL_MASK,
|
|
classh->classh_headroom
|
|
<< CS35L41_CH_HDRM_CTL_SHIFT);
|
|
if (classh->classh_release_rate)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_CLASSH_CFG,
|
|
CS35L41_CH_REL_RATE_MASK,
|
|
classh->classh_release_rate
|
|
<< CS35L41_CH_REL_RATE_SHIFT);
|
|
if (classh->classh_wk_fet_delay)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_WKFET_CFG,
|
|
CS35L41_CH_WKFET_DLY_MASK,
|
|
classh->classh_wk_fet_delay
|
|
<< CS35L41_CH_WKFET_DLY_SHIFT);
|
|
if (classh->classh_wk_fet_thld)
|
|
regmap_update_bits(
|
|
cs35l41->regmap, CS35L41_WKFET_CFG,
|
|
CS35L41_CH_WKFET_THLD_MASK,
|
|
classh->classh_wk_fet_thld
|
|
<< CS35L41_CH_WKFET_THLD_SHIFT);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_component_probe(struct snd_soc_component *component)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
struct snd_kcontrol_new *kcontrol;
|
|
int ret = 0;
|
|
u32 devid_otp;
|
|
|
|
component->regmap = cs35l41->regmap;
|
|
|
|
cs35l41_set_pdata(cs35l41);
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID_OTP, &devid_otp);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read DEVID OTP failed\n");
|
|
ret = -EINVAL;
|
|
goto exit;
|
|
}
|
|
if (devid_otp == CS35L41LV_CHIP_ID)
|
|
/* BST_CTL_LIM_EN = 1 BST_CTL_SEL = b'01 (Class H tracking) */
|
|
regmap_write(cs35l41->regmap, CS35L41_BSTCVRT_VCTRL2, 0x5);
|
|
|
|
/* These should only run once, not every hibernate cycle */
|
|
if (!(cs35l41->skip_codec_probe)) {
|
|
wm_adsp2_component_probe(&cs35l41->dsp, component);
|
|
cs35l41->skip_codec_probe = true;
|
|
|
|
/* Add run-time mixer control for fast use case switch */
|
|
kcontrol = kzalloc(sizeof(*kcontrol), GFP_KERNEL);
|
|
if (!kcontrol) {
|
|
ret = -ENOMEM;
|
|
goto exit;
|
|
}
|
|
|
|
kcontrol->name = "Fast Use Case Delta File";
|
|
kcontrol->iface = SNDRV_CTL_ELEM_IFACE_MIXER;
|
|
kcontrol->info = snd_soc_info_enum_double;
|
|
kcontrol->get = cs35l41_fast_switch_file_get;
|
|
kcontrol->put = cs35l41_fast_switch_file_put;
|
|
kcontrol->private_value =
|
|
(unsigned long)&cs35l41->fast_switch_enum;
|
|
ret = snd_soc_add_component_controls(component, kcontrol, 1);
|
|
if (ret < 0)
|
|
dev_err(cs35l41->dev,
|
|
"snd_soc_add_codec_controls failed (%d)\n",
|
|
ret);
|
|
|
|
dev_dbg(cs35l41->dev, "queue boot_work\n");
|
|
queue_work(cs35l41->dsp.work_queue, &cs35l41->dsp.boot_work);
|
|
kfree(kcontrol);
|
|
}
|
|
exit:
|
|
dev_dbg(cs35l41->dev, "cs35l41_component_probe: X\n");
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_irq_gpio_config(struct cs35l41_private *cs35l41)
|
|
{
|
|
struct irq_cfg *irq_gpio_cfg1 = &cs35l41->pdata.irq_config1;
|
|
struct irq_cfg *irq_gpio_cfg2 = &cs35l41->pdata.irq_config2;
|
|
int irq_pol = IRQF_TRIGGER_NONE;
|
|
|
|
if (irq_gpio_cfg1->is_present) {
|
|
if (irq_gpio_cfg1->irq_pol_inv)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GPIO1_CTRL1,
|
|
CS35L41_GPIO_POL_MASK,
|
|
CS35L41_GPIO_POL_MASK);
|
|
if (irq_gpio_cfg1->irq_out_en)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GPIO1_CTRL1,
|
|
CS35L41_GPIO_DIR_MASK, 0);
|
|
if (irq_gpio_cfg1->irq_src_sel)
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_GPIO_PAD_CONTROL,
|
|
CS35L41_GPIO1_CTRL_MASK,
|
|
irq_gpio_cfg1->irq_src_sel
|
|
<< CS35L41_GPIO1_CTRL_SHIFT);
|
|
}
|
|
|
|
if (irq_gpio_cfg2->is_present) {
|
|
if (irq_gpio_cfg2->irq_pol_inv)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GPIO2_CTRL1,
|
|
CS35L41_GPIO_POL_MASK,
|
|
CS35L41_GPIO_POL_MASK);
|
|
if (irq_gpio_cfg2->irq_out_en)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GPIO2_CTRL1,
|
|
CS35L41_GPIO_DIR_MASK, 0);
|
|
if (irq_gpio_cfg2->irq_src_sel)
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_GPIO_PAD_CONTROL,
|
|
CS35L41_GPIO2_CTRL_MASK,
|
|
irq_gpio_cfg2->irq_src_sel
|
|
<< CS35L41_GPIO2_CTRL_SHIFT);
|
|
}
|
|
|
|
if (irq_gpio_cfg2->irq_src_sel ==
|
|
(CS35L41_GPIO_CTRL_ACTV_LO | CS35L41_VALID_PDATA))
|
|
irq_pol = IRQF_TRIGGER_LOW;
|
|
else if (irq_gpio_cfg2->irq_src_sel ==
|
|
(CS35L41_GPIO_CTRL_ACTV_HI | CS35L41_VALID_PDATA))
|
|
irq_pol = IRQF_TRIGGER_HIGH;
|
|
|
|
return irq_pol;
|
|
}
|
|
|
|
static void cs35l41_component_remove(struct snd_soc_component *component)
|
|
{
|
|
struct cs35l41_private *cs35l41 =
|
|
snd_soc_component_get_drvdata(component);
|
|
|
|
wm_adsp2_component_remove(&cs35l41->dsp, component);
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops cs35l41_ops = {
|
|
.startup = cs35l41_pcm_startup,
|
|
.set_fmt = cs35l41_set_dai_fmt,
|
|
.hw_params = cs35l41_pcm_hw_params,
|
|
.set_sysclk = cs35l41_dai_set_sysclk,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver cs35l41_dai[] = {
|
|
{
|
|
.name = "cs35l41-pcm",
|
|
.id = 0,
|
|
.playback = {
|
|
.stream_name = "AMP Playback",
|
|
.channels_min = 1,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_KNOT,
|
|
.formats = CS35L41_RX_FORMATS,
|
|
},
|
|
.capture = {
|
|
.stream_name = "AMP Capture",
|
|
.channels_min = 1,
|
|
.channels_max = 8,
|
|
.rates = SNDRV_PCM_RATE_KNOT,
|
|
.formats = CS35L41_TX_FORMATS,
|
|
},
|
|
.ops = &cs35l41_ops,
|
|
.symmetric_rates = 1,
|
|
},
|
|
};
|
|
|
|
static struct snd_soc_component_driver soc_component_dev_cs35l41 = {
|
|
.probe = cs35l41_component_probe,
|
|
.remove = cs35l41_component_remove,
|
|
|
|
.dapm_widgets = cs35l41_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(cs35l41_dapm_widgets),
|
|
|
|
.set_sysclk = cs35l41_component_set_sysclk,
|
|
};
|
|
|
|
static int cs35l41_handle_of_data(struct device *dev,
|
|
struct cs35l41_platform_data *pdata,
|
|
struct cs35l41_private *cs35l41)
|
|
{
|
|
struct device_node *np = dev->of_node;
|
|
unsigned int val;
|
|
int ret;
|
|
size_t num_fast_switch;
|
|
struct device_node *sub_node;
|
|
struct classh_cfg *classh_config = &pdata->classh_config;
|
|
struct irq_cfg *irq_gpio1_config = &pdata->irq_config1;
|
|
struct irq_cfg *irq_gpio2_config = &pdata->irq_config2;
|
|
unsigned int i;
|
|
|
|
if (!np)
|
|
return 0;
|
|
|
|
ret = of_property_count_strings(np, "cirrus,fast-switch");
|
|
if (ret < 0) {
|
|
/*
|
|
* device tree do not provide file name.
|
|
* Use default value
|
|
*/
|
|
num_fast_switch = ARRAY_SIZE(cs35l41_fast_switch_text);
|
|
cs35l41->fast_switch_enum.items =
|
|
ARRAY_SIZE(cs35l41_fast_switch_text);
|
|
cs35l41->fast_switch_enum.texts = cs35l41_fast_switch_text;
|
|
cs35l41->fast_switch_names = cs35l41_fast_switch_text;
|
|
} else {
|
|
/* Device tree provides file name */
|
|
num_fast_switch = (size_t)ret;
|
|
dev_info(dev, "num_fast_switch:%zu\n", num_fast_switch);
|
|
cs35l41->fast_switch_names = devm_kmalloc(
|
|
dev, num_fast_switch * sizeof(char *), GFP_KERNEL);
|
|
if (!cs35l41->fast_switch_names)
|
|
return -ENOMEM;
|
|
of_property_read_string_array(np, "cirrus,fast-switch",
|
|
cs35l41->fast_switch_names,
|
|
num_fast_switch);
|
|
for (i = 0; i < num_fast_switch; i++) {
|
|
dev_info(dev, "%d:%s\n", i,
|
|
cs35l41->fast_switch_names[i]);
|
|
}
|
|
cs35l41->fast_switch_enum.items = num_fast_switch;
|
|
cs35l41->fast_switch_enum.texts = cs35l41->fast_switch_names;
|
|
}
|
|
|
|
cs35l41->fast_switch_enum.reg = SND_SOC_NOPM;
|
|
cs35l41->fast_switch_enum.shift_l = 0;
|
|
cs35l41->fast_switch_enum.shift_r = 0;
|
|
cs35l41->fast_switch_enum.mask =
|
|
roundup_pow_of_two(num_fast_switch) - 1;
|
|
|
|
pdata->right_channel =
|
|
of_property_read_bool(np, "cirrus,right-channel-amp");
|
|
pdata->sclk_frc = of_property_read_bool(np, "cirrus,sclk-force-output");
|
|
pdata->lrclk_frc =
|
|
of_property_read_bool(np, "cirrus,lrclk-force-output");
|
|
pdata->amp_gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc");
|
|
pdata->tuning_has_prefix =
|
|
of_property_read_bool(np, "cirrus,tuning-has-prefix");
|
|
pdata->invert_pcm = of_property_read_bool(np, "cirrus,invert-pcm");
|
|
|
|
pdata->fwname_use_revid =
|
|
of_property_read_bool(np, "cirrus,fwname-use-revid");
|
|
|
|
if (of_property_read_u32(np, "cirrus,temp-warn_threshold", &val) >= 0)
|
|
pdata->temp_warn_thld = val | CS35L41_VALID_PDATA;
|
|
|
|
ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val);
|
|
if (ret >= 0) {
|
|
if (val < 2550 || val > 11000) {
|
|
dev_err(dev, "Invalid Boost Voltage %u mV\n", val);
|
|
return -EINVAL;
|
|
}
|
|
pdata->bst_vctrl = ((val - 2550) / 100) + 1;
|
|
}
|
|
|
|
ret = of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val);
|
|
if (ret >= 0)
|
|
pdata->bst_ipk = val;
|
|
|
|
ret = of_property_read_u32(np, "cirrus,boost-ind-nanohenry", &val);
|
|
if (ret >= 0)
|
|
pdata->bst_ind = val;
|
|
|
|
ret = of_property_read_u32(np, "cirrus,boost-cap-microfarad", &val);
|
|
if (ret >= 0)
|
|
pdata->bst_cap = val;
|
|
|
|
ret = of_property_read_u32(np, "cirrus,asp-sdout-hiz", &val);
|
|
if (ret >= 0)
|
|
pdata->dout_hiz = val;
|
|
else
|
|
pdata->dout_hiz = -1;
|
|
|
|
pdata->dsp_ng_enable =
|
|
of_property_read_bool(np, "cirrus,dsp-noise-gate-enable");
|
|
if (of_property_read_u32(np, "cirrus,dsp-noise-gate-threshold", &val) >=
|
|
0)
|
|
pdata->dsp_ng_pcm_thld = val | CS35L41_VALID_PDATA;
|
|
if (of_property_read_u32(np, "cirrus,dsp-noise-gate-delay", &val) >= 0)
|
|
pdata->dsp_ng_delay = val | CS35L41_VALID_PDATA;
|
|
|
|
if (of_property_read_u32(np, "cirrus,hw-noise-gate-select", &val) >= 0)
|
|
pdata->hw_ng_sel = val | CS35L41_VALID_PDATA;
|
|
if (of_property_read_u32(np, "cirrus,hw-noise-gate-threshold", &val) >=
|
|
0)
|
|
pdata->hw_ng_thld = val | CS35L41_VALID_PDATA;
|
|
if (of_property_read_u32(np, "cirrus,hw-noise-gate-delay", &val) >= 0)
|
|
pdata->hw_ng_delay = val | CS35L41_VALID_PDATA;
|
|
|
|
sub_node = of_get_child_by_name(np, "cirrus,classh-internal-algo");
|
|
classh_config->classh_algo_enable = sub_node ? true : false;
|
|
|
|
if (classh_config->classh_algo_enable) {
|
|
classh_config->classh_bst_override = of_property_read_bool(
|
|
sub_node, "cirrus,classh-bst-overide");
|
|
|
|
ret = of_property_read_u32(sub_node,
|
|
"cirrus,classh-bst-max-limit", &val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
classh_config->classh_bst_max_limit = val;
|
|
}
|
|
|
|
ret = of_property_read_u32(sub_node, "cirrus,classh-mem-depth",
|
|
&val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
classh_config->classh_mem_depth = val;
|
|
}
|
|
|
|
ret = of_property_read_u32(sub_node,
|
|
"cirrus,classh-release-rate", &val);
|
|
if (ret >= 0)
|
|
classh_config->classh_release_rate = val;
|
|
|
|
ret = of_property_read_u32(sub_node, "cirrus,classh-headroom",
|
|
&val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
classh_config->classh_headroom = val;
|
|
}
|
|
|
|
ret = of_property_read_u32(sub_node,
|
|
"cirrus,classh-wk-fet-delay", &val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
classh_config->classh_wk_fet_delay = val;
|
|
}
|
|
|
|
ret = of_property_read_u32(sub_node,
|
|
"cirrus,classh-wk-fet-thld", &val);
|
|
if (ret >= 0)
|
|
classh_config->classh_wk_fet_thld = val;
|
|
}
|
|
of_node_put(sub_node);
|
|
|
|
/* GPIO1 Pin Config */
|
|
sub_node = of_get_child_by_name(np, "cirrus,gpio-config1");
|
|
irq_gpio1_config->is_present = sub_node ? true : false;
|
|
if (irq_gpio1_config->is_present) {
|
|
irq_gpio1_config->irq_pol_inv = of_property_read_bool(
|
|
sub_node, "cirrus,gpio-polarity-invert");
|
|
irq_gpio1_config->irq_out_en = of_property_read_bool(
|
|
sub_node, "cirrus,gpio-output-enable");
|
|
ret = of_property_read_u32(sub_node, "cirrus,gpio-src-select",
|
|
&val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
irq_gpio1_config->irq_src_sel = val;
|
|
}
|
|
}
|
|
of_node_put(sub_node);
|
|
|
|
/* GPIO2 Pin Config */
|
|
sub_node = of_get_child_by_name(np, "cirrus,gpio-config2");
|
|
irq_gpio2_config->is_present = sub_node ? true : false;
|
|
if (irq_gpio2_config->is_present) {
|
|
irq_gpio2_config->irq_pol_inv = of_property_read_bool(
|
|
sub_node, "cirrus,gpio-polarity-invert");
|
|
irq_gpio2_config->irq_out_en = of_property_read_bool(
|
|
sub_node, "cirrus,gpio-output-enable");
|
|
ret = of_property_read_u32(sub_node, "cirrus,gpio-src-select",
|
|
&val);
|
|
if (ret >= 0) {
|
|
val |= CS35L41_VALID_PDATA;
|
|
irq_gpio2_config->irq_src_sel = val;
|
|
}
|
|
}
|
|
of_node_put(sub_node);
|
|
|
|
pdata->hibernate_enable =
|
|
of_property_read_bool(np, "cirrus,hibernate-enable");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct reg_sequence cs35l41_reva0_errata_patch[] = {
|
|
{ 0x00000040, 0x00005555 },
|
|
{ 0x00000040, 0x0000AAAA },
|
|
{ 0x00003854, 0x05180240 },
|
|
{ CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 },
|
|
{ 0x00004310, 0x00000000 },
|
|
{ CS35L41_VPVBST_FS_SEL, 0x00000000 },
|
|
{ CS35L41_OTP_TRIM_30, 0x9091A1C8 },
|
|
{ 0x00003014, 0x0200EE0E },
|
|
{ CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 },
|
|
{ 0x00000054, 0x00000004 },
|
|
{ CS35L41_IRQ1_DB3, 0x00000000 },
|
|
{ CS35L41_IRQ2_DB3, 0x00000000 },
|
|
{ CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ 0x00000040, 0x0000CCCC },
|
|
{ 0x00000040, 0x00003333 },
|
|
};
|
|
|
|
static const struct reg_sequence cs35l41_revb0_errata_patch[] = {
|
|
{ 0x00000040, 0x00005555 },
|
|
{ 0x00000040, 0x0000AAAA },
|
|
{ CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 },
|
|
{ 0x00004310, 0x00000000 },
|
|
{ CS35L41_VPVBST_FS_SEL, 0x00000000 },
|
|
{ CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 },
|
|
{ CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ 0x00000040, 0x0000CCCC },
|
|
{ 0x00000040, 0x00003333 },
|
|
};
|
|
|
|
static const struct reg_sequence cs35l41_revb2_errata_patch[] = {
|
|
{ 0x00000040, 0x00005555 },
|
|
{ 0x00000040, 0x0000AAAA },
|
|
{ CS35L41_VIMON_SPKMON_RESYNC, 0x00000000 },
|
|
{ 0x00004310, 0x00000000 },
|
|
{ CS35L41_VPVBST_FS_SEL, 0x00000000 },
|
|
{ CS35L41_BSTCVRT_DCM_CTRL, 0x00000051 },
|
|
{ CS35L41_DSP1_YM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ CS35L41_DSP1_XM_ACCEL_PL0_PRI, 0x00000000 },
|
|
{ 0x00000040, 0x0000CCCC },
|
|
{ 0x00000040, 0x00003333 },
|
|
};
|
|
|
|
static int cs35l41_dsp_init(struct cs35l41_private *cs35l41)
|
|
{
|
|
struct wm_adsp *dsp;
|
|
int ret, i;
|
|
|
|
dsp = &cs35l41->dsp;
|
|
dsp->num = 1;
|
|
dsp->type = WMFW_HALO;
|
|
dsp->rev = 0;
|
|
dsp->dev = cs35l41->dev;
|
|
dsp->regmap = cs35l41->regmap;
|
|
dsp->tuning_has_prefix = cs35l41->pdata.tuning_has_prefix;
|
|
|
|
if (!cs35l41->pdata.fwname_use_revid)
|
|
dsp->part = "cs35l41";
|
|
|
|
dsp->base = CS35L41_DSP1_CTRL_BASE;
|
|
dsp->base_sysinfo = CS35L41_DSP1_SYS_ID;
|
|
dsp->mem = cs35l41_dsp1_regions;
|
|
dsp->num_mems = ARRAY_SIZE(cs35l41_dsp1_regions);
|
|
dsp->lock_regions = 0xFFFFFFFF;
|
|
|
|
dsp->n_rx_channels = CS35L41_DSP_N_RX_RATES;
|
|
dsp->n_tx_channels = CS35L41_DSP_N_TX_RATES;
|
|
|
|
mutex_init(&cs35l41->rate_lock);
|
|
ret = wm_halo_init(dsp, &cs35l41->rate_lock);
|
|
cs35l41->halo_booted = false;
|
|
|
|
for (i = 0; i < CS35L41_DSP_N_RX_RATES; i++)
|
|
dsp->rx_rate_cache[i] = 0x1;
|
|
for (i = 0; i < CS35L41_DSP_N_TX_RATES; i++)
|
|
dsp->tx_rate_cache[i] = 0x1;
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX5_SRC,
|
|
CS35L41_INPUT_SRC_VPMON);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX6_SRC,
|
|
CS35L41_INPUT_SRC_CLASSH);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX7_SRC,
|
|
CS35L41_INPUT_SRC_TEMPMON);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX8_SRC,
|
|
CS35L41_INPUT_SRC_RSVD);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_enter_hibernate(struct cs35l41_private *cs35l41)
|
|
{
|
|
int i;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: hibernate state %d\n", __func__,
|
|
cs35l41->amp_hibernate);
|
|
|
|
if (cs35l41->amp_hibernate == CS35L41_HIBERNATE_STANDBY)
|
|
return 0;
|
|
|
|
/* read all ctl regs */
|
|
for (i = 0; i < CS35L41_CTRL_CACHE_SIZE; i++)
|
|
regmap_read(cs35l41->regmap, cs35l41_ctl_cache_regs[i],
|
|
&cs35l41->ctl_cache[i]);
|
|
|
|
/* Disable interrupts */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
|
|
disable_irq(cs35l41->irq);
|
|
|
|
/* Reset DSP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ2_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT);
|
|
|
|
/* Reset AP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_FW_SHIFT);
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088);
|
|
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188);
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_CSPL_MBOX_CMD_DRV,
|
|
CSPL_MBOX_CMD_HIBERNATE);
|
|
|
|
regcache_cache_only(cs35l41->regmap, true);
|
|
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_STANDBY;
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_wait_for_pwrmgt_sts(struct cs35l41_private *cs35l41)
|
|
{
|
|
int i, ret = 0;
|
|
unsigned int wrpend_sts = 0x2;
|
|
|
|
for (i = 0; (i < 10) && (wrpend_sts & 0x2); i++)
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_PWRMGT_STS,
|
|
&wrpend_sts);
|
|
return ret;
|
|
}
|
|
|
|
static int cs35l41_exit_hibernate(struct cs35l41_private *cs35l41)
|
|
{
|
|
int timeout = 10, ret;
|
|
unsigned int status;
|
|
int retries = 5, i;
|
|
u32 *p_trim_data;
|
|
|
|
dev_dbg(cs35l41->dev, "%s: hibernate state %d\n", __func__,
|
|
cs35l41->amp_hibernate);
|
|
|
|
if (cs35l41->amp_hibernate != CS35L41_HIBERNATE_STANDBY)
|
|
return 0;
|
|
|
|
/* update any regs that changed while in cache-only mode */
|
|
for (i = 0; i < CS35L41_CTRL_CACHE_SIZE; i++)
|
|
regmap_read(cs35l41->regmap, cs35l41_ctl_cache_regs[i],
|
|
&cs35l41->ctl_cache[i]);
|
|
|
|
regcache_cache_only(cs35l41->regmap, false);
|
|
|
|
do {
|
|
do {
|
|
ret = regmap_write(cs35l41->regmap,
|
|
CS35L41_CSPL_MBOX_CMD_DRV,
|
|
CSPL_MBOX_CMD_OUT_OF_HIBERNATE);
|
|
if (ret < 0)
|
|
dev_dbg(cs35l41->dev, "%s: wakeup write fail\n",
|
|
__func__);
|
|
|
|
usleep_range(1000, 1100);
|
|
|
|
ret = regmap_read(cs35l41->regmap,
|
|
CS35L41_CSPL_MBOX_STS, &status);
|
|
if (ret < 0)
|
|
dev_err(cs35l41->dev,
|
|
"%s: mbox status read fail\n",
|
|
__func__);
|
|
|
|
} while (status != CSPL_MBOX_STS_PAUSED && --timeout > 0);
|
|
|
|
if (timeout != 0)
|
|
break;
|
|
|
|
dev_err(cs35l41->dev, "hibernate wake failed\n");
|
|
|
|
cs35l41_wait_for_pwrmgt_sts(cs35l41);
|
|
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0088);
|
|
|
|
cs35l41_wait_for_pwrmgt_sts(cs35l41);
|
|
regmap_write(cs35l41->regmap, CS35L41_WAKESRC_CTL, 0x0188);
|
|
|
|
cs35l41_wait_for_pwrmgt_sts(cs35l41);
|
|
regmap_write(cs35l41->regmap, CS35L41_PWRMGT_CTL, 0x3);
|
|
|
|
timeout = 10;
|
|
|
|
} while (--retries > 0);
|
|
|
|
/* Reset DSP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ2_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_DRV_SHIFT);
|
|
|
|
/* Reset AP sticky bit */
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS2,
|
|
1 << CS35L41_CSPL_MBOX_CMD_FW_SHIFT);
|
|
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_AWAKE;
|
|
|
|
/* invalidate all cached values which have now been reset */
|
|
regcache_drop_region(cs35l41->regmap, CS35L41_DEVID,
|
|
CS35L41_MIXER_NGATE_CH2_CFG);
|
|
|
|
/* sync all control regs to cache value */
|
|
for (i = 0; i < CS35L41_CTRL_CACHE_SIZE; i++)
|
|
regmap_write(cs35l41->regmap, cs35l41_ctl_cache_regs[i],
|
|
cs35l41->ctl_cache[i]);
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000055);
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000AA);
|
|
|
|
/* trim with cache values */
|
|
p_trim_data = cs35l41->trim_cache;
|
|
for (i = 0; i < CS35L41_TRIM_CACHE_REGIONS; i++) {
|
|
regmap_raw_write(cs35l41->regmap,
|
|
cs35l41_trim_cache_regs[i].reg, p_trim_data,
|
|
cs35l41_trim_cache_regs[i].size * sizeof(u32));
|
|
p_trim_data += cs35l41_trim_cache_regs[i].size;
|
|
}
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x000000CC);
|
|
regmap_write(cs35l41->regmap, CS35L41_TEST_KEY_CTL, 0x00000033);
|
|
|
|
retries = 5;
|
|
|
|
do {
|
|
dev_dbg(cs35l41->dev, "cs35l41_restore attempt %d\n",
|
|
6 - retries);
|
|
ret = cs35l41_restore(cs35l41);
|
|
usleep_range(4000, 5000);
|
|
} while (ret < 0 && --retries > 0);
|
|
|
|
if (retries < 0)
|
|
dev_err(cs35l41->dev, "Failed to exit from hibernate\n");
|
|
else
|
|
dev_dbg(cs35l41->dev, "cs35l41 restored in %d attempts\n",
|
|
6 - retries);
|
|
|
|
enable_irq(cs35l41->irq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Restore amp state after hibernate */
|
|
static int cs35l41_restore(struct cs35l41_private *cs35l41)
|
|
{
|
|
int ret;
|
|
u32 regid, reg_revid, mtl_revid, chipid_match;
|
|
u32 devid_otp;
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "%s: Get Device ID fail\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Get Revision ID failed\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
mtl_revid = reg_revid & CS35L41_MTLREVID_MASK;
|
|
chipid_match = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID;
|
|
if (regid != chipid_match && regid != CS35L41LV_CHIP_ID) {
|
|
dev_err(cs35l41->dev,
|
|
"CS35L41 Device ID (%X). Expected ID %X\n", regid,
|
|
chipid_match);
|
|
return -ENODEV;
|
|
}
|
|
|
|
cs35l41_irq_gpio_config(cs35l41);
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
|
|
CS35L41_INT1_MASK_DEFAULT);
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX5_SRC,
|
|
CS35L41_INPUT_SRC_VPMON);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX6_SRC,
|
|
CS35L41_INPUT_SRC_CLASSH);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX7_SRC,
|
|
CS35L41_INPUT_SRC_TEMPMON);
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_RX8_SRC,
|
|
CS35L41_INPUT_SRC_RSVD);
|
|
|
|
switch (reg_revid) {
|
|
case CS35L41_REVID_A0:
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_reva0_errata_patch,
|
|
ARRAY_SIZE(cs35l41_reva0_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply A0 errata patch %d\n", ret);
|
|
}
|
|
break;
|
|
case CS35L41_REVID_B0:
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_revb0_errata_patch,
|
|
ARRAY_SIZE(cs35l41_revb0_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply B0 errata patch %d\n", ret);
|
|
}
|
|
break;
|
|
case CS35L41_REVID_B2:
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_revb2_errata_patch,
|
|
ARRAY_SIZE(cs35l41_revb2_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply B2 errata patch %d\n", ret);
|
|
}
|
|
break;
|
|
}
|
|
|
|
dev_dbg(cs35l41->dev, "Restored CS35L41 (%x), Revision: %02X\n", regid,
|
|
reg_revid);
|
|
|
|
cs35l41_set_pdata(cs35l41);
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID_OTP, &devid_otp);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read DEVID OTP failed\n");
|
|
return -EINVAL;
|
|
}
|
|
if (devid_otp == CS35L41LV_CHIP_ID)
|
|
/* BST_CTL_LIM_EN = 1 BST_CTL_SEL = b'01 (Class H tracking) */
|
|
regmap_write(cs35l41->regmap, CS35L41_BSTCVRT_VCTRL2, 0x5);
|
|
|
|
/* Restore cached values set by ALSA during or before amp reset */
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FRAME_RX_SLOT,
|
|
CS35L41_ASP_RX1_SLOT_MASK,
|
|
((cs35l41->pdata.right_channel) ? 1 : 0)
|
|
<< CS35L41_ASP_RX1_SLOT_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FRAME_RX_SLOT,
|
|
CS35L41_ASP_RX2_SLOT_MASK,
|
|
((cs35l41->pdata.right_channel) ? 0 : 1)
|
|
<< CS35L41_ASP_RX2_SLOT_SHIFT);
|
|
|
|
if (cs35l41->reset_cache.extclk_cfg) {
|
|
/* These values are already cached in cs35l41_private struct */
|
|
|
|
if (cs35l41->clksrc == CS35L41_PLLSRC_SCLK)
|
|
regmap_update_bits(cs35l41->regmap,
|
|
CS35L41_SP_RATE_CTRL, 0x3F,
|
|
cs35l41->extclk_cfg);
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_OPENLOOP_MASK,
|
|
1 << CS35L41_PLL_OPENLOOP_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_REFCLK_FREQ_MASK,
|
|
cs35l41->extclk_cfg
|
|
<< CS35L41_REFCLK_FREQ_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_EN_MASK,
|
|
0 << CS35L41_PLL_CLK_EN_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_SEL_MASK, cs35l41->clksrc);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_OPENLOOP_MASK,
|
|
0 << CS35L41_PLL_OPENLOOP_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_PLL_CLK_CTRL,
|
|
CS35L41_PLL_CLK_EN_MASK,
|
|
1 << CS35L41_PLL_CLK_EN_SHIFT);
|
|
}
|
|
|
|
if (cs35l41->reset_cache.asp_width >= 0) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_WIDTH_RX_MASK,
|
|
cs35l41->reset_cache.asp_width
|
|
<< CS35L41_ASP_WIDTH_RX_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_WIDTH_TX_MASK,
|
|
cs35l41->reset_cache.asp_width
|
|
<< CS35L41_ASP_WIDTH_TX_SHIFT);
|
|
}
|
|
|
|
if (cs35l41->reset_cache.asp_wl >= 0) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_RX_WL,
|
|
CS35L41_ASP_RX_WL_MASK,
|
|
cs35l41->reset_cache.asp_wl
|
|
<< CS35L41_ASP_RX_WL_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_TX_WL,
|
|
CS35L41_ASP_TX_WL_MASK,
|
|
cs35l41->reset_cache.asp_wl
|
|
<< CS35L41_ASP_TX_WL_SHIFT);
|
|
}
|
|
|
|
if (cs35l41->reset_cache.asp_fmt >= 0)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_ASP_FMT_MASK,
|
|
cs35l41->reset_cache.asp_fmt
|
|
<< CS35L41_ASP_FMT_SHIFT);
|
|
|
|
if (cs35l41->reset_cache.lrclk_fmt >= 0)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_LRCLK_INV_MASK,
|
|
cs35l41->reset_cache.lrclk_fmt
|
|
<< CS35L41_LRCLK_INV_SHIFT);
|
|
|
|
if (cs35l41->reset_cache.sclk_fmt >= 0)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_SCLK_INV_MASK,
|
|
cs35l41->reset_cache.sclk_fmt
|
|
<< CS35L41_SCLK_INV_SHIFT);
|
|
|
|
if (cs35l41->reset_cache.slave_mode >= 0) {
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_SCLK_MSTR_MASK,
|
|
cs35l41->reset_cache.slave_mode
|
|
<< CS35L41_SCLK_MSTR_SHIFT);
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_SP_FORMAT,
|
|
CS35L41_LRCLK_MSTR_MASK,
|
|
cs35l41->reset_cache.slave_mode
|
|
<< CS35L41_LRCLK_MSTR_SHIFT);
|
|
}
|
|
|
|
if (cs35l41->reset_cache.fs_cfg >= 0)
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GLOBAL_CLK_CTRL,
|
|
CS35L41_GLOBAL_FS_MASK,
|
|
cs35l41->reset_cache.fs_cfg
|
|
<< CS35L41_GLOBAL_FS_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cs35l41_preset_sample_rate(struct cs35l41_private *cs35l41, int rate)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs35l41_fs_rates); i++) {
|
|
if (rate == cs35l41_fs_rates[i].rate)
|
|
break;
|
|
}
|
|
|
|
if (i >= ARRAY_SIZE(cs35l41_fs_rates)) {
|
|
dev_err(cs35l41->dev, "%s: Invalid sample rate = %d\n",
|
|
__func__, rate);
|
|
return -EINVAL;
|
|
}
|
|
|
|
regmap_update_bits(cs35l41->regmap, CS35L41_GLOBAL_CLK_CTRL,
|
|
CS35L41_GLOBAL_FS_MASK,
|
|
cs35l41_fs_rates[i].fs_cfg
|
|
<< CS35L41_GLOBAL_FS_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int cs35l41_probe(struct cs35l41_private *cs35l41,
|
|
struct cs35l41_platform_data *pdata)
|
|
{
|
|
int ret;
|
|
u32 regid, reg_revid, i, mtl_revid, int_status, chipid_match;
|
|
int timeout = 100;
|
|
int irq_pol = 0;
|
|
u32 *p_trim_data;
|
|
struct snd_kcontrol_new *cs35l41_ctl;
|
|
struct snd_soc_dapm_route *cs35l41_map;
|
|
struct snd_soc_dapm_widget *cs35l41_widgets;
|
|
const struct snd_kcontrol_new *cs35l41_tmp_ctl;
|
|
const struct snd_soc_dapm_route *cs35l41_tmp_map;
|
|
const struct snd_soc_dapm_widget *cs35l41_tmp_widgets;
|
|
int num_cs35l41_tmp_ctl;
|
|
int num_cs35l41_tmp_map;
|
|
int num_cs35l41_tmp_widgets;
|
|
u32 devid_otp;
|
|
|
|
cs35l41->fast_switch_en = false;
|
|
cs35l41->fast_switch_file_idx = 0;
|
|
cs35l41->reload_tuning = false;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cs35l41_supplies); i++)
|
|
cs35l41->supplies[i].supply = cs35l41_supplies[i];
|
|
|
|
cs35l41->num_supplies = ARRAY_SIZE(cs35l41_supplies);
|
|
|
|
ret = devm_regulator_bulk_get(cs35l41->dev, cs35l41->num_supplies,
|
|
cs35l41->supplies);
|
|
if (ret != 0) {
|
|
dev_err(cs35l41->dev, "Failed to request core supplies: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
if (pdata) {
|
|
cs35l41->pdata = *pdata;
|
|
} else if (cs35l41->dev->of_node) {
|
|
ret = cs35l41_handle_of_data(cs35l41->dev, &cs35l41->pdata,
|
|
cs35l41);
|
|
if (ret != 0)
|
|
return ret;
|
|
} else {
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
ret = regulator_bulk_enable(cs35l41->num_supplies, cs35l41->supplies);
|
|
if (ret != 0) {
|
|
dev_err(cs35l41->dev, "Failed to enable core supplies: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
/* returning NULL can be an option if in stereo mode */
|
|
cs35l41->reset_gpio =
|
|
devm_gpiod_get_optional(cs35l41->dev, "reset", GPIOD_OUT_LOW);
|
|
if (IS_ERR(cs35l41->reset_gpio)) {
|
|
ret = PTR_ERR(cs35l41->reset_gpio);
|
|
cs35l41->reset_gpio = NULL;
|
|
if (ret == -EBUSY) {
|
|
dev_info(cs35l41->dev,
|
|
"Reset line busy, assuming shared reset\n");
|
|
} else {
|
|
dev_err(cs35l41->dev, "Failed to get reset GPIO: %d\n",
|
|
ret);
|
|
goto err;
|
|
}
|
|
}
|
|
if (cs35l41->reset_gpio) {
|
|
/* satisfy minimum reset pulse width spec */
|
|
usleep_range(2000, 2100);
|
|
gpiod_set_value_cansleep(cs35l41->reset_gpio, 1);
|
|
}
|
|
|
|
usleep_range(2000, 2100);
|
|
|
|
do {
|
|
if (timeout == 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Timeout waiting for OTP_BOOT_DONE\n");
|
|
ret = -EBUSY;
|
|
goto err;
|
|
}
|
|
usleep_range(1000, 1100);
|
|
regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS4, &int_status);
|
|
timeout--;
|
|
} while (!(int_status & CS35L41_OTP_BOOT_DONE));
|
|
|
|
regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_status);
|
|
if (int_status & CS35L41_OTP_BOOT_ERR) {
|
|
dev_err(cs35l41->dev, "OTP Boot error\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Get Device ID failed\n");
|
|
goto err;
|
|
}
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Get Revision ID failed\n");
|
|
goto err;
|
|
}
|
|
|
|
mtl_revid = reg_revid & CS35L41_MTLREVID_MASK;
|
|
|
|
/* CS35L41 will have even MTLREVID
|
|
* CS35L41R will have odd MTLREVID
|
|
*/
|
|
chipid_match = (mtl_revid % 2) ? CS35L41R_CHIP_ID : CS35L41_CHIP_ID;
|
|
if (regid != chipid_match && regid != CS35L41LV_CHIP_ID) {
|
|
dev_err(cs35l41->dev,
|
|
"CS35L41 Device ID (%X). Expected ID %X\n", regid,
|
|
chipid_match);
|
|
ret = -ENODEV;
|
|
goto err;
|
|
}
|
|
|
|
irq_pol = cs35l41_irq_gpio_config(cs35l41);
|
|
|
|
mutex_init(&cs35l41->vol_ctl.vol_mutex);
|
|
cs35l41->vol_ctl.dig_vol = 0;
|
|
cs35l41->vol_ctl.ramp_init_att = 0;
|
|
cs35l41->vol_ctl.ramp_knee_att = 0;
|
|
cs35l41->vol_ctl.ramp_knee_time = 0;
|
|
cs35l41->vol_ctl.ramp_end_time = 0;
|
|
atomic_set(&cs35l41->vol_ctl.playback, 0);
|
|
atomic_set(&cs35l41->vol_ctl.vol_ramp, 0);
|
|
atomic_set(&cs35l41->vol_ctl.manual_ramp, 0);
|
|
atomic_set(&cs35l41->vol_ctl.ramp_abort, 0);
|
|
cs35l41->vol_ctl.auto_ramp_timeout = 0;
|
|
cs35l41->vol_ctl.output_dev = CS35L41_OUTPUT_DEV_SPK;
|
|
cs35l41->vol_ctl.prev_active_dev = CS35L41_OUTPUT_DEV_SPK;
|
|
cs35l41->vol_ctl.ramp_wq =
|
|
create_singlethread_workqueue("cs35l41_ramp");
|
|
INIT_WORK(&cs35l41->vol_ctl.ramp_work, cs35l41_vol_ramp);
|
|
|
|
ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL,
|
|
cs35l41_irq,
|
|
IRQF_ONESHOT | IRQF_SHARED | irq_pol,
|
|
"cs35l41", cs35l41);
|
|
|
|
/* CS35L41 needs INT for PDN_DONE */
|
|
if (ret != 0) {
|
|
dev_err(cs35l41->dev, "Failed to request IRQ: %d\n", ret);
|
|
//goto err;
|
|
}
|
|
|
|
/* Set interrupt masks for critical errors */
|
|
#if defined(BRINGUP_IRQ_VERIFY)
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
|
|
CS35L41_INT1_MASK_DEFAULT & CS35L41_INT1_UNMASK_PUP);
|
|
#else
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1,
|
|
CS35L41_INT1_MASK_DEFAULT);
|
|
#endif
|
|
|
|
mutex_init(&cs35l41->force_int_lock);
|
|
|
|
switch (reg_revid) {
|
|
case CS35L41_REVID_A0:
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_INCOMPATIBLE;
|
|
if (cs35l41->pdata.fwname_use_revid)
|
|
cs35l41->dsp.part = "cs35l41-revA";
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_reva0_errata_patch,
|
|
ARRAY_SIZE(cs35l41_reva0_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply A0 errata patch %d\n", ret);
|
|
goto err;
|
|
}
|
|
break;
|
|
case CS35L41_REVID_B0:
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_INCOMPATIBLE;
|
|
if (cs35l41->pdata.fwname_use_revid)
|
|
cs35l41->dsp.part = "cs35l41-revB0";
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_revb0_errata_patch,
|
|
ARRAY_SIZE(cs35l41_revb0_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply B0 errata patch %d\n", ret);
|
|
goto err;
|
|
}
|
|
break;
|
|
case CS35L41_REVID_B2:
|
|
if (cs35l41->pdata.fwname_use_revid)
|
|
cs35l41->dsp.part = "cs35l41-revB2";
|
|
ret = regmap_multi_reg_write(
|
|
cs35l41->regmap, cs35l41_revb2_errata_patch,
|
|
ARRAY_SIZE(cs35l41_revb2_errata_patch));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev,
|
|
"Failed to apply B2 errata patch %d\n", ret);
|
|
goto err;
|
|
}
|
|
|
|
if (cs35l41->pdata.hibernate_enable &&
|
|
devid_otp != CS35L41LV_CHIP_ID)
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_NOT_LOADED;
|
|
else
|
|
cs35l41->amp_hibernate = CS35L41_HIBERNATE_INCOMPATIBLE;
|
|
|
|
cs35l41->reset_cache.extclk_cfg = false;
|
|
cs35l41->reset_cache.asp_wl = -1;
|
|
cs35l41->reset_cache.asp_width = -1;
|
|
cs35l41->reset_cache.asp_fmt = -1;
|
|
cs35l41->reset_cache.sclk_fmt = -1;
|
|
cs35l41->reset_cache.slave_mode = -1;
|
|
cs35l41->reset_cache.lrclk_fmt = -1;
|
|
cs35l41->reset_cache.fs_cfg = -1;
|
|
break;
|
|
}
|
|
|
|
ret = cs35l41_otp_unpack(cs35l41);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "OTP Unpack failed\n");
|
|
goto err;
|
|
}
|
|
|
|
/* read all trim regs */
|
|
p_trim_data = cs35l41->trim_cache;
|
|
for (i = 0; i < CS35L41_TRIM_CACHE_REGIONS; i++) {
|
|
regmap_raw_read(cs35l41->regmap, cs35l41_trim_cache_regs[i].reg,
|
|
p_trim_data,
|
|
cs35l41_trim_cache_regs[i].size * sizeof(u32));
|
|
p_trim_data += cs35l41_trim_cache_regs[i].size;
|
|
}
|
|
|
|
regmap_write(cs35l41->regmap, CS35L41_DSP1_CCM_CORE_CTRL, 0);
|
|
cs35l41_dsp_init(cs35l41);
|
|
|
|
ret = regmap_read(cs35l41->regmap, CS35L41_DEVID_OTP, &devid_otp);
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "Read DEVID OTP failed\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
if (devid_otp == CS35L41LV_CHIP_ID) {
|
|
cs35l41_tmp_ctl = cs35l41_lv_ctl;
|
|
num_cs35l41_tmp_ctl = ARRAY_SIZE(cs35l41_lv_ctl);
|
|
cs35l41_tmp_map = NULL;
|
|
num_cs35l41_tmp_map = 0;
|
|
cs35l41_tmp_widgets = NULL;
|
|
num_cs35l41_tmp_widgets = 0;
|
|
} else {
|
|
cs35l41_tmp_ctl = cs35l41_bst_ctl;
|
|
num_cs35l41_tmp_ctl = ARRAY_SIZE(cs35l41_bst_ctl);
|
|
cs35l41_tmp_map = cs35l41_hib_map;
|
|
num_cs35l41_tmp_map = ARRAY_SIZE(cs35l41_hib_map);
|
|
cs35l41_tmp_widgets = cs35l41_hib_widgets;
|
|
num_cs35l41_tmp_widgets = ARRAY_SIZE(cs35l41_hib_widgets);
|
|
}
|
|
cs35l41_ctl = devm_kmalloc(cs35l41->dev,
|
|
sizeof(struct snd_kcontrol_new) *
|
|
(num_cs35l41_tmp_ctl +
|
|
ARRAY_SIZE(cs35l41_aud_controls)),
|
|
GFP_KERNEL);
|
|
if (!cs35l41_ctl) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
memcpy(cs35l41_ctl, cs35l41_tmp_ctl,
|
|
num_cs35l41_tmp_ctl * sizeof(struct snd_kcontrol_new));
|
|
memcpy(cs35l41_ctl + num_cs35l41_tmp_ctl, cs35l41_aud_controls,
|
|
ARRAY_SIZE(cs35l41_aud_controls) *
|
|
sizeof(struct snd_kcontrol_new));
|
|
soc_component_dev_cs35l41.controls = cs35l41_ctl;
|
|
soc_component_dev_cs35l41.num_controls =
|
|
num_cs35l41_tmp_ctl + ARRAY_SIZE(cs35l41_aud_controls);
|
|
cs35l41_map = devm_kmalloc(
|
|
cs35l41->dev,
|
|
sizeof(struct snd_soc_dapm_route) *
|
|
(num_cs35l41_tmp_map + ARRAY_SIZE(cs35l41_audio_map)),
|
|
GFP_KERNEL);
|
|
if (!cs35l41_map) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
memcpy(cs35l41_map, cs35l41_tmp_map,
|
|
num_cs35l41_tmp_map * sizeof(struct snd_soc_dapm_route));
|
|
memcpy(cs35l41_map + num_cs35l41_tmp_map, cs35l41_audio_map,
|
|
ARRAY_SIZE(cs35l41_audio_map) *
|
|
sizeof(struct snd_soc_dapm_route));
|
|
soc_component_dev_cs35l41.dapm_routes = cs35l41_map;
|
|
soc_component_dev_cs35l41.num_dapm_routes =
|
|
num_cs35l41_tmp_map + ARRAY_SIZE(cs35l41_audio_map);
|
|
cs35l41_widgets =
|
|
devm_kmalloc(cs35l41->dev,
|
|
sizeof(struct snd_soc_dapm_widget) *
|
|
(num_cs35l41_tmp_widgets +
|
|
ARRAY_SIZE(cs35l41_dapm_widgets)),
|
|
GFP_KERNEL);
|
|
if (!cs35l41_widgets) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
memcpy(cs35l41_widgets, cs35l41_tmp_widgets,
|
|
num_cs35l41_tmp_widgets * sizeof(struct snd_soc_dapm_widget));
|
|
memcpy(cs35l41_widgets + num_cs35l41_tmp_widgets, cs35l41_dapm_widgets,
|
|
ARRAY_SIZE(cs35l41_dapm_widgets) *
|
|
sizeof(struct snd_soc_dapm_widget));
|
|
soc_component_dev_cs35l41.dapm_widgets = cs35l41_widgets;
|
|
soc_component_dev_cs35l41.num_dapm_widgets =
|
|
num_cs35l41_tmp_widgets + ARRAY_SIZE(cs35l41_dapm_widgets);
|
|
ret = snd_soc_register_component(cs35l41->dev,
|
|
&soc_component_dev_cs35l41,
|
|
cs35l41_dai, ARRAY_SIZE(cs35l41_dai));
|
|
if (ret < 0) {
|
|
dev_err(cs35l41->dev, "%s: Register codec failed\n", __func__);
|
|
goto err;
|
|
}
|
|
|
|
dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n",
|
|
regid, reg_revid);
|
|
|
|
cs35l41->wq = create_singlethread_workqueue("cs35l41");
|
|
if (cs35l41->wq == NULL) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&cs35l41->hb_work, cs35l41_hibernate_work);
|
|
mutex_init(&cs35l41->hb_lock);
|
|
|
|
/* Workarounds for specific project: */
|
|
// Preset sample rate
|
|
cs35l41_preset_sample_rate(cs35l41, 48000);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
regulator_bulk_disable(cs35l41->num_supplies, cs35l41->supplies);
|
|
return ret;
|
|
}
|
|
|
|
int cs35l41_remove(struct cs35l41_private *cs35l41)
|
|
{
|
|
destroy_workqueue(cs35l41->wq);
|
|
mutex_destroy(&cs35l41->hb_lock);
|
|
regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF);
|
|
mutex_destroy(&cs35l41->force_int_lock);
|
|
wm_adsp2_remove(&cs35l41->dsp);
|
|
regulator_bulk_disable(cs35l41->num_supplies, cs35l41->supplies);
|
|
snd_soc_unregister_component(cs35l41->dev);
|
|
return 0;
|
|
}
|
|
|
|
MODULE_DESCRIPTION("ASoC CS35L41 driver");
|
|
MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, <david.rhodes@cirrus.com>");
|
|
MODULE_LICENSE("GPL");
|