From 6387f866a2ccbf393ed5ffe7e2754eb5d0781441 Mon Sep 17 00:00:00 2001 From: Brian Austin Date: Mon, 6 Mar 2017 08:07:59 -0600 Subject: [PATCH 01/24] ASoC: Add support for Cirrus Logic CS35L35 Amplifier This patch adds support for the Cirrus Logic CS35L35 9V Boosted Amplifier Signed-off-by: Brian Austin Signed-off-by: Mark Brown --- include/sound/cs35l35.h | 103 +++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cs35l35.c | 1553 ++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cs35l35.h | 284 +++++++ 5 files changed, 1947 insertions(+) create mode 100644 include/sound/cs35l35.h create mode 100644 sound/soc/codecs/cs35l35.c create mode 100644 sound/soc/codecs/cs35l35.h diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h new file mode 100644 index 000000000000..de92e452bf93 --- /dev/null +++ b/include/sound/cs35l35.h @@ -0,0 +1,103 @@ +/* + * linux/sound/cs35l35.h -- Platform data for CS35l35 + * + * Copyright (c) 2016 Cirrus Logic Inc. + * + * 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. + */ + +#ifndef __CS35L35_H +#define __CS35L35_H + +struct classh_cfg { + /* + * Class H Algorithm Control Variables + * You can either have it done + * automatically or you can adjust + * these variables for tuning + * + * if you do not enable the internal algorithm + * you will get a set of mixer controls for + * Class H tuning + * + * Section 4.3 of the datasheet + */ + bool classh_bst_override; + bool classh_algo_enable; + int classh_bst_max_limit; + int classh_mem_depth; + int classh_release_rate; + int classh_headroom; + int classh_wk_fet_disable; + int classh_wk_fet_delay; + int classh_wk_fet_thld; + int classh_vpch_auto; + int classh_vpch_rate; + int classh_vpch_man; +}; + +struct monitor_cfg { + /* + * Signal Monitor Data + * highly configurable signal monitoring + * data positioning and different types of + * monitoring data. + * + * Section 4.8.2 - 4.8.4 of the datasheet + */ + bool is_present; + bool imon_specs; + bool vmon_specs; + bool vpmon_specs; + bool vbstmon_specs; + bool vpbrstat_specs; + bool zerofill_specs; + u8 imon_dpth; + u8 imon_loc; + u8 imon_frm; + u8 vmon_dpth; + u8 vmon_loc; + u8 vmon_frm; + u8 vpmon_dpth; + u8 vpmon_loc; + u8 vpmon_frm; + u8 vbstmon_dpth; + u8 vbstmon_loc; + u8 vbstmon_frm; + u8 vpbrstat_dpth; + u8 vpbrstat_loc; + u8 vpbrstat_frm; + u8 zerofill_dpth; + u8 zerofill_loc; + u8 zerofill_frm; +}; + +struct cs35l35_platform_data { + + /* Stereo (2 Device) */ + bool stereo; + /* serial port drive strength */ + int sp_drv_str; + /* Boost Power Down with FET */ + bool bst_pdn_fet_on; + /* Boost Voltage : used if ClassH Algo Enabled */ + int bst_vctl; + /* Boost Converter Peak Current CTRL */ + int bst_ipk; + /* Amp Gain Zero Cross */ + bool gain_zc; + /* Audio Input Location */ + int aud_channel; + /* Advisory Input Location */ + int adv_channel; + /* Shared Boost for stereo */ + bool shared_bst; + /* ClassH Algorithm */ + struct classh_cfg classh_algo; + /* Monitor Config */ + struct monitor_cfg mon_cfg; +}; + +#endif /* __CS35L35_H */ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e49e9da7f1f6..00885c2cb685 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -49,6 +49,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS35L32 if I2C select SND_SOC_CS35L33 if I2C select SND_SOC_CS35L34 if I2C + select SND_SOC_CS35L35 if I2C select SND_SOC_CS42L42 if I2C select SND_SOC_CS42L51_I2C if I2C select SND_SOC_CS42L52 if I2C && INPUT @@ -408,6 +409,10 @@ config SND_SOC_CS35L34 tristate "Cirrus Logic CS35L34 CODEC" depends on I2C +config SND_SOC_CS35L35 + tristate "Cirrus Logic CS35L35 CODEC" + depends on I2C + config SND_SOC_CS42L42 tristate "Cirrus Logic CS42L42 CODEC" depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1796cb987e71..97b36e4ce665 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -39,6 +39,7 @@ snd-soc-cq93vc-objs := cq93vc.o snd-soc-cs35l32-objs := cs35l32.o snd-soc-cs35l33-objs := cs35l33.o snd-soc-cs35l34-objs := cs35l34.o +snd-soc-cs35l35-objs := cs35l35.o snd-soc-cs42l42-objs := cs42l42.o snd-soc-cs42l51-objs := cs42l51.o snd-soc-cs42l51-i2c-objs := cs42l51-i2c.o @@ -269,6 +270,7 @@ obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o obj-$(CONFIG_SND_SOC_CS35L32) += snd-soc-cs35l32.o obj-$(CONFIG_SND_SOC_CS35L33) += snd-soc-cs35l33.o obj-$(CONFIG_SND_SOC_CS35L34) += snd-soc-cs35l34.o +obj-$(CONFIG_SND_SOC_CS35L35) += snd-soc-cs35l35.o obj-$(CONFIG_SND_SOC_CS42L42) += snd-soc-cs42l42.o obj-$(CONFIG_SND_SOC_CS42L51) += snd-soc-cs42l51.o obj-$(CONFIG_SND_SOC_CS42L51_I2C) += snd-soc-cs42l51-i2c.o diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c new file mode 100644 index 000000000000..260ed42c71e9 --- /dev/null +++ b/sound/soc/codecs/cs35l35.c @@ -0,0 +1,1553 @@ +/* + * cs35l35.c -- CS35L35 ALSA SoC audio driver + * + * Copyright 2017 Cirrus Logic, Inc. + * + * Author: Brian Austin + * + * 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cs35l35.h" + +/* + * Some fields take zero as a valid value so use a high bit flag that won't + * get written to the device to mark those. + */ +#define CS35L35_VALID_PDATA 0x80000000 + +static const struct reg_default cs35l35_reg[] = { + {CS35L35_PWRCTL1, 0x01}, + {CS35L35_PWRCTL2, 0x11}, + {CS35L35_PWRCTL3, 0x00}, + {CS35L35_CLK_CTL1, 0x04}, + {CS35L35_CLK_CTL2, 0x10}, + {CS35L35_CLK_CTL3, 0xCF}, + {CS35L35_SP_FMT_CTL1, 0x20}, + {CS35L35_SP_FMT_CTL2, 0x00}, + {CS35L35_SP_FMT_CTL3, 0x02}, + {CS35L35_MAG_COMP_CTL, 0x00}, + {CS35L35_AMP_INP_DRV_CTL, 0x01}, + {CS35L35_AMP_DIG_VOL_CTL, 0x12}, + {CS35L35_AMP_DIG_VOL, 0x00}, + {CS35L35_ADV_DIG_VOL, 0x00}, + {CS35L35_PROTECT_CTL, 0x06}, + {CS35L35_AMP_GAIN_AUD_CTL, 0x13}, + {CS35L35_AMP_GAIN_PDM_CTL, 0x00}, + {CS35L35_AMP_GAIN_ADV_CTL, 0x00}, + {CS35L35_GPI_CTL, 0x00}, + {CS35L35_BST_CVTR_V_CTL, 0x00}, + {CS35L35_BST_PEAK_I, 0x07}, + {CS35L35_BST_RAMP_CTL, 0x85}, + {CS35L35_BST_CONV_COEF_1, 0x24}, + {CS35L35_BST_CONV_COEF_2, 0x24}, + {CS35L35_BST_CONV_SLOPE_COMP, 0x47}, + {CS35L35_BST_CONV_SW_FREQ, 0x04}, + {CS35L35_CLASS_H_CTL, 0x0B}, + {CS35L35_CLASS_H_HEADRM_CTL, 0x0B}, + {CS35L35_CLASS_H_RELEASE_RATE, 0x08}, + {CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41}, + {CS35L35_CLASS_H_VP_CTL, 0xC5}, + {CS35L35_VPBR_CTL, 0x0A}, + {CS35L35_VPBR_VOL_CTL, 0x09}, + {CS35L35_VPBR_TIMING_CTL, 0x6A}, + {CS35L35_VPBR_MODE_VOL_CTL, 0x40}, + {CS35L35_SPKR_MON_CTL, 0xC0}, + {CS35L35_IMON_SCALE_CTL, 0x30}, + {CS35L35_AUDIN_RXLOC_CTL, 0x00}, + {CS35L35_ADVIN_RXLOC_CTL, 0x80}, + {CS35L35_VMON_TXLOC_CTL, 0x00}, + {CS35L35_IMON_TXLOC_CTL, 0x80}, + {CS35L35_VPMON_TXLOC_CTL, 0x04}, + {CS35L35_VBSTMON_TXLOC_CTL, 0x84}, + {CS35L35_VPBR_STATUS_TXLOC_CTL, 0x04}, + {CS35L35_ZERO_FILL_LOC_CTL, 0x00}, + {CS35L35_AUDIN_DEPTH_CTL, 0x0F}, + {CS35L35_SPKMON_DEPTH_CTL, 0x0F}, + {CS35L35_SUPMON_DEPTH_CTL, 0x0F}, + {CS35L35_ZEROFILL_DEPTH_CTL, 0x00}, + {CS35L35_MULT_DEV_SYNCH1, 0x02}, + {CS35L35_MULT_DEV_SYNCH2, 0x80}, + {CS35L35_PROT_RELEASE_CTL, 0x00}, + {CS35L35_DIAG_MODE_REG_LOCK, 0x00}, + {CS35L35_DIAG_MODE_CTL_1, 0x40}, + {CS35L35_DIAG_MODE_CTL_2, 0x00}, + {CS35L35_INT_MASK_1, 0xFF}, + {CS35L35_INT_MASK_2, 0xFF}, + {CS35L35_INT_MASK_3, 0xFF}, + {CS35L35_INT_MASK_4, 0xFF}, + +}; + +static bool cs35l35_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_DEVID_AB ... CS35L35_PWRCTL3: + case CS35L35_CLK_CTL1 ... CS35L35_SP_FMT_CTL3: + case CS35L35_MAG_COMP_CTL ... CS35L35_AMP_GAIN_AUD_CTL: + case CS35L35_AMP_GAIN_PDM_CTL ... CS35L35_BST_PEAK_I: + case CS35L35_BST_RAMP_CTL ... CS35L35_BST_CONV_SW_FREQ: + case CS35L35_CLASS_H_CTL ... CS35L35_CLASS_H_VP_CTL: + case CS35L35_CLASS_H_STATUS: + case CS35L35_VPBR_CTL ... CS35L35_VPBR_MODE_VOL_CTL: + case CS35L35_VPBR_ATTEN_STATUS: + case CS35L35_SPKR_MON_CTL: + case CS35L35_IMON_SCALE_CTL ... CS35L35_ZEROFILL_DEPTH_CTL: + case CS35L35_MULT_DEV_SYNCH1 ... CS35L35_PROT_RELEASE_CTL: + case CS35L35_DIAG_MODE_REG_LOCK ... CS35L35_DIAG_MODE_CTL_2: + case CS35L35_INT_MASK_1 ... CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static bool cs35l35_precious_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case CS35L35_INT_STATUS_1: + case CS35L35_INT_STATUS_2: + case CS35L35_INT_STATUS_3: + case CS35L35_INT_STATUS_4: + case CS35L35_PLL_STATUS: + case CS35L35_OTP_TRIM_STATUS: + return true; + default: + return false; + } +} + +static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int ret = 0; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 0 << CS35L35_MCLK_DIS_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 0 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 0); + break; + case SND_SOC_DAPM_POST_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_DISCHG_FILT_MASK, + 1 << CS35L35_DISCHG_FILT_SHIFT); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL_MASK, 1); + + reinit_completion(&cs35l35->pdn_done); + + ret = wait_for_completion_timeout(&cs35l35->pdn_done, + msecs_to_jiffies(100)); + if (ret == 0) { + dev_err(codec->dev, "TIMEOUT PDN_DONE did not complete in 100ms\n"); + ret = -ETIMEDOUT; + } + + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MCLK_DIS_MASK, + 1 << CS35L35_MCLK_DIS_SHIFT); + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + ret = -EINVAL; + } + return ret; +} + +static int cs35l35_main_amp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = snd_soc_dapm_to_codec(w->dapm); + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + unsigned int reg[4]; + int i; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 0 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMU: + usleep_range(5000, 5100); + /* If in PDM mode we must use VP for Voltage control */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + 0 << CS35L35_BST_CTL_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 0); + + for (i = 0; i < 2; i++) + regmap_bulk_read(cs35l35->regmap, CS35L35_INT_STATUS_1, + ®, ARRAY_SIZE(reg)); + + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, + 1 << CS35L35_AMP_MUTE_SHIFT); + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + break; + case SND_SOC_DAPM_POST_PMD: + usleep_range(5000, 5100); + /* + * If PDM mode we should switch back to pdata value + * for Voltage control when we go down + */ + if (cs35l35->pdm_mode) + regmap_update_bits(cs35l35->regmap, + CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl + << CS35L35_BST_CTL_SHIFT); + + break; + default: + dev_err(codec->dev, "Invalid event = 0x%x\n", event); + } + return 0; +} + +static DECLARE_TLV_DB_SCALE(amp_gain_tlv, 0, 1, 1); +static DECLARE_TLV_DB_SCALE(dig_vol_tlv, -10200, 50, 0); + +static const struct snd_kcontrol_new cs35l35_aud_controls[] = { + SOC_SINGLE_SX_TLV("Digital Audio Volume", CS35L35_AMP_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Audio Volume", CS35L35_AMP_GAIN_AUD_CTL, 0, 19, 0, + amp_gain_tlv), + SOC_SINGLE_TLV("PDM Volume", CS35L35_AMP_GAIN_PDM_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_kcontrol_new cs35l35_adv_controls[] = { + SOC_SINGLE_SX_TLV("Digital Advisory Volume", CS35L35_ADV_DIG_VOL, + 0, 0x34, 0xE4, dig_vol_tlv), + SOC_SINGLE_TLV("Analog Advisory Volume", CS35L35_AMP_GAIN_ADV_CTL, 0, 19, 0, + amp_gain_tlv), +}; + +static const struct snd_soc_dapm_widget cs35l35_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN_E("SDIN", NULL, 0, CS35L35_PWRCTL3, 1, 1, + cs35l35_sdin_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_AIF_OUT("SDOUT", NULL, 0, CS35L35_PWRCTL3, 2, 1), + + SND_SOC_DAPM_OUTPUT("SPK"), + + SND_SOC_DAPM_INPUT("VP"), + SND_SOC_DAPM_INPUT("VBST"), + SND_SOC_DAPM_INPUT("ISENSE"), + SND_SOC_DAPM_INPUT("VSENSE"), + + SND_SOC_DAPM_ADC("VMON ADC", NULL, CS35L35_PWRCTL2, 7, 1), + SND_SOC_DAPM_ADC("IMON ADC", NULL, CS35L35_PWRCTL2, 6, 1), + SND_SOC_DAPM_ADC("VPMON ADC", NULL, CS35L35_PWRCTL3, 3, 1), + SND_SOC_DAPM_ADC("VBSTMON ADC", NULL, CS35L35_PWRCTL3, 4, 1), + SND_SOC_DAPM_ADC("CLASS H", NULL, CS35L35_PWRCTL2, 5, 1), + + SND_SOC_DAPM_OUT_DRV_E("Main AMP", CS35L35_PWRCTL2, 0, 1, NULL, 0, + cs35l35_main_amp_event, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_PRE_PMD), +}; + +static const struct snd_soc_dapm_route cs35l35_audio_map[] = { + {"VPMON ADC", NULL, "VP"}, + {"VBSTMON ADC", NULL, "VBST"}, + {"IMON ADC", NULL, "ISENSE"}, + {"VMON ADC", NULL, "VSENSE"}, + {"SDOUT", NULL, "IMON ADC"}, + {"SDOUT", NULL, "VMON ADC"}, + {"SDOUT", NULL, "VBSTMON ADC"}, + {"SDOUT", NULL, "VPMON ADC"}, + {"AMP Capture", NULL, "SDOUT"}, + + {"SDIN", NULL, "AMP Playback"}, + {"CLASS H", NULL, "SDIN"}, + {"Main AMP", NULL, "CLASS H"}, + {"SPK", NULL, "Main AMP"}, +}; + +static int cs35l35_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 1 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = false; + break; + case SND_SOC_DAIFMT_CBS_CFS: + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_MS_MASK, 0 << CS35L35_MS_SHIFT); + cs35l35->slave_mode = true; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + cs35l35->i2s_mode = true; + cs35l35->pdm_mode = false; + break; + case SND_SOC_DAIFMT_PDM: + cs35l35->pdm_mode = true; + cs35l35->i2s_mode = false; + break; + default: + return -EINVAL; + } + + return 0; +} + +struct cs35l35_sysclk_config { + int sysclk; + int srate; + u8 clk_cfg; +}; + +static struct cs35l35_sysclk_config cs35l35_clk_ctl[] = { + + /* SYSCLK, Sample Rate, Serial Port Cfg */ + {5644800, 44100, 0x00}, + {5644800, 88200, 0x40}, + {6144000, 48000, 0x10}, + {6144000, 96000, 0x50}, + {11289600, 44100, 0x01}, + {11289600, 88200, 0x41}, + {11289600, 176400, 0x81}, + {12000000, 44100, 0x03}, + {12000000, 48000, 0x13}, + {12000000, 88200, 0x43}, + {12000000, 96000, 0x53}, + {12000000, 176400, 0x83}, + {12000000, 192000, 0x93}, + {12288000, 48000, 0x11}, + {12288000, 96000, 0x51}, + {12288000, 192000, 0x91}, + {13000000, 44100, 0x07}, + {13000000, 48000, 0x17}, + {13000000, 88200, 0x47}, + {13000000, 96000, 0x57}, + {13000000, 176400, 0x87}, + {13000000, 192000, 0x97}, + {22579200, 44100, 0x02}, + {22579200, 88200, 0x42}, + {22579200, 176400, 0x82}, + {24000000, 44100, 0x0B}, + {24000000, 48000, 0x1B}, + {24000000, 88200, 0x4B}, + {24000000, 96000, 0x5B}, + {24000000, 176400, 0x8B}, + {24000000, 192000, 0x9B}, + {24576000, 48000, 0x12}, + {24576000, 96000, 0x52}, + {24576000, 192000, 0x92}, + {26000000, 44100, 0x0F}, + {26000000, 48000, 0x1F}, + {26000000, 88200, 0x4F}, + {26000000, 96000, 0x5F}, + {26000000, 176400, 0x8F}, + {26000000, 192000, 0x9F}, +}; + +static int cs35l35_get_clk_config(int sysclk, int srate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cs35l35_clk_ctl); i++) { + if (cs35l35_clk_ctl[i].sysclk == sysclk && + cs35l35_clk_ctl[i].srate == srate) + return cs35l35_clk_ctl[i].clk_cfg; + } + return -EINVAL; +} + +static int cs35l35_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + int srate = params_rate(params); + int ret = 0; + u8 sp_sclks; + int audin_format; + int errata_chk; + + int clk_ctl = cs35l35_get_clk_config(cs35l35->sysclk, srate); + + if (clk_ctl < 0) { + dev_err(codec->dev, "Invalid CLK:Rate %d:%d\n", + cs35l35->sysclk, srate); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL2, + CS35L35_CLK_CTL2_MASK, clk_ctl); + if (ret != 0) { + dev_err(codec->dev, "Failed to set port config %d\n", ret); + return ret; + } + + /* + * Rev A0 Errata + * When configured for the weak-drive detection path (CH_WKFET_DIS = 0) + * the Class H algorithm does not enable weak-drive operation for + * nonzero values of CH_WKFET_DELAY if SP_RATE = 01 or 10 + */ + errata_chk = clk_ctl & CS35L35_SP_RATE_MASK; + + if (classh->classh_wk_fet_disable == 0x00 && + (errata_chk == 0x01 || errata_chk == 0x03)) { + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + 0 << CS35L35_CH_WKFET_DEL_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fet config %d\n", + ret); + return ret; + } + } + + /* + * You can pull more Monitor data from the SDOUT pin than going to SDIN + * Just make sure your SCLK is fast enough to fill the frame + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + switch (params_width(params)) { + case 8: + audin_format = CS35L35_SDIN_DEPTH_8; + break; + case 16: + audin_format = CS35L35_SDIN_DEPTH_16; + break; + case 24: + audin_format = CS35L35_SDIN_DEPTH_24; + break; + default: + dev_err(codec->dev, "Unsupported Width %d\n", + params_width(params)); + return -EINVAL; + } + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_AUDIN_DEPTH_MASK, + audin_format << + CS35L35_AUDIN_DEPTH_SHIFT); + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_DEPTH_CTL, + CS35L35_ADVIN_DEPTH_MASK, + audin_format << + CS35L35_ADVIN_DEPTH_SHIFT); + } + } + + if (cs35l35->i2s_mode) { + /* We have to take the SCLK to derive num sclks + * to configure the CLOCK_CTL3 register correctly + */ + if ((cs35l35->sclk / srate) % 4) { + dev_err(codec->dev, "Unsupported sclk/fs ratio %d:%d\n", + cs35l35->sclk, srate); + return -EINVAL; + } + sp_sclks = ((cs35l35->sclk / srate) / 4) - 1; + + /* Only certain ratios are supported in I2S Slave Mode */ + if (cs35l35->slave_mode) { + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_48FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + }; + } else { + /* Only certain ratios supported in I2S MASTER Mode */ + switch (sp_sclks) { + case CS35L35_SP_SCLKS_32FS: + case CS35L35_SP_SCLKS_64FS: + break; + default: + dev_err(codec->dev, "ratio not supported\n"); + return -EINVAL; + }; + } + ret = regmap_update_bits(cs35l35->regmap, + CS35L35_CLK_CTL3, + CS35L35_SP_SCLKS_MASK, sp_sclks << + CS35L35_SP_SCLKS_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set fsclk %d\n", ret); + return ret; + } + } + + return ret; +} + +static const unsigned int cs35l35_src_rates[] = { + 44100, 48000, 88200, 96000, 176400, 192000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_constraints = { + .count = ARRAY_SIZE(cs35l35_src_rates), + .list = cs35l35_src_rates, +}; + +static int cs35l35_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, &cs35l35_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 0 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static const unsigned int cs35l35_pdm_rates[] = { + 44100, 48000, 88200, 96000 +}; + +static const struct snd_pcm_hw_constraint_list cs35l35_pdm_constraints = { + .count = ARRAY_SIZE(cs35l35_pdm_rates), + .list = cs35l35_pdm_rates, +}; + +static int cs35l35_pdm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + if (!substream->runtime) + return 0; + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &cs35l35_pdm_constraints); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_INP_DRV_CTL, + CS35L35_PDM_MODE_MASK, + 1 << CS35L35_PDM_MODE_SHIFT); + + return 0; +} + +static int cs35l35_dai_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + + /* Need the SCLK Frequency regardless of sysclk source for I2S */ + cs35l35->sclk = freq; + + return 0; +} + +static const struct snd_soc_dai_ops cs35l35_ops = { + .startup = cs35l35_pcm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, + .set_sysclk = cs35l35_dai_set_sysclk, +}; + +static const struct snd_soc_dai_ops cs35l35_pdm_ops = { + .startup = cs35l35_pdm_startup, + .set_fmt = cs35l35_set_dai_fmt, + .hw_params = cs35l35_hw_params, +}; + +static struct snd_soc_dai_driver cs35l35_dai[] = { + { + .name = "cs35l35-pcm", + .id = 0, + .playback = { + .stream_name = "AMP Playback", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .capture = { + .stream_name = "AMP Capture", + .channels_min = 1, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_ops, + .symmetric_rates = 1, + }, + { + .name = "cs35l35-pdm", + .id = 1, + .playback = { + .stream_name = "PDM Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_KNOT, + .formats = CS35L35_FORMATS, + }, + .ops = &cs35l35_pdm_ops, + }, +}; + +static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec, + int clk_id, int source, unsigned int freq, + int dir) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + int clksrc; + int ret = 0; + + switch (clk_id) { + case 0: + clksrc = CS35L35_CLK_SOURCE_MCLK; + break; + case 1: + clksrc = CS35L35_CLK_SOURCE_SCLK; + break; + case 2: + clksrc = CS35L35_CLK_SOURCE_PDM; + break; + default: + dev_err(codec->dev, "Invalid CLK Source\n"); + return -EINVAL; + }; + + switch (freq) { + case 5644800: + case 6144000: + case 11289600: + case 12000000: + case 12288000: + case 13000000: + case 22579200: + case 24000000: + case 24576000: + case 26000000: + cs35l35->sysclk = freq; + break; + default: + dev_err(codec->dev, "Invalid CLK Frequency Input : %d\n", freq); + return -EINVAL; + } + + ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_CLK_SOURCE_MASK, + clksrc << CS35L35_CLK_SOURCE_SHIFT); + if (ret != 0) { + dev_err(codec->dev, "Failed to set sysclk %d\n", ret); + return ret; + } + + return ret; +} + +static int cs35l35_codec_probe(struct snd_soc_codec *codec) +{ + struct cs35l35_private *cs35l35 = snd_soc_codec_get_drvdata(codec); + struct classh_cfg *classh = &cs35l35->pdata.classh_algo; + struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg; + int ret; + + cs35l35->codec = codec; + + /* Set Platform Data */ + if (cs35l35->pdata.bst_vctl) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL, + CS35L35_BST_CTL_MASK, + cs35l35->pdata.bst_vctl); + + if (cs35l35->pdata.bst_ipk) + regmap_update_bits(cs35l35->regmap, CS35L35_BST_PEAK_I, + CS35L35_BST_IPK_MASK, + cs35l35->pdata.bst_ipk << + CS35L35_BST_IPK_SHIFT); + + if (cs35l35->pdata.gain_zc) + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_GAIN_ZC_MASK, + cs35l35->pdata.gain_zc << + CS35L35_AMP_GAIN_ZC_SHIFT); + + if (cs35l35->pdata.aud_channel) + regmap_update_bits(cs35l35->regmap, + CS35L35_AUDIN_RXLOC_CTL, + CS35L35_AUD_IN_LR_MASK, + cs35l35->pdata.aud_channel << + CS35L35_AUD_IN_LR_SHIFT); + + if (cs35l35->pdata.stereo) { + regmap_update_bits(cs35l35->regmap, + CS35L35_ADVIN_RXLOC_CTL, + CS35L35_ADV_IN_LR_MASK, + cs35l35->pdata.adv_channel << + CS35L35_ADV_IN_LR_SHIFT); + if (cs35l35->pdata.shared_bst) + regmap_update_bits(cs35l35->regmap, CS35L35_CLASS_H_CTL, + CS35L35_CH_STEREO_MASK, + 1 << CS35L35_CH_STEREO_SHIFT); + ret = snd_soc_add_codec_controls(codec, cs35l35_adv_controls, + ARRAY_SIZE(cs35l35_adv_controls)); + if (ret) + return ret; + } + + if (cs35l35->pdata.sp_drv_str) + regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, + CS35L35_SP_DRV_MASK, + cs35l35->pdata.sp_drv_str << + CS35L35_SP_DRV_SHIFT); + + if (classh->classh_algo_enable) { + if (classh->classh_bst_override) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_OVR_MASK, + classh->classh_bst_override << + CS35L35_CH_BST_OVR_SHIFT); + if (classh->classh_bst_max_limit) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_BST_LIM_MASK, + classh->classh_bst_max_limit << + CS35L35_CH_BST_LIM_SHIFT); + if (classh->classh_mem_depth) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_CTL, + CS35L35_CH_MEM_DEPTH_MASK, + classh->classh_mem_depth << + CS35L35_CH_MEM_DEPTH_SHIFT); + if (classh->classh_headroom) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_HEADRM_CTL, + CS35L35_CH_HDRM_CTL_MASK, + classh->classh_headroom << + CS35L35_CH_HDRM_CTL_SHIFT); + if (classh->classh_release_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_RELEASE_RATE, + CS35L35_CH_REL_RATE_MASK, + classh->classh_release_rate << + CS35L35_CH_REL_RATE_SHIFT); + if (classh->classh_wk_fet_disable) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DIS_MASK, + classh->classh_wk_fet_disable << + CS35L35_CH_WKFET_DIS_SHIFT); + if (classh->classh_wk_fet_delay) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_DEL_MASK, + classh->classh_wk_fet_delay << + CS35L35_CH_WKFET_DEL_SHIFT); + if (classh->classh_wk_fet_thld) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_FET_DRIVE_CTL, + CS35L35_CH_WKFET_THLD_MASK, + classh->classh_wk_fet_thld << + CS35L35_CH_WKFET_THLD_SHIFT); + if (classh->classh_vpch_auto) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_AUTO_MASK, + classh->classh_vpch_auto << + CS35L35_CH_VP_AUTO_SHIFT); + if (classh->classh_vpch_rate) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_RATE_MASK, + classh->classh_vpch_rate << + CS35L35_CH_VP_RATE_SHIFT); + if (classh->classh_vpch_man) + regmap_update_bits(cs35l35->regmap, + CS35L35_CLASS_H_VP_CTL, + CS35L35_CH_VP_MAN_MASK, + classh->classh_vpch_man << + CS35L35_CH_VP_MAN_SHIFT); + } + + if (monitor_config->is_present) { + if (monitor_config->vmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_VMON_DEPTH_MASK, + monitor_config->vmon_dpth << + CS35L35_VMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->imon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SPKMON_DEPTH_CTL, + CS35L35_IMON_DEPTH_MASK, + monitor_config->imon_dpth << + CS35L35_IMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->imon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->imon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vpmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VPMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vbstmon_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VBSTMON_DEPTH_MASK, + monitor_config->vpmon_dpth << + CS35L35_VBSTMON_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vbstmon_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VBSTMON_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vbstmon_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->vpbrstat_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_VPBRSTAT_DEPTH_MASK, + monitor_config->vpbrstat_dpth << + CS35L35_VPBRSTAT_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->vpbrstat_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_VPBR_STATUS_TXLOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->vpbrstat_frm << + CS35L35_MON_FRM_SHIFT); + } + if (monitor_config->zerofill_specs) { + regmap_update_bits(cs35l35->regmap, + CS35L35_SUPMON_DEPTH_CTL, + CS35L35_ZEROFILL_DEPTH_MASK, + monitor_config->zerofill_dpth << + CS35L35_ZEROFILL_DEPTH_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_TXLOC_MASK, + monitor_config->zerofill_loc << + CS35L35_MON_TXLOC_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_ZERO_FILL_LOC_CTL, + CS35L35_MON_FRM_MASK, + monitor_config->zerofill_frm << + CS35L35_MON_FRM_SHIFT); + } + } + + return ret; +} + +static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = { + .probe = cs35l35_codec_probe, + .set_sysclk = cs35l35_codec_set_sysclk, + .component_driver = { + .dapm_widgets = cs35l35_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(cs35l35_dapm_widgets), + + .dapm_routes = cs35l35_audio_map, + .num_dapm_routes = ARRAY_SIZE(cs35l35_audio_map), + + .controls = cs35l35_aud_controls, + .num_controls = ARRAY_SIZE(cs35l35_aud_controls), + }, + +}; + +static struct regmap_config cs35l35_regmap = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = CS35L35_MAX_REGISTER, + .reg_defaults = cs35l35_reg, + .num_reg_defaults = ARRAY_SIZE(cs35l35_reg), + .volatile_reg = cs35l35_volatile_register, + .readable_reg = cs35l35_readable_register, + .precious_reg = cs35l35_precious_register, + .cache_type = REGCACHE_RBTREE, +}; + +static irqreturn_t cs35l35_irq(int irq, void *data) +{ + struct cs35l35_private *cs35l35 = data; + struct snd_soc_codec *codec = cs35l35->codec; + unsigned int sticky1, sticky2, sticky3, sticky4; + unsigned int mask1, mask2, mask3, mask4, current1; + + /* ack the irq by reading all status registers */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_4, &sticky4); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_3, &sticky3); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_2, &sticky2); + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, &sticky1); + + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_4, &mask4); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_3, &mask3); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_2, &mask2); + regmap_read(cs35l35->regmap, CS35L35_INT_MASK_1, &mask1); + + /* Check to see if unmasked bits are active */ + if (!(sticky1 & ~mask1) && !(sticky2 & ~mask2) && !(sticky3 & ~mask3) + && !(sticky4 & ~mask4)) + return IRQ_NONE; + + if (sticky2 & CS35L35_PDN_DONE) + complete(&cs35l35->pdn_done); + + /* read the current values */ + regmap_read(cs35l35->regmap, CS35L35_INT_STATUS_1, ¤t1); + + /* handle the interrupts */ + if (sticky1 & CS35L35_CAL_ERR) { + dev_crit(codec->dev, "Calibration Error\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_CAL_ERR)) { + pr_debug("%s : Cal error release\n", __func__); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, + CS35L35_CAL_ERR_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_CAL_ERR_RLS, 0); + } + } + + if (sticky1 & CS35L35_AMP_SHORT) { + dev_crit(codec->dev, "AMP Short Error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_AMP_SHORT)) { + dev_dbg(codec->dev, "Amp short error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, + CS35L35_SHORT_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_SHORT_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTW) { + dev_warn(codec->dev, "Over temperature warning\n"); + + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTW)) { + dev_dbg(codec->dev, "Over temperature warn release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, + CS35L35_OTW_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTW_RLS, 0); + } + } + + if (sticky1 & CS35L35_OTE) { + dev_crit(codec->dev, "Over temperature error\n"); + /* error is no longer asserted; safe to reset */ + if (!(current1 & CS35L35_OTE)) { + dev_dbg(codec->dev, "Over temperature error release\n"); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, + CS35L35_OTE_RLS); + regmap_update_bits(cs35l35->regmap, + CS35L35_PROT_RELEASE_CTL, + CS35L35_OTE_RLS, 0); + } + } + + if (sticky3 & CS35L35_BST_HIGH) { + dev_crit(codec->dev, "VBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky3 & CS35L35_LBST_SHORT) { + dev_crit(codec->dev, "LBST error: powering off!\n"); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_AMP, CS35L35_PDN_AMP); + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, + CS35L35_PDN_ALL, CS35L35_PDN_ALL); + } + + if (sticky2 & CS35L35_VPBR_ERR) + dev_dbg(codec->dev, "Error: Reactive Brownout\n"); + + if (sticky4 & CS35L35_VMON_OVFL) + dev_dbg(codec->dev, "Error: VMON overflow\n"); + + if (sticky4 & CS35L35_IMON_OVFL) + dev_dbg(codec->dev, "Error: IMON overflow\n"); + + return IRQ_HANDLED; +} + + +static int cs35l35_handle_of_data(struct i2c_client *i2c_client, + struct cs35l35_platform_data *pdata) +{ + struct device_node *np = i2c_client->dev.of_node; + struct device_node *classh, *signal_format; + struct classh_cfg *classh_config = &pdata->classh_algo; + struct monitor_cfg *monitor_config = &pdata->mon_cfg; + unsigned int val32 = 0; + u8 monitor_array[3]; + int ret = 0; + + if (!np) + return 0; + + pdata->bst_pdn_fet_on = of_property_read_bool(np, + "cirrus,boost-pdn-fet-on"); + + ret = of_property_read_u32(np, "cirrus,boost-ctl-millivolt", &val32); + if (ret >= 0) { + if (val32 < 2600 || val32 > 9000) { + dev_err(&i2c_client->dev, + "Invalid Boost Voltage %d mV\n", val32); + return -EINVAL; + } + pdata->bst_vctl = ((val32 - 2600) / 100) + 1; + } + + ret = of_property_read_u32(np, "cirrus,boost-peak-milliamp", &val32); + if (ret >= 0) { + if (val32 < 1680 || val32 > 4480) { + dev_err(&i2c_client->dev, + "Invalid Boost Peak Current %u mA\n", val32); + return -EINVAL; + } + + pdata->bst_ipk = (val32 - 1680) / 110; + } + + if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0) + pdata->sp_drv_str = val32; + + pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config"); + + if (pdata->stereo) { + ret = of_property_read_u32(np, "cirrus,audio-channel", &val32); + if (ret >= 0) + pdata->aud_channel = val32; + + ret = of_property_read_u32(np, "cirrus,advisory-channel", + &val32); + if (ret >= 0) + pdata->adv_channel = val32; + + pdata->shared_bst = of_property_read_bool(np, + "cirrus,shared-boost"); + } + + pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc"); + + classh = of_get_child_by_name(np, "cirrus,classh-internal-algo"); + classh_config->classh_algo_enable = classh ? true : false; + + if (classh_config->classh_algo_enable) { + classh_config->classh_bst_override = + of_property_read_bool(np, "cirrus,classh-bst-overide"); + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-bst-max-limit", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_bst_max_limit = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-mem-depth", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_mem_depth = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-release-rate", + &val32); + if (ret >= 0) + classh_config->classh_release_rate = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-headroom", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_headroom = val32; + } + + ret = of_property_read_u32(classh, + "cirrus,classh-wk-fet-disable", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_disable = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-delay", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_wk_fet_delay = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-wk-fet-thld", + &val32); + if (ret >= 0) + classh_config->classh_wk_fet_thld = val32; + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-auto", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_auto = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-rate", + &val32); + if (ret >= 0) { + val32 |= CS35L35_VALID_PDATA; + classh_config->classh_vpch_rate = val32; + } + + ret = of_property_read_u32(classh, "cirrus,classh-vpch-man", + &val32); + if (ret >= 0) + classh_config->classh_vpch_man = val32; + } + of_node_put(classh); + + /* frame depth location */ + signal_format = of_get_child_by_name(np, "cirrus,monitor-signal-format"); + monitor_config->is_present = signal_format ? true : false; + if (monitor_config->is_present) { + ret = of_property_read_u8_array(signal_format, "cirrus,imon", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->imon_specs = true; + monitor_config->imon_dpth = monitor_array[0]; + monitor_config->imon_loc = monitor_array[1]; + monitor_config->imon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vmon", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->vmon_specs = true; + monitor_config->vmon_dpth = monitor_array[0]; + monitor_config->vmon_loc = monitor_array[1]; + monitor_config->vmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpmon", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->vpmon_specs = true; + monitor_config->vpmon_dpth = monitor_array[0]; + monitor_config->vpmon_loc = monitor_array[1]; + monitor_config->vpmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->vbstmon_specs = true; + monitor_config->vbstmon_dpth = monitor_array[0]; + monitor_config->vbstmon_loc = monitor_array[1]; + monitor_config->vbstmon_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->vpbrstat_specs = true; + monitor_config->vpbrstat_dpth = monitor_array[0]; + monitor_config->vpbrstat_loc = monitor_array[1]; + monitor_config->vpbrstat_frm = monitor_array[2]; + } + ret = of_property_read_u8_array(signal_format, "cirrus,zerofill", + monitor_array, ARRAY_SIZE(monitor_array)); + if (!ret) { + monitor_config->zerofill_specs = true; + monitor_config->zerofill_dpth = monitor_array[0]; + monitor_config->zerofill_loc = monitor_array[1]; + monitor_config->zerofill_frm = monitor_array[2]; + } + } + of_node_put(signal_format); + + return 0; +} + +/* Errata Rev A0 */ +static const struct reg_sequence cs35l35_errata_patch[] = { + + { 0x7F, 0x99 }, + { 0x00, 0x99 }, + { 0x52, 0x22 }, + { 0x04, 0x14 }, + { 0x6D, 0x44 }, + { 0x24, 0x10 }, + { 0x58, 0xC4 }, + { 0x00, 0x98 }, + { 0x18, 0x08 }, + { 0x00, 0x00 }, + { 0x7F, 0x00 }, +}; + +static int cs35l35_i2c_probe(struct i2c_client *i2c_client, + const struct i2c_device_id *id) +{ + struct cs35l35_private *cs35l35; + struct cs35l35_platform_data *pdata = + dev_get_platdata(&i2c_client->dev); + int i; + int ret; + unsigned int devid = 0; + unsigned int reg; + + cs35l35 = devm_kzalloc(&i2c_client->dev, + sizeof(struct cs35l35_private), + GFP_KERNEL); + if (!cs35l35) + return -ENOMEM; + + i2c_set_clientdata(i2c_client, cs35l35); + cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); + if (IS_ERR(cs35l35->regmap)) { + ret = PTR_ERR(cs35l35->regmap); + dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++) + cs35l35->supplies[i].supply = cs35l35_supplies[i]; + cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); + + ret = devm_regulator_bulk_get(&i2c_client->dev, + cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to request core supplies: %d\n", + ret); + return ret; + } + + if (pdata) { + cs35l35->pdata = *pdata; + } else { + pdata = devm_kzalloc(&i2c_client->dev, + sizeof(struct cs35l35_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + if (i2c_client->dev.of_node) { + ret = cs35l35_handle_of_data(i2c_client, pdata); + if (ret != 0) + return ret; + + } + cs35l35->pdata = *pdata; + } + + ret = regulator_bulk_enable(cs35l35->num_supplies, + cs35l35->supplies); + if (ret != 0) { + dev_err(&i2c_client->dev, + "Failed to enable core supplies: %d\n", + ret); + return ret; + } + + /* returning NULL can be valid if in stereo mode */ + cs35l35->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, + "reset", GPIOD_OUT_LOW); + if (IS_ERR(cs35l35->reset_gpio)) { + ret = PTR_ERR(cs35l35->reset_gpio); + if (ret == -EBUSY) { + dev_info(&i2c_client->dev, + "Reset line busy, assuming shared reset\n"); + cs35l35->reset_gpio = NULL; + } else { + dev_err(&i2c_client->dev, + "Failed to get reset GPIO: %d\n", ret); + goto err; + } + } + + gpiod_set_value_cansleep(cs35l35->reset_gpio, 1); + + init_completion(&cs35l35->pdn_done); + + ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, + cs35l35_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l35", cs35l35); + if (ret != 0) { + dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + goto err; + } + /* initialize codec */ + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_AB, ®); + + devid = (reg & 0xFF) << 12; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_CD, ®); + devid |= (reg & 0xFF) << 4; + ret = regmap_read(cs35l35->regmap, CS35L35_DEVID_E, ®); + devid |= (reg & 0xF0) >> 4; + + if (devid != CS35L35_CHIP_ID) { + dev_err(&i2c_client->dev, + "CS35L35 Device ID (%X). Expected ID %X\n", + devid, CS35L35_CHIP_ID); + ret = -ENODEV; + goto err; + } + + ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®); + if (ret < 0) { + dev_err(&i2c_client->dev, "Get Revision ID failed: %d\n", ret); + goto err; + } + + ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch, + ARRAY_SIZE(cs35l35_errata_patch)); + if (ret < 0) { + dev_err(&i2c_client->dev, "Failed to apply errata patch: %d\n", + ret); + goto err; + } + + dev_info(&i2c_client->dev, + "Cirrus Logic CS35L35 (%x), Revision: %02X\n", devid, + ret & 0xFF); + + /* Set the INT Masks for critical errors */ + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, + CS35L35_INT1_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_2, + CS35L35_INT2_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_3, + CS35L35_INT3_CRIT_MASK); + regmap_write(cs35l35->regmap, CS35L35_INT_MASK_4, + CS35L35_INT4_CRIT_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PWR2_PDN_MASK, + CS35L35_PWR2_PDN_MASK); + + if (cs35l35->pdata.bst_pdn_fet_on) + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETON_SHIFT); + else + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, + CS35L35_PDN_BST_MASK, + 1 << CS35L35_PDN_BST_FETOFF_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL3, + CS35L35_PWR3_PDN_MASK, + CS35L35_PWR3_PDN_MASK); + + regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, + CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT); + + ret = snd_soc_register_codec(&i2c_client->dev, + &soc_codec_dev_cs35l35, cs35l35_dai, + ARRAY_SIZE(cs35l35_dai)); + if (ret < 0) { + dev_err(&i2c_client->dev, + "Failed to register codec: %d\n", ret); + goto err; + } + +err: + regulator_bulk_disable(cs35l35->num_supplies, + cs35l35->supplies); + gpiod_set_value_cansleep(cs35l35->reset_gpio, 0); + + return ret; +} + +static int cs35l35_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id cs35l35_of_match[] = { + {.compatible = "cirrus,cs35l35"}, + {}, +}; +MODULE_DEVICE_TABLE(of, cs35l35_of_match); + +static const struct i2c_device_id cs35l35_id[] = { + {"cs35l35", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, cs35l35_id); + +static struct i2c_driver cs35l35_i2c_driver = { + .driver = { + .name = "cs35l35", + .of_match_table = cs35l35_of_match, + }, + .id_table = cs35l35_id, + .probe = cs35l35_i2c_probe, + .remove = cs35l35_i2c_remove, +}; + +module_i2c_driver(cs35l35_i2c_driver); + +MODULE_DESCRIPTION("ASoC CS35L35 driver"); +MODULE_AUTHOR("Brian Austin, Cirrus Logic Inc, "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h new file mode 100644 index 000000000000..e27a7fef5fb1 --- /dev/null +++ b/sound/soc/codecs/cs35l35.h @@ -0,0 +1,284 @@ +/* + * cs35l35.h -- CS35L35 ALSA SoC audio driver + * + * Copyright 2016 Cirrus Logic, Inc. + * + * Author: Brian Austin + * + * 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. + * + */ + +#ifndef __CS35L35_H__ +#define __CS35L35_H__ + +#define CS35L35_FIRSTREG 0x01 +#define CS35L35_LASTREG 0x7E +#define CS35L35_CHIP_ID 0x00035A35 +#define CS35L35_DEVID_AB 0x01 /* Device ID A & B [RO] */ +#define CS35L35_DEVID_CD 0x02 /* Device ID C & D [RO] */ +#define CS35L35_DEVID_E 0x03 /* Device ID E [RO] */ +#define CS35L35_FAB_ID 0x04 /* Fab ID [RO] */ +#define CS35L35_REV_ID 0x05 /* Revision ID [RO] */ +#define CS35L35_PWRCTL1 0x06 /* Power Ctl 1 */ +#define CS35L35_PWRCTL2 0x07 /* Power Ctl 2 */ +#define CS35L35_PWRCTL3 0x08 /* Power Ctl 3 */ +#define CS35L35_CLK_CTL1 0x0A /* Clocking Ctl 1 */ +#define CS35L35_CLK_CTL2 0x0B /* Clocking Ctl 2 */ +#define CS35L35_CLK_CTL3 0x0C /* Clocking Ctl 3 */ +#define CS35L35_SP_FMT_CTL1 0x0D /* Serial Port Format CTL1 */ +#define CS35L35_SP_FMT_CTL2 0x0E /* Serial Port Format CTL2 */ +#define CS35L35_SP_FMT_CTL3 0x0F /* Serial Port Format CTL3 */ +#define CS35L35_MAG_COMP_CTL 0x13 /* Magnitude Comp CTL */ +#define CS35L35_AMP_INP_DRV_CTL 0x14 /* Amp Input Drive Ctl */ +#define CS35L35_AMP_DIG_VOL_CTL 0x15 /* Amplifier Dig Volume Ctl */ +#define CS35L35_AMP_DIG_VOL 0x16 /* Amplifier Dig Volume */ +#define CS35L35_ADV_DIG_VOL 0x17 /* Advisory Digital Volume */ +#define CS35L35_PROTECT_CTL 0x18 /* Amp Gain - Prot Ctl Param */ +#define CS35L35_AMP_GAIN_AUD_CTL 0x19 /* Amp Serial Port Gain Ctl */ +#define CS35L35_AMP_GAIN_PDM_CTL 0x1A /* Amplifier Gain PDM Ctl */ +#define CS35L35_AMP_GAIN_ADV_CTL 0x1B /* Amplifier Gain Ctl */ +#define CS35L35_GPI_CTL 0x1C /* GPI Ctl */ +#define CS35L35_BST_CVTR_V_CTL 0x1D /* Boost Conv Voltage Ctl */ +#define CS35L35_BST_PEAK_I 0x1E /* Boost Conv Peak Current */ +#define CS35L35_BST_RAMP_CTL 0x20 /* Boost Conv Soft Ramp Ctl */ +#define CS35L35_BST_CONV_COEF_1 0x21 /* Boost Conv Coefficients 1 */ +#define CS35L35_BST_CONV_COEF_2 0x22 /* Boost Conv Coefficients 2 */ +#define CS35L35_BST_CONV_SLOPE_COMP 0x23 /* Boost Conv Slope Comp */ +#define CS35L35_BST_CONV_SW_FREQ 0x24 /* Boost Conv L BST SW Freq */ +#define CS35L35_CLASS_H_CTL 0x30 /* CLS H Control */ +#define CS35L35_CLASS_H_HEADRM_CTL 0x31 /* CLS H Headroom Ctl */ +#define CS35L35_CLASS_H_RELEASE_RATE 0x32 /* CLS H Release Rate */ +#define CS35L35_CLASS_H_FET_DRIVE_CTL 0x33 /* CLS H Weak FET Drive Ctl */ +#define CS35L35_CLASS_H_VP_CTL 0x34 /* CLS H VP Ctl */ +#define CS35L35_CLASS_H_STATUS 0x38 /* CLS H Status */ +#define CS35L35_VPBR_CTL 0x3A /* VPBR Ctl */ +#define CS35L35_VPBR_VOL_CTL 0x3B /* VPBR Volume Ctl */ +#define CS35L35_VPBR_TIMING_CTL 0x3C /* VPBR Timing Ctl */ +#define CS35L35_VPBR_MODE_VOL_CTL 0x3D /* VPBR Mode/Attack Vol Ctl */ +#define CS35L35_VPBR_ATTEN_STATUS 0x4B /* VPBR Attenuation Status */ +#define CS35L35_SPKR_MON_CTL 0x4E /* Speaker Monitoring Ctl */ +#define CS35L35_IMON_SCALE_CTL 0x51 /* IMON Scale Ctl */ +#define CS35L35_AUDIN_RXLOC_CTL 0x52 /* Audio Input RX Loc Ctl */ +#define CS35L35_ADVIN_RXLOC_CTL 0x53 /* Advisory Input RX Loc Ctl */ +#define CS35L35_VMON_TXLOC_CTL 0x54 /* VMON TX Loc Ctl */ +#define CS35L35_IMON_TXLOC_CTL 0x55 /* IMON TX Loc Ctl */ +#define CS35L35_VPMON_TXLOC_CTL 0x56 /* VPMON TX Loc Ctl */ +#define CS35L35_VBSTMON_TXLOC_CTL 0x57 /* VBSTMON TX Loc Ctl */ +#define CS35L35_VPBR_STATUS_TXLOC_CTL 0x58 /* VPBR Status TX Loc Ctl */ +#define CS35L35_ZERO_FILL_LOC_CTL 0x59 /* Zero Fill Loc Ctl */ +#define CS35L35_AUDIN_DEPTH_CTL 0x5A /* Audio Input Depth Ctl */ +#define CS35L35_SPKMON_DEPTH_CTL 0x5B /* SPK Mon Output Depth Ctl */ +#define CS35L35_SUPMON_DEPTH_CTL 0x5C /* Supply Mon Out Depth Ctl */ +#define CS35L35_ZEROFILL_DEPTH_CTL 0x5D /* Zero Fill Mon Output Ctl */ +#define CS35L35_MULT_DEV_SYNCH1 0x62 /* Multidevice Synch */ +#define CS35L35_MULT_DEV_SYNCH2 0x63 /* Multidevice Synch 2 */ +#define CS35L35_PROT_RELEASE_CTL 0x64 /* Protection Release Ctl */ +#define CS35L35_DIAG_MODE_REG_LOCK 0x68 /* Diagnostic Mode Reg Lock */ +#define CS35L35_DIAG_MODE_CTL_1 0x69 /* Diagnostic Mode Ctl 1 */ +#define CS35L35_DIAG_MODE_CTL_2 0x6A /* Diagnostic Mode Ctl 2 */ +#define CS35L35_INT_MASK_1 0x70 /* Interrupt Mask 1 */ +#define CS35L35_INT_MASK_2 0x71 /* Interrupt Mask 2 */ +#define CS35L35_INT_MASK_3 0x72 /* Interrupt Mask 3 */ +#define CS35L35_INT_MASK_4 0x73 /* Interrupt Mask 4 */ +#define CS35L35_INT_STATUS_1 0x74 /* Interrupt Status 1 */ +#define CS35L35_INT_STATUS_2 0x75 /* Interrupt Status 2 */ +#define CS35L35_INT_STATUS_3 0x76 /* Interrupt Status 3 */ +#define CS35L35_INT_STATUS_4 0x77 /* Interrupt Status 4 */ +#define CS35L35_PLL_STATUS 0x78 /* PLL Status */ +#define CS35L35_OTP_TRIM_STATUS 0x7E /* OTP Trim Status */ + +#define CS35L35_MAX_REGISTER 0x7F + +/* CS35L35_PWRCTL1 */ +#define CS35L35_SFT_RST 0x80 +#define CS35L35_DISCHG_FLT 0x02 +#define CS35L35_PDN_ALL 0x01 + +/* CS35L35_PWRCTL2 */ +#define CS35L35_PDN_VMON 0x80 +#define CS35L35_PDN_IMON 0x40 +#define CS35L35_PDN_CLASSH 0x20 +#define CS35L35_PDN_VPBR 0x10 +#define CS35L35_PDN_BST 0x04 +#define CS35L35_PDN_AMP 0x01 + +/* CS35L35_PWRCTL3 */ +#define CS35L35_PDN_VBSTMON_OUT 0x10 +#define CS35L35_PDN_VMON_OUT 0x08 + +#define CS35L35_AUDIN_DEPTH_MASK 0x03 +#define CS35L35_AUDIN_DEPTH_SHIFT 0 +#define CS35L35_ADVIN_DEPTH_MASK 0x0C +#define CS35L35_ADVIN_DEPTH_SHIFT 2 +#define CS35L35_SDIN_DEPTH_8 0x01 +#define CS35L35_SDIN_DEPTH_16 0x02 +#define CS35L35_SDIN_DEPTH_24 0x03 + +#define CS35L35_SDOUT_DEPTH_8 0x01 +#define CS35L35_SDOUT_DEPTH_12 0x02 +#define CS35L35_SDOUT_DEPTH_16 0x03 + +#define CS35L35_AUD_IN_LR_MASK 0x80 +#define CS35L35_AUD_IN_LR_SHIFT 7 +#define CS35L35_ADV_IN_LR_MASK 0x80 +#define CS35L35_ADV_IN_LR_SHIFT 7 +#define CS35L35_AUD_IN_LOC_MASK 0x0F +#define CS35L35_AUD_IN_LOC_SHIFT 0 +#define CS35L35_ADV_IN_LOC_MASK 0x0F +#define CS35L35_ADV_IN_LOC_SHIFT 0 + +#define CS35L35_IMON_DEPTH_MASK 0x03 +#define CS35L35_IMON_DEPTH_SHIFT 0 +#define CS35L35_VMON_DEPTH_MASK 0x0C +#define CS35L35_VMON_DEPTH_SHIFT 2 +#define CS35L35_VBSTMON_DEPTH_MASK 0x03 +#define CS35L35_VBSTMON_DEPTH_SHIFT 0 +#define CS35L35_VPMON_DEPTH_MASK 0x0C +#define CS35L35_VPMON_DEPTH_SHIFT 2 +#define CS35L35_VPBRSTAT_DEPTH_MASK 0x30 +#define CS35L35_VPBRSTAT_DEPTH_SHIFT 4 +#define CS35L35_ZEROFILL_DEPTH_MASK 0x03 +#define CS35L35_ZEROFILL_DEPTH_SHIFT 0x00 + +#define CS35L35_MON_TXLOC_MASK 0x3F +#define CS35L35_MON_TXLOC_SHIFT 0 +#define CS35L35_MON_FRM_MASK 0x80 +#define CS35L35_MON_FRM_SHIFT 7 + +#define CS35L35_MS_MASK 0x80 +#define CS35L35_MS_SHIFT 7 +#define CS35L35_SPMODE_MASK 0x40 +#define CS35L35_SP_DRV_MASK 0x10 +#define CS35L35_SP_DRV_SHIFT 4 +#define CS35L35_CLK_CTL2_MASK 0xFF +#define CS35L35_PDM_MODE_MASK 0x40 +#define CS35L35_PDM_MODE_SHIFT 6 +#define CS35L35_CLK_SOURCE_MASK 0x03 +#define CS35L35_CLK_SOURCE_SHIFT 0 +#define CS35L35_CLK_SOURCE_MCLK 0 +#define CS35L35_CLK_SOURCE_SCLK 1 +#define CS35L35_CLK_SOURCE_PDM 2 + +#define CS35L35_SP_SCLKS_MASK 0x0F +#define CS35L35_SP_SCLKS_SHIFT 0x00 +#define CS35L35_SP_SCLKS_16FS 0x03 +#define CS35L35_SP_SCLKS_32FS 0x07 +#define CS35L35_SP_SCLKS_48FS 0x0B +#define CS35L35_SP_SCLKS_64FS 0x0F +#define CS35L35_SP_RATE_MASK 0xC0 + +#define CS35L35_PDN_BST_MASK 0x06 +#define CS35L35_PDN_BST_FETON_SHIFT 1 +#define CS35L35_PDN_BST_FETOFF_SHIFT 2 +#define CS35L35_PWR2_PDN_MASK 0xE0 +#define CS35L35_PWR3_PDN_MASK 0x1E +#define CS35L35_PDN_ALL_MASK 0x01 +#define CS35L35_DISCHG_FILT_MASK 0x02 +#define CS35L35_DISCHG_FILT_SHIFT 1 +#define CS35L35_MCLK_DIS_MASK 0x04 +#define CS35L35_MCLK_DIS_SHIFT 2 + +#define CS35L35_BST_CTL_MASK 0x7F +#define CS35L35_BST_CTL_SHIFT 0 +#define CS35L35_BST_IPK_MASK 0x1F +#define CS35L35_BST_IPK_SHIFT 0 +#define CS35L35_AMP_MUTE_MASK 0x20 +#define CS35L35_AMP_MUTE_SHIFT 5 +#define CS35L35_AMP_GAIN_ZC_MASK 0x10 +#define CS35L35_AMP_GAIN_ZC_SHIFT 4 + +/* Class H Algorithm Control */ +#define CS35L35_CH_STEREO_MASK 0x40 +#define CS35L35_CH_STEREO_SHIFT 6 +#define CS35L35_CH_BST_OVR_MASK 0x04 +#define CS35L35_CH_BST_OVR_SHIFT 2 +#define CS35L35_CH_BST_LIM_MASK 0x08 +#define CS35L35_CH_BST_LIM_SHIFT 3 +#define CS35L35_CH_MEM_DEPTH_MASK 0x01 +#define CS35L35_CH_MEM_DEPTH_SHIFT 0 +#define CS35L35_CH_HDRM_CTL_MASK 0x3F +#define CS35L35_CH_HDRM_CTL_SHIFT 0 +#define CS35L35_CH_REL_RATE_MASK 0xFF +#define CS35L35_CH_REL_RATE_SHIFT 0 +#define CS35L35_CH_WKFET_DIS_MASK 0x80 +#define CS35L35_CH_WKFET_DIS_SHIFT 7 +#define CS35L35_CH_WKFET_DEL_MASK 0x70 +#define CS35L35_CH_WKFET_DEL_SHIFT 4 +#define CS35L35_CH_WKFET_THLD_MASK 0x0F +#define CS35L35_CH_WKFET_THLD_SHIFT 0 +#define CS35L35_CH_VP_AUTO_MASK 0x80 +#define CS35L35_CH_VP_AUTO_SHIFT 7 +#define CS35L35_CH_VP_RATE_MASK 0x60 +#define CS35L35_CH_VP_RATE_SHIFT 5 +#define CS35L35_CH_VP_MAN_MASK 0x1F +#define CS35L35_CH_VP_MAN_SHIFT 0 + +/* CS35L35_PROT_RELEASE_CTL */ +#define CS35L35_CAL_ERR_RLS 0x80 +#define CS35L35_SHORT_RLS 0x04 +#define CS35L35_OTW_RLS 0x02 +#define CS35L35_OTE_RLS 0x01 + +/* INT Mask Registers */ +#define CS35L35_INT1_CRIT_MASK 0x38 +#define CS35L35_INT2_CRIT_MASK 0xEF +#define CS35L35_INT3_CRIT_MASK 0xEE +#define CS35L35_INT4_CRIT_MASK 0xFF + +/* PDN DONE Masks */ +#define CS35L35_M_PDN_DONE_SHIFT 4 +#define CS35L35_M_PDN_DONE_MASK 0x10 + +/* CS35L35_INT_1 */ +#define CS35L35_CAL_ERR 0x80 +#define CS35L35_OTP_ERR 0x40 +#define CS35L35_LRCLK_ERR 0x20 +#define CS35L35_SPCLK_ERR 0x10 +#define CS35L35_MCLK_ERR 0x08 +#define CS35L35_AMP_SHORT 0x04 +#define CS35L35_OTW 0x02 +#define CS35L35_OTE 0x01 + +/* CS35L35_INT_2 */ +#define CS35L35_PDN_DONE 0x10 +#define CS35L35_VPBR_ERR 0x02 +#define CS35L35_VPBR_CLR 0x01 + +/* CS35L35_INT_3 */ +#define CS35L35_BST_HIGH 0x10 +#define CS35L35_BST_HIGH_FLAG 0x08 +#define CS35L35_BST_IPK_FLAG 0x04 +#define CS35L35_LBST_SHORT 0x01 + +/* CS35L35_INT_4 */ +#define CS35L35_VMON_OVFL 0x08 +#define CS35L35_IMON_OVFL 0x04 + +#define CS35L35_FORMATS (SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct cs35l35_private { + struct snd_soc_codec *codec; + struct cs35l35_platform_data pdata; + struct regmap *regmap; + struct regulator_bulk_data supplies[2]; + int num_supplies; + int sysclk; + int sclk; + bool pdm_mode; + bool i2s_mode; + bool slave_mode; + /* GPIO for /RST */ + struct gpio_desc *reset_gpio; + struct completion pdn_done; +}; + +static const char * const cs35l35_supplies[] = { + "VA", + "VP", +}; + +#endif From ed864461bfbdacefc242d4c0675989915a1431b0 Mon Sep 17 00:00:00 2001 From: Brian Austin Date: Mon, 6 Mar 2017 08:08:00 -0600 Subject: [PATCH 02/24] ASoC: cs35l35: Add bindings file for CS35L35 Add bindings documentation for CS35L35 Amplifier Signed-off-by: Brian Austin Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/cs35l35.txt | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/cs35l35.txt diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt new file mode 100644 index 000000000000..07d9c0f177fb --- /dev/null +++ b/Documentation/devicetree/bindings/sound/cs35l35.txt @@ -0,0 +1,171 @@ +CS35L35 Boosted Speaker Amplifier + +Required properties: + + - compatible : "cirrus,cs35l35" + + - reg : the I2C address of the device for I2C + + - VA-supply, VP-supply : power supplies for the device, + as covered in + Documentation/devicetree/bindings/regulator/regulator.txt. + + - interrupt-parent : Specifies the phandle of the interrupt controller to + which the IRQs from CS35L35 are delivered to. + - interrupts : IRQ line info CS35L35. + (See Documentation/devicetree/bindings/interrupt-controller/interrupts.txt + for further information relating to interrupt properties) + +Optional properties: + - reset-gpios : gpio used to reset the amplifier + + - cirrus,stereo-config : Boolean to determine if there are 2 AMPs for a + Stereo configuration + + - cirrus,audio-channel : Set Location of Audio Signal on Serial Port + 0 = Data Packet received on Left I2S Channel + 1 = Data Packet received on Right I2S Channel + + - cirrus,advisory-channel : Set Location of Advisory Signal on Serial Port + 0 = Data Packet received on Left I2S Channel + 1 = Data Packet received on Right I2S Channel + + - cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal + if 2 Devices share Boost BST_CTL + + - cirrus,sp-drv-strength : Value for setting the Serial Port drive strength + Table 3-10 of the datasheet lists drive-strength specifications + 0 = 1x (Default) + 1 = .5x + + - cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control + powers down with a rectification FET On or Off. If VSPK is supplied + externally then FET is off. + + - cirrus,boost-ctl-millivolt : Boost Voltage Value. Configures the boost + converter's output voltage in mV. The range is from 2600mV to 9000mV with + increments of 100mV. + (Default) VP + + - cirrus,boost-peak-milliamp : Boost-converter peak current limit in mA. + Configures the peak current by monitoring the current through the boost FET. + Range starts at 1680mA and goes to a maximum of 4480mA with increments of + 110mA. + (Default) 2.46 Amps + + - cirrus,amp-gain-zc : Boolean to determine if to use Amplifier gain-change + zero-cross + +Optional H/G Algorithm sub-node: + + The cs35l35 node can have a single "cirrus,classh-internal-algo" sub-node + that will disable automatic control of the internal H/G Algorithm. + + It is strongly recommended that the Datasheet be referenced when adjusting + or using these Class H Algorithm controls over the internal Algorithm. + Serious damage can occur to the Device and surrounding components. + + - cirrus,classh-internal-algo : Sub-node for the Internal Class H Algorithm + See Section 4.3 Internal Class H Algorithm in the Datasheet. + If not used, the device manages the ClassH Algorithm internally. + +Optional properties for the "cirrus,classh-internal-algo" Sub-node + + Section 7.29 Class H Control + - cirrus,classh-bst-overide : Boolean + - cirrus,classh-bst-max-limit + - cirrus,classh-mem-depth + + Section 7.30 Class H Headroom Control + - cirrus,classh-headroom + + Section 7.31 Class H Release Rate + - cirrus,classh-release-rate + + Section 7.32 Class H Weak FET Drive Control + - cirrus,classh-wk-fet-disable + - cirrus,classh-wk-fet-delay + - cirrus,classh-wk-fet-thld + + Section 7.34 Class H VP Control + - cirrus,classh-vpch-auto + - cirrus,classh-vpch-rate + - cirrus,classh-vpch-man + +Optional Monitor Signal Format sub-node: + + The cs35l35 node can have a single "cirrus,monitor-signal-format" sub-node + for adjusting the Depth, Location and Frame of the Monitoring Signals + for Algorithms. + + See Sections 4.8.2 through 4.8.4 Serial-Port Control in the Datasheet + + -cirrus,monitor-signal-format : Sub-node for the Monitor Signaling Formating + on the I2S Port. Each of the 3 8 bit values in the array contain the settings + for depth, location, and frame. + + If not used, the defaults for the 6 monitor signals is used. + + Sections 7.44 - 7.53 lists values for the depth, location, and frame + for each monitoring signal. + + - cirrus,imon : 3 8 bit values to set the depth, location, and frame + of the IMON monitor signal. + + - cirrus,vmon : 3 8 bit values to set the depth, location, and frame + of the VMON monitor signal. + + - cirrus,vpmon : 3 8 bit values to set the depth, location, and frame + of the VPMON monitor signal. + + - cirrus,vbstmon : 3 8 bit values to set the depth, location, and frame + of the VBSTMON monitor signal + + - cirrus,vpbrstat : 3 8 bit values to set the depth, location, and frame + of the VPBRSTAT monitor signal + + - cirrus,zerofill : 3 8 bit values to set the depth, location, and frame\ + of the ZEROFILL packet in the monitor signal + +Example: + +cs35l35: cs35l35@20 { + compatible = "cirrus,cs35l35"; + reg = <0x20>; + VA-supply = <&dummy_vreg>; + VP-supply = <&dummy_vreg>; + reset-gpios = <&axi_gpio 54 0>; + interrupt-parent = <&gpio8>; + interrupts = <3 IRQ_TYPE_LEVEL_LOW>; + cirrus,boost-ctl-millivolt = <9000>; + + cirrus,stereo-config; + cirrus,audio-channel = <0x00>; + cirrus,advisory-channel = <0x01>; + cirrus,shared-boost; + + cirrus,classh-internal-algo { + cirrus,classh-bst-overide; + cirrus,classh-bst-max-limit = <0x01>; + cirrus,classh-mem-depth = <0x01>; + cirrus,classh-release-rate = <0x08>; + cirrus,classh-headroom-millivolt = <0x0B>; + cirrus,classh-wk-fet-disable = <0x01>; + cirrus,classh-wk-fet-delay = <0x04>; + cirrus,classh-wk-fet-thld = <0x01>; + cirrus,classh-vpch-auto = <0x01>; + cirrus,classh-vpch-rate = <0x02>; + cirrus,classh-vpch-man = <0x05>; + }; + + /* Depth, Location, Frame */ + cirrus,monitor-signal-format { + cirrus,imon = /bits/ 8 <0x03 0x00 0x01>; + cirrus,vmon = /bits/ 8 <0x03 0x00 0x00>; + cirrus,vpmon = /bits/ 8 <0x03 0x04 0x00>; + cirrus,vbstmon = /bits/ 8 <0x03 0x04 0x01>; + cirrus,vpbrstat = /bits/ 8 <0x00 0x04 0x00>; + cirrus,zerofill = /bits/ 8 <0x00 0x00 0x00>; + }; + +}; From 85825d5e886912655f6c1896d76035ce1316254b Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Mar 2017 18:44:50 +0100 Subject: [PATCH 03/24] ASoC: dio2125: add dio2125 amp driver The dio2125 is a stereo output driver with adjustable gain. Signed-off-by: Jerome Brunet Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 5 ++ sound/soc/codecs/Makefile | 2 + sound/soc/codecs/dio2125.c | 120 +++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 sound/soc/codecs/dio2125.c diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e49e9da7f1f6..5e5fcb660ca3 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -69,6 +69,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_DA7219 if I2C select SND_SOC_DA732X if I2C select SND_SOC_DA9055 if I2C + select SND_SOC_DIO2125 select SND_SOC_DMIC select SND_SOC_ES8328_SPI if SPI_MASTER select SND_SOC_ES8328_I2C if I2C @@ -516,6 +517,10 @@ config SND_SOC_DA732X config SND_SOC_DA9055 tristate +config SND_SOC_DIO2125 + tristate "Dioo DIO2125 Amplifier" + select GPIOLIB + config SND_SOC_DMIC tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1796cb987e71..04894fa8891b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -221,6 +221,7 @@ snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o snd-soc-wm-hubs-objs := wm_hubs.o # Amp +snd-soc-dio2125-objs := dio2125.o snd-soc-max9877-objs := max9877.o snd-soc-max98504-objs := max98504.o snd-soc-tpa6130a2-objs := tpa6130a2.o @@ -448,6 +449,7 @@ obj-$(CONFIG_SND_SOC_WM_ADSP) += snd-soc-wm-adsp.o obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp +obj-$(CONFIG_SND_SOC_DIO2125) += snd-soc-dio2125.o obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o obj-$(CONFIG_SND_SOC_MAX98504) += snd-soc-max98504.o obj-$(CONFIG_SND_SOC_TPA6130A2) += snd-soc-tpa6130a2.o diff --git a/sound/soc/codecs/dio2125.c b/sound/soc/codecs/dio2125.c new file mode 100644 index 000000000000..015e310556d3 --- /dev/null +++ b/sound/soc/codecs/dio2125.c @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017 BayLibre, SAS. + * Author: Jerome Brunet + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * The full GNU General Public License is included in this distribution + * in the file called COPYING. + */ + +#include +#include +#include + +#define DRV_NAME "dio2125" + +struct dio2125 { + struct gpio_desc *gpiod_enable; +}; + +static int drv_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_component *c = snd_soc_dapm_to_component(w->dapm); + struct dio2125 *priv = snd_soc_component_get_drvdata(c); + int val; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + val = 1; + break; + case SND_SOC_DAPM_PRE_PMD: + val = 0; + break; + default: + WARN(1, "Unexpected event"); + return -EINVAL; + } + + gpiod_set_value(priv->gpiod_enable, val); + + return 0; +} + +static const struct snd_soc_dapm_widget dio2125_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("INL"), + SND_SOC_DAPM_INPUT("INR"), + SND_SOC_DAPM_OUT_DRV_E("DRV", SND_SOC_NOPM, 0, 0, NULL, 0, drv_event, + (SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD)), + SND_SOC_DAPM_OUTPUT("OUTL"), + SND_SOC_DAPM_OUTPUT("OUTR"), +}; + +static const struct snd_soc_dapm_route dio2125_dapm_routes[] = { + { "DRV", NULL, "INL" }, + { "DRV", NULL, "INR" }, + { "OUTL", NULL, "DRV" }, + { "OUTR", NULL, "DRV" }, +}; + +static const struct snd_soc_component_driver dio2125_component_driver = { + .dapm_widgets = dio2125_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(dio2125_dapm_widgets), + .dapm_routes = dio2125_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(dio2125_dapm_routes), +}; + +static int dio2125_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dio2125 *priv; + int err; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (priv == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, priv); + + priv->gpiod_enable = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(priv->gpiod_enable)) { + err = PTR_ERR(priv->gpiod_enable); + if (err != -EPROBE_DEFER) + dev_err(dev, "Failed to get 'enable' gpio: %d", err); + return err; + } + + return devm_snd_soc_register_component(dev, &dio2125_component_driver, + NULL, 0); +} + +#ifdef CONFIG_OF +static const struct of_device_id dio2125_ids[] = { + { .compatible = "dioo,dio2125", }, + { } +}; +MODULE_DEVICE_TABLE(of, dio2125_ids); +#endif + +static struct platform_driver dio2125_driver = { + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(dio2125_ids), + }, + .probe = dio2125_probe, +}; + +module_platform_driver(dio2125_driver); + +MODULE_DESCRIPTION("ASoC DIO2125 output driver"); +MODULE_AUTHOR("Jerome Brunet "); +MODULE_LICENSE("GPL"); From 1e03580b3a7213292fae9a6677edfaa83843fa4e Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Mon, 6 Mar 2017 18:44:51 +0100 Subject: [PATCH 04/24] ASoC: dio2125: add dt-bindings Signed-off-by: Jerome Brunet Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/dioo,dio2125.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/dioo,dio2125.txt diff --git a/Documentation/devicetree/bindings/sound/dioo,dio2125.txt b/Documentation/devicetree/bindings/sound/dioo,dio2125.txt new file mode 100644 index 000000000000..63dbfe0f11d0 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/dioo,dio2125.txt @@ -0,0 +1,12 @@ +DIO2125 Audio Driver + +Required properties: +- compatible : "dioo,dio2125" +- enable-gpios : the gpio connected to the enable pin of the dio2125 + +Example: + +amp: analog-amplifier { + compatible = "dioo,dio2125"; + enable-gpios = <&gpio GPIOH_3 0>; +}; From ea2a2ad17ca1e9dbb01ac935f1b1b53f99f73b13 Mon Sep 17 00:00:00 2001 From: Jerome Brunet Date: Tue, 7 Mar 2017 14:12:22 +0100 Subject: [PATCH 05/24] ASoC: dio2125: use gpiod_set_value_cansleep Use the "cansleep" variant of gpiod_set_value so the driver can be used with slow gpio controllers as well. Fixes: 85825d5e8869 ("ASoC: dio2125: add dio2125 amp driver") Reported-by: Mark Brown Signed-off-by: Jerome Brunet Signed-off-by: Mark Brown --- sound/soc/codecs/dio2125.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/dio2125.c b/sound/soc/codecs/dio2125.c index 015e310556d3..09451cd44f9b 100644 --- a/sound/soc/codecs/dio2125.c +++ b/sound/soc/codecs/dio2125.c @@ -46,7 +46,7 @@ static int drv_event(struct snd_soc_dapm_widget *w, return -EINVAL; } - gpiod_set_value(priv->gpiod_enable, val); + gpiod_set_value_cansleep(priv->gpiod_enable, val); return 0; } From 1bb06ada038548b4e2449159e80badf106bb779f Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Mar 2017 16:42:47 +0000 Subject: [PATCH 06/24] ASoC: cs35l35: Add missing return in probe A return statement is missing just before the error paths at the end of probe. This causes us to fall straight into the error path and disable the supplies and re-enable reset, as these are only controlled during probe this causes the part to no longer function. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 260ed42c71e9..48b45dc904ea 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1509,6 +1509,8 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, goto err; } + return 0; + err: regulator_bulk_disable(cs35l35->num_supplies, cs35l35->supplies); From 45114c364d476ba0bbc294d63955f49fbf378779 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Mar 2017 16:42:48 +0000 Subject: [PATCH 07/24] ASoC: cs35l35: Add device tree binding for I2S drive configuration Add a binding for setting how the I2S pins are driven in unused slots, currently the chip will just use the default of drive 0, however this causes issues when multiple devices are attached to the same bus. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/cs35l35.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt index 07d9c0f177fb..958f0eabfdfc 100644 --- a/Documentation/devicetree/bindings/sound/cs35l35.txt +++ b/Documentation/devicetree/bindings/sound/cs35l35.txt @@ -37,6 +37,11 @@ Optional properties: Table 3-10 of the datasheet lists drive-strength specifications 0 = 1x (Default) 1 = .5x + - cirrus,sp-drv-unused : Determines how unused slots should be driven on the + Serial Port. + 0 - Hi-Z + 2 - Drive 0's (Default) + 3 - Drive 1's - cirrus,bst-pdn-fet-on : Boolean to determine if the Boost PDN control powers down with a rectification FET On or Off. If VSPK is supplied From 8d45f2d23864ae1582d095c54605540cd3640169 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Mar 2017 16:42:49 +0000 Subject: [PATCH 08/24] ASoC: cs35l35: Add for configuring drive mode in unused slots Add support for setting how the I2S pins are driven in unused slots, currently the chip will just use the default of drive 0, however this causes issues when multiple devices are attached to the same bus. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- include/sound/cs35l35.h | 2 ++ sound/soc/codecs/cs35l35.c | 7 +++++++ sound/soc/codecs/cs35l35.h | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h index de92e452bf93..983c610eba2e 100644 --- a/include/sound/cs35l35.h +++ b/include/sound/cs35l35.h @@ -80,6 +80,8 @@ struct cs35l35_platform_data { bool stereo; /* serial port drive strength */ int sp_drv_str; + /* serial port drive in unused slots */ + int sp_drv_unused; /* Boost Power Down with FET */ bool bst_pdn_fet_on; /* Boost Voltage : used if ClassH Algo Enabled */ diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 48b45dc904ea..5a9fe5addb86 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -789,6 +789,11 @@ static int cs35l35_codec_probe(struct snd_soc_codec *codec) CS35L35_SP_DRV_MASK, cs35l35->pdata.sp_drv_str << CS35L35_SP_DRV_SHIFT); + if (cs35l35->pdata.sp_drv_unused) + regmap_update_bits(cs35l35->regmap, CS35L35_SP_FMT_CTL3, + CS35L35_SP_I2S_DRV_MASK, + cs35l35->pdata.sp_drv_unused << + CS35L35_SP_I2S_DRV_SHIFT); if (classh->classh_algo_enable) { if (classh->classh_bst_override) @@ -1169,6 +1174,8 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, if (of_property_read_u32(np, "cirrus,sp-drv-strength", &val32) >= 0) pdata->sp_drv_str = val32; + if (of_property_read_u32(np, "cirrus,sp-drv-unused", &val32) >= 0) + pdata->sp_drv_unused = val32 | CS35L35_VALID_PDATA; pdata->stereo = of_property_read_bool(np, "cirrus,stereo-config"); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h index e27a7fef5fb1..c203081fc94c 100644 --- a/sound/soc/codecs/cs35l35.h +++ b/sound/soc/codecs/cs35l35.h @@ -190,6 +190,10 @@ #define CS35L35_AMP_GAIN_ZC_MASK 0x10 #define CS35L35_AMP_GAIN_ZC_SHIFT 4 +/* CS35L35_SP_FMT_CTL3 */ +#define CS35L35_SP_I2S_DRV_MASK 0x03 +#define CS35L35_SP_I2S_DRV_SHIFT 0 + /* Class H Algorithm Control */ #define CS35L35_CH_STEREO_MASK 0x40 #define CS35L35_CH_STEREO_SHIFT 6 From 1f758cd9da61a1ae2202405746d259ba640d1e8a Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Mar 2017 16:42:50 +0000 Subject: [PATCH 09/24] ASoC: cs35l35: Add local variable for dev in probe Tidy up the code a little by adding a local variable for i2c_client->dev rather than referring to it explicitly everytime. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 66 +++++++++++++++----------------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 5a9fe5addb86..99bcdb962359 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1354,16 +1354,14 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id) { struct cs35l35_private *cs35l35; - struct cs35l35_platform_data *pdata = - dev_get_platdata(&i2c_client->dev); + struct device *dev = &i2c_client->dev; + struct cs35l35_platform_data *pdata = dev_get_platdata(dev); int i; int ret; unsigned int devid = 0; unsigned int reg; - cs35l35 = devm_kzalloc(&i2c_client->dev, - sizeof(struct cs35l35_private), - GFP_KERNEL); + cs35l35 = devm_kzalloc(dev, sizeof(struct cs35l35_private), GFP_KERNEL); if (!cs35l35) return -ENOMEM; @@ -1371,7 +1369,7 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); if (IS_ERR(cs35l35->regmap)) { ret = PTR_ERR(cs35l35->regmap); - dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret); + dev_err(dev, "regmap_init() failed: %d\n", ret); goto err; } @@ -1379,22 +1377,18 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, cs35l35->supplies[i].supply = cs35l35_supplies[i]; cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); - ret = devm_regulator_bulk_get(&i2c_client->dev, - cs35l35->num_supplies, - cs35l35->supplies); + ret = devm_regulator_bulk_get(dev, cs35l35->num_supplies, + cs35l35->supplies); if (ret != 0) { - dev_err(&i2c_client->dev, - "Failed to request core supplies: %d\n", - ret); + dev_err(dev, "Failed to request core supplies: %d\n", ret); return ret; } if (pdata) { cs35l35->pdata = *pdata; } else { - pdata = devm_kzalloc(&i2c_client->dev, - sizeof(struct cs35l35_platform_data), - GFP_KERNEL); + pdata = devm_kzalloc(dev, sizeof(struct cs35l35_platform_data), + GFP_KERNEL); if (!pdata) return -ENOMEM; if (i2c_client->dev.of_node) { @@ -1409,24 +1403,21 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, ret = regulator_bulk_enable(cs35l35->num_supplies, cs35l35->supplies); if (ret != 0) { - dev_err(&i2c_client->dev, - "Failed to enable core supplies: %d\n", - ret); + dev_err(dev, "Failed to enable core supplies: %d\n", ret); return ret; } /* returning NULL can be valid if in stereo mode */ - cs35l35->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev, - "reset", GPIOD_OUT_LOW); + cs35l35->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_LOW); if (IS_ERR(cs35l35->reset_gpio)) { ret = PTR_ERR(cs35l35->reset_gpio); if (ret == -EBUSY) { - dev_info(&i2c_client->dev, + dev_info(dev, "Reset line busy, assuming shared reset\n"); cs35l35->reset_gpio = NULL; } else { - dev_err(&i2c_client->dev, - "Failed to get reset GPIO: %d\n", ret); + dev_err(dev, "Failed to get reset GPIO: %d\n", ret); goto err; } } @@ -1435,11 +1426,11 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, init_completion(&cs35l35->pdn_done); - ret = devm_request_threaded_irq(&i2c_client->dev, i2c_client->irq, NULL, - cs35l35_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "cs35l35", cs35l35); + ret = devm_request_threaded_irq(dev, i2c_client->irq, NULL, cs35l35_irq, + IRQF_ONESHOT | IRQF_TRIGGER_LOW, + "cs35l35", cs35l35); if (ret != 0) { - dev_err(&i2c_client->dev, "Failed to request IRQ: %d\n", ret); + dev_err(dev, "Failed to request IRQ: %d\n", ret); goto err; } /* initialize codec */ @@ -1452,8 +1443,7 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, devid |= (reg & 0xF0) >> 4; if (devid != CS35L35_CHIP_ID) { - dev_err(&i2c_client->dev, - "CS35L35 Device ID (%X). Expected ID %X\n", + dev_err(dev, "CS35L35 Device ID (%X). Expected ID %X\n", devid, CS35L35_CHIP_ID); ret = -ENODEV; goto err; @@ -1461,21 +1451,19 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, ret = regmap_read(cs35l35->regmap, CS35L35_REV_ID, ®); if (ret < 0) { - dev_err(&i2c_client->dev, "Get Revision ID failed: %d\n", ret); + dev_err(dev, "Get Revision ID failed: %d\n", ret); goto err; } ret = regmap_register_patch(cs35l35->regmap, cs35l35_errata_patch, ARRAY_SIZE(cs35l35_errata_patch)); if (ret < 0) { - dev_err(&i2c_client->dev, "Failed to apply errata patch: %d\n", - ret); + dev_err(dev, "Failed to apply errata patch: %d\n", ret); goto err; } - dev_info(&i2c_client->dev, - "Cirrus Logic CS35L35 (%x), Revision: %02X\n", devid, - ret & 0xFF); + dev_info(dev, "Cirrus Logic CS35L35 (%x), Revision: %02X\n", + devid, ret & 0xFF); /* Set the INT Masks for critical errors */ regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, @@ -1507,12 +1495,10 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, regmap_update_bits(cs35l35->regmap, CS35L35_PROTECT_CTL, CS35L35_AMP_MUTE_MASK, 1 << CS35L35_AMP_MUTE_SHIFT); - ret = snd_soc_register_codec(&i2c_client->dev, - &soc_codec_dev_cs35l35, cs35l35_dai, - ARRAY_SIZE(cs35l35_dai)); + ret = snd_soc_register_codec(dev, &soc_codec_dev_cs35l35, cs35l35_dai, + ARRAY_SIZE(cs35l35_dai)); if (ret < 0) { - dev_err(&i2c_client->dev, - "Failed to register codec: %d\n", ret); + dev_err(dev, "Failed to register codec: %d\n", ret); goto err; } From bf5043d69c9bf33696f9151552175f1203f8c9d9 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Wed, 8 Mar 2017 16:42:51 +0000 Subject: [PATCH 10/24] ASoC: cs35l35: Add IRQF_SHARED to IRQ flags As it is quite common to use a stereo pair of amps but share the IRQ line between them both add the IRQF_SHARED flag whilst requesting cs35l35's IRQ. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 99bcdb962359..7c4d74ec32cb 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1427,8 +1427,8 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, init_completion(&cs35l35->pdn_done); ret = devm_request_threaded_irq(dev, i2c_client->irq, NULL, cs35l35_irq, - IRQF_ONESHOT | IRQF_TRIGGER_LOW, - "cs35l35", cs35l35); + IRQF_ONESHOT | IRQF_TRIGGER_LOW | + IRQF_SHARED, "cs35l35", cs35l35); if (ret != 0) { dev_err(dev, "Failed to request IRQ: %d\n", ret); goto err; From 8f42c23a9861df7796e019d32fd5c4dea01c8e51 Mon Sep 17 00:00:00 2001 From: Pierre-Louis Bossart Date: Thu, 9 Mar 2017 18:18:58 -0600 Subject: [PATCH 11/24] ASoC: da7213: add ACPI support Add DLGS7212 and DLGS7213 HID Signed-off-by: Pierre-Louis Bossart Acked-by: Vinod Koul Signed-off-by: Mark Brown --- sound/soc/codecs/da7213.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/sound/soc/codecs/da7213.c b/sound/soc/codecs/da7213.c index 12da55882c06..6dd7578f0bb8 100644 --- a/sound/soc/codecs/da7213.c +++ b/sound/soc/codecs/da7213.c @@ -12,6 +12,7 @@ * option) any later version. */ +#include #include #include #include @@ -1528,12 +1529,23 @@ static int da7213_set_bias_level(struct snd_soc_codec *codec, return 0; } +#if defined(CONFIG_OF) /* DT */ static const struct of_device_id da7213_of_match[] = { { .compatible = "dlg,da7213", }, { } }; MODULE_DEVICE_TABLE(of, da7213_of_match); +#endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id da7213_acpi_match[] = { + { "DLGS7212", 0}, + { "DLGS7213", 0}, + { }, +}; +MODULE_DEVICE_TABLE(acpi, da7213_acpi_match); +#endif static enum da7213_micbias_voltage da7213_of_micbias_lvl(struct snd_soc_codec *codec, u32 val) @@ -1844,6 +1856,7 @@ static struct i2c_driver da7213_i2c_driver = { .driver = { .name = "da7213", .of_match_table = of_match_ptr(da7213_of_match), + .acpi_match_table = ACPI_PTR(da7213_acpi_match), }, .probe = da7213_i2c_probe, .remove = da7213_remove, From 82875163a8ef1e25477402c5ebb8f5beaea5e93e Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Sat, 11 Mar 2017 20:57:07 +0800 Subject: [PATCH 12/24] ASoC: cs35l35: Fix display revision id Signed-off-by: Axel Lin Acked-by: Paul Handrigan Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 7c4d74ec32cb..a9e45dea309e 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1463,7 +1463,7 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, } dev_info(dev, "Cirrus Logic CS35L35 (%x), Revision: %02X\n", - devid, ret & 0xFF); + devid, reg & 0xFF); /* Set the INT Masks for critical errors */ regmap_write(cs35l35->regmap, CS35L35_INT_MASK_1, From 03ff570c6b45b27e233f5cb1fd04404374fd883f Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 11 Mar 2017 19:19:22 +0000 Subject: [PATCH 13/24] ASoC: cs35l35: trivial fix to indentation Remove extraneous tab to correct the nesting level indentation Detected by CoverityScan, CID#1416584 ("Nesting level does not match indentation") Signed-off-by: Colin Ian King Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index a9e45dea309e..a5da1d511a8a 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1375,7 +1375,8 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, for (i = 0; i < ARRAY_SIZE(cs35l35_supplies); i++) cs35l35->supplies[i].supply = cs35l35_supplies[i]; - cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); + + cs35l35->num_supplies = ARRAY_SIZE(cs35l35_supplies); ret = devm_regulator_bulk_get(dev, cs35l35->num_supplies, cs35l35->supplies); From f3a612a655c1ca585c89c654c06e56eb2f8539e4 Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Sun, 12 Mar 2017 09:11:32 +0800 Subject: [PATCH 14/24] ASoC: cs35l35: fix semicolon.cocci warnings sound/soc/codecs/cs35l35.c:706:2-3: Unneeded semicolon sound/soc/codecs/cs35l35.c:543:4-5: Unneeded semicolon sound/soc/codecs/cs35l35.c:553:4-5: Unneeded semicolon Remove unneeded semicolon. Generated by: scripts/coccinelle/misc/semicolon.cocci CC: Brian Austin Signed-off-by: Fengguang Wu Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index a5da1d511a8a..6a266516f543 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -540,7 +540,7 @@ static int cs35l35_hw_params(struct snd_pcm_substream *substream, default: dev_err(codec->dev, "ratio not supported\n"); return -EINVAL; - }; + } } else { /* Only certain ratios supported in I2S MASTER Mode */ switch (sp_sclks) { @@ -550,7 +550,7 @@ static int cs35l35_hw_params(struct snd_pcm_substream *substream, default: dev_err(codec->dev, "ratio not supported\n"); return -EINVAL; - }; + } } ret = regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL3, @@ -703,7 +703,7 @@ static int cs35l35_codec_set_sysclk(struct snd_soc_codec *codec, default: dev_err(codec->dev, "Invalid CLK Source\n"); return -EINVAL; - }; + } switch (freq) { case 5644800: From bfe41c678d49f440de3ae80b945f7f94d5dbd340 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 13 Mar 2017 15:32:37 +0300 Subject: [PATCH 15/24] ASoC: cs35l35: returning uninitialized in probe() If cs35l35->pdata.stereo is false then "ret" isn't initialized. Fixes: 6387f866a2cc ("ASoC: Add support for Cirrus Logic CS35L35 Amplifier") Signed-off-by: Dan Carpenter Acked-by: Paul Handrigan Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 6a266516f543..05117fca7e3c 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -969,7 +969,7 @@ static int cs35l35_codec_probe(struct snd_soc_codec *codec) } } - return ret; + return 0; } static struct snd_soc_codec_driver soc_codec_dev_cs35l35 = { From 5d3d0ad688eacf9567d7d67a5eec3c436cc1064c Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Fri, 17 Mar 2017 15:44:55 +0000 Subject: [PATCH 16/24] ASoC: cs35l35: Stash dev pointer directly rather than CODEC pointer The driver stashes a CODEC pointer in the cs35l35_private structure, which is used to obtain a struct device pointer for error messages in the interrupt handler. However, doing so is not very safe as the interrupt is registered, as it should be in bus probe, but the CODEC pointer can't be safely stored until the ASoC level probe. This leaves a window between the two probes where if any interrupts are received a NULL pointer will be deferenced in the IRQ handler. Fix this issue by saving a pointer to the device directly and passing that to the error messages in the interrupt handler rather than using the CODEC pointer to access the device pointer. Signed-off-by: Charles Keepax Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 29 ++++++++++++++--------------- sound/soc/codecs/cs35l35.h | 2 +- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 05117fca7e3c..1d9f332b2eec 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -741,8 +741,6 @@ static int cs35l35_codec_probe(struct snd_soc_codec *codec) struct monitor_cfg *monitor_config = &cs35l35->pdata.mon_cfg; int ret; - cs35l35->codec = codec; - /* Set Platform Data */ if (cs35l35->pdata.bst_vctl) regmap_update_bits(cs35l35->regmap, CS35L35_BST_CVTR_V_CTL, @@ -1004,7 +1002,6 @@ static struct regmap_config cs35l35_regmap = { static irqreturn_t cs35l35_irq(int irq, void *data) { struct cs35l35_private *cs35l35 = data; - struct snd_soc_codec *codec = cs35l35->codec; unsigned int sticky1, sticky2, sticky3, sticky4; unsigned int mask1, mask2, mask3, mask4, current1; @@ -1032,7 +1029,7 @@ static irqreturn_t cs35l35_irq(int irq, void *data) /* handle the interrupts */ if (sticky1 & CS35L35_CAL_ERR) { - dev_crit(codec->dev, "Calibration Error\n"); + dev_crit(cs35l35->dev, "Calibration Error\n"); /* error is no longer asserted; safe to reset */ if (!(current1 & CS35L35_CAL_ERR)) { @@ -1051,10 +1048,10 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky1 & CS35L35_AMP_SHORT) { - dev_crit(codec->dev, "AMP Short Error\n"); + dev_crit(cs35l35->dev, "AMP Short Error\n"); /* error is no longer asserted; safe to reset */ if (!(current1 & CS35L35_AMP_SHORT)) { - dev_dbg(codec->dev, "Amp short error release\n"); + dev_dbg(cs35l35->dev, "Amp short error release\n"); regmap_update_bits(cs35l35->regmap, CS35L35_PROT_RELEASE_CTL, CS35L35_SHORT_RLS, 0); @@ -1069,11 +1066,11 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky1 & CS35L35_OTW) { - dev_warn(codec->dev, "Over temperature warning\n"); + dev_warn(cs35l35->dev, "Over temperature warning\n"); /* error is no longer asserted; safe to reset */ if (!(current1 & CS35L35_OTW)) { - dev_dbg(codec->dev, "Over temperature warn release\n"); + dev_dbg(cs35l35->dev, "Over temperature warn release\n"); regmap_update_bits(cs35l35->regmap, CS35L35_PROT_RELEASE_CTL, CS35L35_OTW_RLS, 0); @@ -1088,10 +1085,10 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky1 & CS35L35_OTE) { - dev_crit(codec->dev, "Over temperature error\n"); + dev_crit(cs35l35->dev, "Over temperature error\n"); /* error is no longer asserted; safe to reset */ if (!(current1 & CS35L35_OTE)) { - dev_dbg(codec->dev, "Over temperature error release\n"); + dev_dbg(cs35l35->dev, "Over temperature error release\n"); regmap_update_bits(cs35l35->regmap, CS35L35_PROT_RELEASE_CTL, CS35L35_OTE_RLS, 0); @@ -1106,7 +1103,7 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky3 & CS35L35_BST_HIGH) { - dev_crit(codec->dev, "VBST error: powering off!\n"); + dev_crit(cs35l35->dev, "VBST error: powering off!\n"); regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, CS35L35_PDN_AMP, CS35L35_PDN_AMP); regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, @@ -1114,7 +1111,7 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky3 & CS35L35_LBST_SHORT) { - dev_crit(codec->dev, "LBST error: powering off!\n"); + dev_crit(cs35l35->dev, "LBST error: powering off!\n"); regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL2, CS35L35_PDN_AMP, CS35L35_PDN_AMP); regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, @@ -1122,13 +1119,13 @@ static irqreturn_t cs35l35_irq(int irq, void *data) } if (sticky2 & CS35L35_VPBR_ERR) - dev_dbg(codec->dev, "Error: Reactive Brownout\n"); + dev_dbg(cs35l35->dev, "Error: Reactive Brownout\n"); if (sticky4 & CS35L35_VMON_OVFL) - dev_dbg(codec->dev, "Error: VMON overflow\n"); + dev_dbg(cs35l35->dev, "Error: VMON overflow\n"); if (sticky4 & CS35L35_IMON_OVFL) - dev_dbg(codec->dev, "Error: IMON overflow\n"); + dev_dbg(cs35l35->dev, "Error: IMON overflow\n"); return IRQ_HANDLED; } @@ -1365,6 +1362,8 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, if (!cs35l35) return -ENOMEM; + cs35l35->dev = dev; + i2c_set_clientdata(i2c_client, cs35l35); cs35l35->regmap = devm_regmap_init_i2c(i2c_client, &cs35l35_regmap); if (IS_ERR(cs35l35->regmap)) { diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h index c203081fc94c..156d2f0e6fd8 100644 --- a/sound/soc/codecs/cs35l35.h +++ b/sound/soc/codecs/cs35l35.h @@ -265,7 +265,7 @@ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) struct cs35l35_private { - struct snd_soc_codec *codec; + struct device *dev; struct cs35l35_platform_data pdata; struct regmap *regmap; struct regulator_bulk_data supplies[2]; From 8e71321d19c4ed02d9eed15955b8d485bab016fc Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Mon, 27 Mar 2017 16:54:27 +0100 Subject: [PATCH 17/24] ASoC: cs35l35: Clear reset_gpio on the error path in probe The error path in probe attempts to put the device back into reset. Should we fail to get the reset_gpio (such as a probe defer) we will leave the error value in there, which the gpiod_set_value_cansleep on the error path will attempt to deference. Fix this issue by clearing reset_gpio before we head into the error path. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 1d9f332b2eec..9688274f7c90 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -1412,10 +1412,10 @@ static int cs35l35_i2c_probe(struct i2c_client *i2c_client, GPIOD_OUT_LOW); if (IS_ERR(cs35l35->reset_gpio)) { ret = PTR_ERR(cs35l35->reset_gpio); + cs35l35->reset_gpio = NULL; if (ret == -EBUSY) { dev_info(dev, "Reset line busy, assuming shared reset\n"); - cs35l35->reset_gpio = NULL; } else { dev_err(dev, "Failed to get reset GPIO: %d\n", ret); goto err; From 13023ff3b3372543e4197b4b57d378350780fe96 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 4 Apr 2017 14:59:30 -0400 Subject: [PATCH 18/24] ASoC: cs53l30: Set .of_match_table to OF device ID table The driver has an OF device ID table but the struct i2c_driver .of_match_table field is not set. Signed-off-by: Javier Martinez Canillas Signed-off-by: Mark Brown --- sound/soc/codecs/cs53l30.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/codecs/cs53l30.c b/sound/soc/codecs/cs53l30.c index cb47fb595ff4..1e0d5973b758 100644 --- a/sound/soc/codecs/cs53l30.c +++ b/sound/soc/codecs/cs53l30.c @@ -1130,6 +1130,7 @@ MODULE_DEVICE_TABLE(i2c, cs53l30_id); static struct i2c_driver cs53l30_i2c_driver = { .driver = { .name = "cs53l30", + .of_match_table = cs53l30_of_match, .pm = &cs53l30_runtime_pm, }, .id_table = cs53l30_id, From 2c84afb52ebea59e9862fcb8234126b7ae1d1960 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 6 Apr 2017 13:52:12 +0100 Subject: [PATCH 19/24] ASoC: cs35l35: Improve power down time Shorten the time it takes to power down the amp by disabling the volume ramp whilst doing the final shutdown. The driver has already muted the amplifier at this stage so doing the volume ramp serves no purpose. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 8 ++++++++ sound/soc/codecs/cs35l35.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 9688274f7c90..1db07a6296a4 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -187,6 +187,10 @@ static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, regmap_update_bits(cs35l35->regmap, CS35L35_PWRCTL1, CS35L35_PDN_ALL_MASK, 1); + /* Already muted, so disable volume ramp for faster shutdown */ + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, 0); + reinit_completion(&cs35l35->pdn_done); ret = wait_for_completion_timeout(&cs35l35->pdn_done, @@ -199,6 +203,10 @@ static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, CS35L35_MCLK_DIS_MASK, 1 << CS35L35_MCLK_DIS_SHIFT); + + regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, + CS35L35_AMP_DIGSFT_MASK, + 1 << CS35L35_AMP_DIGSFT_SHIFT); break; default: dev_err(codec->dev, "Invalid event = 0x%x\n", event); diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h index 156d2f0e6fd8..54e9ac536b20 100644 --- a/sound/soc/codecs/cs35l35.h +++ b/sound/soc/codecs/cs35l35.h @@ -190,6 +190,9 @@ #define CS35L35_AMP_GAIN_ZC_MASK 0x10 #define CS35L35_AMP_GAIN_ZC_SHIFT 4 +#define CS35L35_AMP_DIGSFT_MASK 0x02 +#define CS35L35_AMP_DIGSFT_SHIFT 1 + /* CS35L35_SP_FMT_CTL3 */ #define CS35L35_SP_I2S_DRV_MASK 0x03 #define CS35L35_SP_I2S_DRV_SHIFT 0 From 77b329d1943d38a5acdbaf9d57754975bce701d4 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 6 Apr 2017 13:52:13 +0100 Subject: [PATCH 20/24] ASoC: cs35l35: Correct handling of PDN_DONE with external boost When using an external boost supply the PDN_DONE bit is not set, update the handling in this case to use to use an appropriate fixed delay. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- include/sound/cs35l35.h | 2 ++ sound/soc/codecs/cs35l35.c | 32 ++++++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h index 983c610eba2e..88744bbd6728 100644 --- a/include/sound/cs35l35.h +++ b/include/sound/cs35l35.h @@ -96,6 +96,8 @@ struct cs35l35_platform_data { int adv_channel; /* Shared Boost for stereo */ bool shared_bst; + /* Specifies this amp is using an external boost supply */ + bool ext_bst; /* ClassH Algorithm */ struct classh_cfg classh_algo; /* Monitor Config */ diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 1db07a6296a4..6ecb7ddae9cf 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -162,6 +162,27 @@ static bool cs35l35_precious_register(struct device *dev, unsigned int reg) } } +static int cs35l35_wait_for_pdn(struct cs35l35_private *cs35l35) +{ + int ret; + + if (cs35l35->pdata.ext_bst) { + usleep_range(5000, 5500); + return 0; + } + + reinit_completion(&cs35l35->pdn_done); + + ret = wait_for_completion_timeout(&cs35l35->pdn_done, + msecs_to_jiffies(100)); + if (ret == 0) { + dev_err(cs35l35->dev, "PDN_DONE did not complete\n"); + return -ETIMEDOUT; + } + + return 0; +} + static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -191,14 +212,7 @@ static int cs35l35_sdin_event(struct snd_soc_dapm_widget *w, regmap_update_bits(cs35l35->regmap, CS35L35_AMP_DIG_VOL_CTL, CS35L35_AMP_DIGSFT_MASK, 0); - reinit_completion(&cs35l35->pdn_done); - - ret = wait_for_completion_timeout(&cs35l35->pdn_done, - msecs_to_jiffies(100)); - if (ret == 0) { - dev_err(codec->dev, "TIMEOUT PDN_DONE did not complete in 100ms\n"); - ret = -ETIMEDOUT; - } + ret = cs35l35_wait_for_pdn(cs35l35); regmap_update_bits(cs35l35->regmap, CS35L35_CLK_CTL1, CS35L35_MCLK_DIS_MASK, @@ -1198,6 +1212,8 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, "cirrus,shared-boost"); } + pdata->ext_bst = of_property_read_bool(np, "cirrus,external-boost"); + pdata->gain_zc = of_property_read_bool(np, "cirrus,amp-gain-zc"); classh = of_get_child_by_name(np, "cirrus,classh-internal-algo"); From 0c67fb5544061cb990e07a73e1b7ba1f8ca22479 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 6 Apr 2017 13:52:14 +0100 Subject: [PATCH 21/24] ASoC: cs35l35: Add DT binding to specify usage of an external boost supply Add a device tree binding to let the driver know that the amplifier is configured to use an external boost supply. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- Documentation/devicetree/bindings/sound/cs35l35.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt index 958f0eabfdfc..457d176dcee0 100644 --- a/Documentation/devicetree/bindings/sound/cs35l35.txt +++ b/Documentation/devicetree/bindings/sound/cs35l35.txt @@ -33,6 +33,10 @@ Optional properties: - cirrus,shared-boost : Boolean to enable ClassH tracking of Advisory Signal if 2 Devices share Boost BST_CTL + - cirrus,external-boost : Boolean to specify the device is using an external + boost supply, note that sharing a boost from another cs35l35 would constitute + using an external supply for the slave device + - cirrus,sp-drv-strength : Value for setting the Serial Port drive strength Table 3-10 of the datasheet lists drive-strength specifications 0 = 1x (Default) From fbeea237af65c6dceca00886aba30839bc986fd7 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Tue, 18 Apr 2017 18:32:52 +0100 Subject: [PATCH 22/24] ASoC: cs35l35: Correct some register defaults Correct some minor errors in the register defaults. Signed-off-by: Charles Keepax Acked-by: Brian Austin Signed-off-by: Mark Brown --- sound/soc/codecs/cs35l35.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index 6ecb7ddae9cf..dc6591adc96d 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -50,7 +50,7 @@ static const struct reg_default cs35l35_reg[] = { {CS35L35_PWRCTL2, 0x11}, {CS35L35_PWRCTL3, 0x00}, {CS35L35_CLK_CTL1, 0x04}, - {CS35L35_CLK_CTL2, 0x10}, + {CS35L35_CLK_CTL2, 0x12}, {CS35L35_CLK_CTL3, 0xCF}, {CS35L35_SP_FMT_CTL1, 0x20}, {CS35L35_SP_FMT_CTL2, 0x00}, @@ -70,7 +70,7 @@ static const struct reg_default cs35l35_reg[] = { {CS35L35_BST_RAMP_CTL, 0x85}, {CS35L35_BST_CONV_COEF_1, 0x24}, {CS35L35_BST_CONV_COEF_2, 0x24}, - {CS35L35_BST_CONV_SLOPE_COMP, 0x47}, + {CS35L35_BST_CONV_SLOPE_COMP, 0x4E}, {CS35L35_BST_CONV_SW_FREQ, 0x04}, {CS35L35_CLASS_H_CTL, 0x0B}, {CS35L35_CLASS_H_HEADRM_CTL, 0x0B}, @@ -78,9 +78,9 @@ static const struct reg_default cs35l35_reg[] = { {CS35L35_CLASS_H_FET_DRIVE_CTL, 0x41}, {CS35L35_CLASS_H_VP_CTL, 0xC5}, {CS35L35_VPBR_CTL, 0x0A}, - {CS35L35_VPBR_VOL_CTL, 0x09}, + {CS35L35_VPBR_VOL_CTL, 0x90}, {CS35L35_VPBR_TIMING_CTL, 0x6A}, - {CS35L35_VPBR_MODE_VOL_CTL, 0x40}, + {CS35L35_VPBR_MODE_VOL_CTL, 0x00}, {CS35L35_SPKR_MON_CTL, 0xC0}, {CS35L35_IMON_SCALE_CTL, 0x30}, {CS35L35_AUDIN_RXLOC_CTL, 0x00}, From 06bdf385f66a53b335b324e28a43788b03e6f3e3 Mon Sep 17 00:00:00 2001 From: Charles Keepax Date: Thu, 13 Apr 2017 16:52:09 +0100 Subject: [PATCH 23/24] ASoC: cs35l35: Allow user to configure IMON SCALE On the chip the IMON signal is a full 24-bits however normally only some of the bits will be sent over the bus. The chip provides a field to select which bits of the IMON will be sent back, this is the only feedback signal that has this feature. Add an additional entry to the cirrus,imon device tree property to allow the IMON scale parameter to be passed. Signed-off-by: Charles Keepax Acked-by: Brian Austin Acked-by: Rob Herring Signed-off-by: Mark Brown --- .../devicetree/bindings/sound/cs35l35.txt | 4 ++-- include/sound/cs35l35.h | 1 + sound/soc/codecs/cs35l35.c | 22 +++++++++++++------ sound/soc/codecs/cs35l35.h | 3 +++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/Documentation/devicetree/bindings/sound/cs35l35.txt b/Documentation/devicetree/bindings/sound/cs35l35.txt index 457d176dcee0..016b768bc722 100644 --- a/Documentation/devicetree/bindings/sound/cs35l35.txt +++ b/Documentation/devicetree/bindings/sound/cs35l35.txt @@ -118,8 +118,8 @@ Optional Monitor Signal Format sub-node: Sections 7.44 - 7.53 lists values for the depth, location, and frame for each monitoring signal. - - cirrus,imon : 3 8 bit values to set the depth, location, and frame - of the IMON monitor signal. + - cirrus,imon : 4 8 bit values to set the depth, location, frame and ADC + scale of the IMON monitor signal. - cirrus,vmon : 3 8 bit values to set the depth, location, and frame of the VMON monitor signal. diff --git a/include/sound/cs35l35.h b/include/sound/cs35l35.h index 88744bbd6728..29da899e17e4 100644 --- a/include/sound/cs35l35.h +++ b/include/sound/cs35l35.h @@ -57,6 +57,7 @@ struct monitor_cfg { u8 imon_dpth; u8 imon_loc; u8 imon_frm; + u8 imon_scale; u8 vmon_dpth; u8 vmon_loc; u8 vmon_frm; diff --git a/sound/soc/codecs/cs35l35.c b/sound/soc/codecs/cs35l35.c index dc6591adc96d..f8aef5869b03 100644 --- a/sound/soc/codecs/cs35l35.c +++ b/sound/soc/codecs/cs35l35.c @@ -918,6 +918,11 @@ static int cs35l35_codec_probe(struct snd_soc_codec *codec) CS35L35_MON_FRM_MASK, monitor_config->imon_frm << CS35L35_MON_FRM_SHIFT); + regmap_update_bits(cs35l35->regmap, + CS35L35_IMON_SCALE_CTL, + CS35L35_IMON_SCALE_MASK, + monitor_config->imon_scale << + CS35L35_IMON_SCALE_SHIFT); } if (monitor_config->vpmon_specs) { regmap_update_bits(cs35l35->regmap, @@ -1161,7 +1166,9 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, struct classh_cfg *classh_config = &pdata->classh_algo; struct monitor_cfg *monitor_config = &pdata->mon_cfg; unsigned int val32 = 0; - u8 monitor_array[3]; + u8 monitor_array[4]; + const int imon_array_size = ARRAY_SIZE(monitor_array); + const int mon_array_size = imon_array_size - 1; int ret = 0; if (!np) @@ -1302,15 +1309,16 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, monitor_config->is_present = signal_format ? true : false; if (monitor_config->is_present) { ret = of_property_read_u8_array(signal_format, "cirrus,imon", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, imon_array_size); if (!ret) { monitor_config->imon_specs = true; monitor_config->imon_dpth = monitor_array[0]; monitor_config->imon_loc = monitor_array[1]; monitor_config->imon_frm = monitor_array[2]; + monitor_config->imon_scale = monitor_array[3]; } ret = of_property_read_u8_array(signal_format, "cirrus,vmon", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, mon_array_size); if (!ret) { monitor_config->vmon_specs = true; monitor_config->vmon_dpth = monitor_array[0]; @@ -1318,7 +1326,7 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, monitor_config->vmon_frm = monitor_array[2]; } ret = of_property_read_u8_array(signal_format, "cirrus,vpmon", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, mon_array_size); if (!ret) { monitor_config->vpmon_specs = true; monitor_config->vpmon_dpth = monitor_array[0]; @@ -1326,7 +1334,7 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, monitor_config->vpmon_frm = monitor_array[2]; } ret = of_property_read_u8_array(signal_format, "cirrus,vbstmon", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, mon_array_size); if (!ret) { monitor_config->vbstmon_specs = true; monitor_config->vbstmon_dpth = monitor_array[0]; @@ -1334,7 +1342,7 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, monitor_config->vbstmon_frm = monitor_array[2]; } ret = of_property_read_u8_array(signal_format, "cirrus,vpbrstat", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, mon_array_size); if (!ret) { monitor_config->vpbrstat_specs = true; monitor_config->vpbrstat_dpth = monitor_array[0]; @@ -1342,7 +1350,7 @@ static int cs35l35_handle_of_data(struct i2c_client *i2c_client, monitor_config->vpbrstat_frm = monitor_array[2]; } ret = of_property_read_u8_array(signal_format, "cirrus,zerofill", - monitor_array, ARRAY_SIZE(monitor_array)); + monitor_array, mon_array_size); if (!ret) { monitor_config->zerofill_specs = true; monitor_config->zerofill_dpth = monitor_array[0]; diff --git a/sound/soc/codecs/cs35l35.h b/sound/soc/codecs/cs35l35.h index 54e9ac536b20..5a6e43a87c4d 100644 --- a/sound/soc/codecs/cs35l35.h +++ b/sound/soc/codecs/cs35l35.h @@ -148,6 +148,9 @@ #define CS35L35_MON_FRM_MASK 0x80 #define CS35L35_MON_FRM_SHIFT 7 +#define CS35L35_IMON_SCALE_MASK 0xF8 +#define CS35L35_IMON_SCALE_SHIFT 3 + #define CS35L35_MS_MASK 0x80 #define CS35L35_MS_SHIFT 7 #define CS35L35_SPMODE_MASK 0x40 From c9afc1834e8132783772d73007706d3ae3848483 Mon Sep 17 00:00:00 2001 From: Jose Abreu Date: Fri, 28 Apr 2017 10:55:25 +0100 Subject: [PATCH 24/24] ASoC: dwc: Disallow building designware_pcm as a module Designware PCM is an extension to Designware I2S and they are dependent on each other. For this reason, make Designware PCM a boolean which will compile with Desigwnare I2S module. The name of the module is not changed but the name of the files need to be changed. Also, without this commit we get errors when probbing designware_i2s module because of unspecified license: designware_pcm: module license 'unspecified' taints kernel. Disabling lock debugging due to kernel taint designware_pcm: Unknown symbol __rcu_read_lock (err 0) designware_pcm: Unknown symbol devm_snd_soc_register_platform (err 0) designware_pcm: Unknown symbol synchronize_rcu (err 0) designware_pcm: Unknown symbol __rcu_read_unlock (err 0) designware_pcm: Unknown symbol snd_soc_set_runtime_hwparams (err 0) So, this is really needed as a fix. Fixes: 79361b2b98b7 ("ASoC: dwc: Add PIO PCM extension") Signed-off-by: Lubomir Rintel Signed-off-by: Jose Abreu Signed-off-by: Mark Brown --- sound/soc/dwc/Kconfig | 4 ++-- sound/soc/dwc/Makefile | 6 +++--- sound/soc/dwc/{designware_i2s.c => dwc-i2s.c} | 0 sound/soc/dwc/{designware_pcm.c => dwc-pcm.c} | 3 --- 4 files changed, 5 insertions(+), 8 deletions(-) rename sound/soc/dwc/{designware_i2s.c => dwc-i2s.c} (100%) rename sound/soc/dwc/{designware_pcm.c => dwc-pcm.c} (98%) diff --git a/sound/soc/dwc/Kconfig b/sound/soc/dwc/Kconfig index c297efe43861..c6fd95fa5ca6 100644 --- a/sound/soc/dwc/Kconfig +++ b/sound/soc/dwc/Kconfig @@ -8,10 +8,10 @@ config SND_DESIGNWARE_I2S maximum of 8 channels each for play and record. config SND_DESIGNWARE_PCM - tristate "PCM PIO extension for I2S driver" + bool "PCM PIO extension for I2S driver" depends on SND_DESIGNWARE_I2S help - Say Y, M or N if you want to add a custom ALSA extension that registers + Say Y or N if you want to add a custom ALSA extension that registers a PCM and uses PIO to transfer data. This functionality is specially suited for I2S devices that don't have diff --git a/sound/soc/dwc/Makefile b/sound/soc/dwc/Makefile index 38f1ca31c5fa..3e24c0ff95fb 100644 --- a/sound/soc/dwc/Makefile +++ b/sound/soc/dwc/Makefile @@ -1,5 +1,5 @@ # SYNOPSYS Platform Support obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_i2s.o -ifdef CONFIG_SND_DESIGNWARE_PCM -obj-$(CONFIG_SND_DESIGNWARE_I2S) += designware_pcm.o -endif + +designware_i2s-y := dwc-i2s.o +designware_i2s-$(CONFIG_SND_DESIGNWARE_PCM) += dwc-pcm.o diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/dwc-i2s.c similarity index 100% rename from sound/soc/dwc/designware_i2s.c rename to sound/soc/dwc/dwc-i2s.c diff --git a/sound/soc/dwc/designware_pcm.c b/sound/soc/dwc/dwc-pcm.c similarity index 98% rename from sound/soc/dwc/designware_pcm.c rename to sound/soc/dwc/dwc-pcm.c index 459ec861e6b6..406fd867117b 100644 --- a/sound/soc/dwc/designware_pcm.c +++ b/sound/soc/dwc/dwc-pcm.c @@ -129,13 +129,11 @@ void dw_pcm_push_tx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, true); } -EXPORT_SYMBOL_GPL(dw_pcm_push_tx); void dw_pcm_pop_rx(struct dw_i2s_dev *dev) { dw_pcm_transfer(dev, false); } -EXPORT_SYMBOL_GPL(dw_pcm_pop_rx); static int dw_pcm_open(struct snd_pcm_substream *substream) { @@ -281,4 +279,3 @@ int dw_pcm_register(struct platform_device *pdev) { return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform); } -EXPORT_SYMBOL_GPL(dw_pcm_register);