@@ -63,6 +63,7 @@ config SND_SOC_ALL_CODECS
imply SND_SOC_BT_SCO
imply SND_SOC_BD28623
imply SND_SOC_CHV3_CODEC
+ imply SND_SOC_CMX655
imply SND_SOC_CQ0093VC
imply SND_SOC_CROS_EC_CODEC
imply SND_SOC_CS35L32
@@ -747,6 +748,32 @@ config SND_SOC_CPCAP
tristate "Motorola CPCAP codec"
depends on MFD_CPCAP || COMPILE_TEST
+config SND_SOC_CMX655D
+ tristate "CMX655D codec"
+ depends on I2C || SPI_MASTER
+ help
+ Enable support for CML CMX655D audio codec with a speaker and
+ two microphones. You also need to enable at least one bus
+ adapter, I2C and/or SPI.
+
+config SND_SOC_CMX655D_I2C
+ tristate "CMX655D codec (I2C)"
+ depends on I2C && SND_SOC_CMX655D
+ default I2C && SND_SOC_CMX655D
+ select REGMAP
+ select REGMAP_I2C
+ help
+ Enable support for CML CMX655D audio codec with I2C control.
+
+config SND_SOC_CMX655D_SPI
+ tristate "CMX655D codec (SPI)"
+ depends on SPI_MASTER && SND_SOC_CMX655D
+ default SPI_MASTER && SND_SOC_CMX655D
+ select REGMAP
+ select REGMAP_SPI
+ help
+ Enable support for CML CMX655D audio codec with SPI control.
+
config SND_SOC_CQ0093VC
tristate
@@ -58,6 +58,7 @@ snd-soc-aw88399-y := aw88399.o
snd-soc-bd28623-y := bd28623.o
snd-soc-bt-sco-y := bt-sco.o
snd-soc-chv3-codec-y := chv3-codec.o
+snd-soc-cmx655-y := cmx655.o
snd-soc-cpcap-y := cpcap.o
snd-soc-cq93vc-y := cq93vc.o
snd-soc-cros-ec-codec-y := cros_ec_codec.o
@@ -475,6 +476,9 @@ obj-$(CONFIG_SND_SOC_AW88399) += snd-soc-aw88399.o
obj-$(CONFIG_SND_SOC_BD28623) += snd-soc-bd28623.o
obj-$(CONFIG_SND_SOC_BT_SCO) += snd-soc-bt-sco.o
obj-$(CONFIG_SND_SOC_CHV3_CODEC) += snd-soc-chv3-codec.o
+obj-$(CONFIG_SND_SOC_CMX655D) += snd-soc-cmx655.o
+obj-$(CONFIG_SND_SOC_CMX655D_I2C) += cmx655_i2c.o
+obj-$(CONFIG_SND_SOC_CMX655D_SPI) += cmx655_spi.o
obj-$(CONFIG_SND_SOC_CQ0093VC) += snd-soc-cq93vc.o
obj-$(CONFIG_SND_SOC_CPCAP) += snd-soc-cpcap.o
obj-$(CONFIG_SND_SOC_CROS_EC_CODEC) += snd-soc-cros-ec-codec.o
new file mode 100644
@@ -0,0 +1,1179 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/version.h>
+#include <linux/regmap.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/of.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/tlv.h>
+
+#include "cmx655.h"
+
+/*
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Create Regmap
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+static const struct reg_default cmx655_reg_defaults[] = {
+ { CMX655_ISR, 0x00 },
+ { CMX655_ISM, 0x00 },
+ { CMX655_ISE, 0x00 },
+ { CMX655_CLKCTRL, 0x00 },
+ { CMX655_RDIVHI, 0x00 },
+ { CMX655_RDIVLO, 0x00 },
+ { CMX655_NDIVHI, 0x00 },
+ { CMX655_NDIVLO, 0x00 },
+ { CMX655_PLLCTRL, 0x00 },
+ { CMX655_SAICTRL, 0x00 },
+ { CMX655_SAIMUX, 0x00 },
+
+ { CMX655_RVF, 0x00 },
+ { CMX655_LDCTRL, 0x00 },
+ { CMX655_RDCTRL, 0x00 },
+ { CMX655_LEVEL, 0x00 },
+
+ { CMX655_NGCTRL, 0x00 },
+ { CMX655_NGTIME, 0x00 },
+ { CMX655_NGLSTAT, 0x00 },
+ { CMX655_NGRSTAT, 0x00 },
+
+ { CMX655_PVF, 0x00 },
+ { CMX655_PREAMP, 0x00 },
+ { CMX655_VOLUME, 0x00 },
+ { CMX655_ALCCTRL, 0x00 },
+ { CMX655_ALCTIME, 0x00 },
+ { CMX655_ALCGAIN, 0x00 },
+ { CMX655_ALCSTAT, 0x00 },
+ { CMX655_DST, 0x00 },
+ { CMX655_CPR, 0x00 },
+
+ { CMX655_SYSCTRL, 0x00 },
+ { CMX655_COMMAND, 0x00 },
+ { 0x34, 0x29 },
+ { 0x35, 0x40 },
+ { 0x36, 0x80 },
+ { 0x37, 0x80 },
+
+};
+
+/*
+ * Define all registers as regmap ranges
+ */
+static const struct regmap_range cmx655_valid_reg[] = {
+ { 0x00, 0x0a },
+ { 0x0c, 0x0f },
+ { 0x1c, 0x1f },
+ { 0x28, 0x30 },
+ { 0x32, 0x33 }
+};
+
+/*
+ * Define read only as regmap ranges
+ */
+static const struct regmap_range cmx655_read_only_reg[] = {
+ { 0x00, 0x00 },
+ { 0x1e, 0x1f },
+ { 0x2e, 0x2e }
+};
+
+/*
+ * Define write only as regmap ranges
+ */
+static const struct regmap_range cmx655_write_only_reg[] = {
+ { 0x33, 0x33 }
+};
+
+/*
+ * Define access table for readable registers
+ * Valid register and not write only
+ */
+static const struct regmap_access_table cmx655_readable_reg = {
+ .yes_ranges = cmx655_valid_reg,
+ .n_yes_ranges = ARRAY_SIZE(cmx655_valid_reg),
+ .no_ranges = cmx655_write_only_reg,
+ .n_no_ranges = ARRAY_SIZE(cmx655_write_only_reg)
+};
+
+/*
+ * Define access table for writeable registers
+ * Valid register and not read only
+ */
+static const struct regmap_access_table cmx655_writeable_reg = {
+ .yes_ranges = cmx655_valid_reg,
+ .n_yes_ranges = ARRAY_SIZE(cmx655_valid_reg),
+ .no_ranges = cmx655_read_only_reg,
+ .n_no_ranges = ARRAY_SIZE(cmx655_read_only_reg)
+};
+
+/*
+ * Define volatile regs with function
+ */
+static bool cmx655_volatile_reg(struct device *dev, unsigned int reg)
+{
+ bool ret;
+
+ switch (reg) {
+ case CMX655_COMMAND:
+ case CMX655_SYSCTRL:
+ case CMX655_ISR:
+ case CMX655_ISE:
+ ret = true;
+ break;
+ default:
+ ret = false;
+ break;
+ }
+ return ret;
+};
+
+const struct regmap_config cmx655_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = CMX655_COMMAND,
+
+ .volatile_reg = cmx655_volatile_reg,
+ .wr_table = &cmx655_writeable_reg,
+ .rd_table = &cmx655_readable_reg,
+
+ .reg_defaults = cmx655_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(cmx655_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+};
+
+EXPORT_SYMBOL(cmx655_regmap);
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End of regmap define
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Define some functions used by this module to control the CMX655
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/*
+ * Start CMX655 internal clock and wait for clock ready bit
+ */
+static int cmx655_start_sys_clk(struct snd_soc_component *component)
+{
+ int ret;
+ int i;
+ unsigned int val;
+ // Dummy read to clear status bits
+ val = snd_soc_component_read(component, CMX655_ISR);
+ // Start clock
+ ret = snd_soc_component_write(component, CMX655_COMMAND,
+ CMX655_CMD_CLOCK_START);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "Failed to write start clock command %d\n", ret);
+ return ret;
+ }
+ // Wait for status bit
+ for (i = 0; i < 100; i++) {
+ val = snd_soc_component_read(component, CMX655_ISR);
+ if (val & CMX655_ISR_CLKRDY)
+ break;
+ }
+ if (i == 100) {
+ // Clock did not start
+ ret = -EIO;
+ }
+ return ret;
+}
+
+static int cmx655_stop_sys_clk(struct snd_soc_component *component)
+{
+ return snd_soc_component_write(component, CMX655_COMMAND,
+ CMX655_CMD_CLOCK_STOP);
+}
+
+/*
+ * Get the clock setup the system clock based on clock Id, DAI master mode
+ * and sample rate
+ * clk_id - Clock source setting as defined in cmx655.h
+ * master_mode - Non-zero if the CMX655 is the DAI master
+ * sr_setting - Setting for sample rate 0 to 3
+ * clk_src - pointer for storing clock source (PLLREF, PLLSEL and
+ * CLKSEL bits)
+ * rdiv - pointer for storing PLL's RDIV value (13 bits)
+ * ndiv - pointer for storing PLL's NDIV value (13 bits)
+ * pll_ctrl - pointer for storing PLLCTRL register value (8 bits)
+ */
+static int cmx655_get_sys_clk_config(int clk_id,
+ int master_mode,
+ int sr_setting,
+ int *clk_src,
+ int *rdiv, int *ndiv, int *pll_ctrl)
+{
+ // Do auto selection
+ if (clk_id == CMX655_SYSCLK_AUTO) {
+ if (master_mode != 0)
+ clk_id = CMX655_SYSCLK_RCLK;
+ else
+ clk_id = CMX655_SYSCLK_LRCLK;
+ }
+ // Set default values
+ *rdiv = 0;
+ *ndiv = 0;
+ *pll_ctrl = 0;
+ switch (clk_id) {
+ case (CMX655_SYSCLK_RCLK):
+ *clk_src = CMX655_CLKCTRL_CLRSRC_RCLK;
+ break;
+ case (CMX655_SYSCLK_LPO):
+ *clk_src = CMX655_CLKCTRL_CLRSRC_LPO;
+ break;
+ case (CMX655_SYSCLK_LRCLK):
+ *clk_src = CMX655_CLKCTRL_CLRSRC_LRCLK;
+ *rdiv = 1;
+ switch (sr_setting) {
+ case (CMX655_CLKCTRL_SR_8K):
+ *ndiv = 3072;
+ *pll_ctrl = (0 << CMX655_PLLCTRL_LFILT_SHIFT) ||
+ (3 << CMX655_PLLCTRL_CPI_SHIFT);
+ break;
+ case (CMX655_CLKCTRL_SR_16K):
+ *ndiv = 1536;
+ *pll_ctrl = (0 << CMX655_PLLCTRL_LFILT_SHIFT) ||
+ (3 << CMX655_PLLCTRL_CPI_SHIFT);
+ break;
+ case (CMX655_CLKCTRL_SR_32K):
+ *ndiv = 768;
+ *pll_ctrl = (12 << CMX655_PLLCTRL_LFILT_SHIFT) ||
+ (3 << CMX655_PLLCTRL_CPI_SHIFT);
+ break;
+ case (CMX655_CLKCTRL_SR_48K):
+ *ndiv = 512;
+ *pll_ctrl = (12 << CMX655_PLLCTRL_LFILT_SHIFT) ||
+ (3 << CMX655_PLLCTRL_CPI_SHIFT);
+ break;
+ default:
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+};
+
+/*
+ * Setup the clock and sample rate. The clock needs to be setup at the same
+ * time as the sample rate encase we are using the serial port as the clock
+ * source.
+ * If the clock source the serial port then the PLL settings are dependent on
+ * the sample rate.
+ */
+static int cmx655_setup_rate(struct snd_soc_component *component,
+ struct snd_pcm_hw_params *hw_params)
+{
+ int ret;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ struct cmx655_dai_data *cmx655_dai_data = &cmx655_data->dai_data;
+ int srate = params_rate(hw_params);
+ int master_mode;
+ int srate_setting;
+ int clk_src;
+ int rdiv;
+ int ndiv;
+ int pll_ctrl;
+ int sys_ctrl;
+ int vol;
+
+ master_mode = snd_soc_component_read(component, CMX655_SAICTRL);
+ master_mode = master_mode & CMX655_SAI_MSTR;
+
+ // Workout clock settings
+ // Start with sample rate
+ switch (srate) {
+ case 8000:
+ srate_setting = CMX655_CLKCTRL_SR_8K;
+ break;
+ case 16000:
+ srate_setting = CMX655_CLKCTRL_SR_16K;
+ break;
+ case 32000:
+ srate_setting = CMX655_CLKCTRL_SR_32K;
+ break;
+ case 48000:
+ srate_setting = CMX655_CLKCTRL_SR_48K;
+ break;
+ default:
+ dev_err(component->dev, "Unsupported rate %d\n", srate);
+ return -EINVAL;
+ }
+
+ ret = cmx655_get_sys_clk_config(cmx655_dai_data->sys_clk,
+ master_mode, srate_setting,
+ &clk_src, &rdiv, &ndiv, &pll_ctrl);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "Failed to get system clock settings %i\n", ret);
+ }
+ // Check if we are using the LRCLK as the source.
+ if (clk_src == CMX655_CLKCTRL_CLRSRC_LRCLK) {
+ dev_dbg(component->dev,
+ "Using LRCLK as clk source. Using LPO for setup then switch over to LRCLK later");
+ // Store correct clock source for later use
+ cmx655_dai_data->clk_src = clk_src;
+ cmx655_dai_data->best_clk_running = false; // Need more setup later
+ clk_src = CMX655_CLKCTRL_CLRSRC_LPO;
+ } else {
+ cmx655_dai_data->best_clk_running = true;
+ }
+ // Test to see if the clock source and sample rate are correct.
+ // If so we can skip the setup
+ if (snd_soc_component_test_bits(component, CMX655_CLKCTRL,
+ CMX655_CLKCTRL_CLRSRC_MASK |
+ CMX655_CLKCTRL_SR_MASK,
+ clk_src | srate_setting) == 0) {
+ dev_dbg(component->dev, "Rate Setup correct skipping setup\n");
+ return 0;
+ }
+ // Turn all inputs and outputs off before disabling clock
+ sys_ctrl = snd_soc_component_read(component, CMX655_SYSCTRL);
+ snd_soc_component_update_bits(component, CMX655_SYSCTRL,
+ CMX655_SYSCTRL_MICR |
+ CMX655_SYSCTRL_MICL |
+ CMX655_SYSCTRL_PAMP |
+ CMX655_SYSCTRL_LOUT, 0);
+
+ cmx655_stop_sys_clk(component);
+ // Set new sample rate and clock source
+ snd_soc_component_update_bits(component, CMX655_CLKCTRL,
+ CMX655_CLKCTRL_CLRSRC_MASK |
+ CMX655_CLKCTRL_SR_MASK,
+ clk_src | srate_setting);
+ // Set new RDIV
+ snd_soc_component_update_bits(component, CMX655_RDIVHI,
+ 0x1F, rdiv >> 8);
+ snd_soc_component_update_bits(component, CMX655_RDIVLO,
+ 0xFF, rdiv & 0xFF);
+ // Set new NDIV
+ snd_soc_component_update_bits(component, CMX655_NDIVHI,
+ 0x1F, ndiv >> 8);
+ snd_soc_component_update_bits(component, CMX655_NDIVLO,
+ 0xFF, ndiv & 0xFF);
+ // Set new PLLCTRL
+ snd_soc_component_update_bits(component, CMX655_PLLCTRL,
+ 0xFF, pll_ctrl & 0xFF);
+ // Now we can re-start the clock
+ ret = cmx655_start_sys_clk(component);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "System clock failed to start %i\n", ret);
+ return ret;
+ }
+ // Turn anything on that we turned off
+ if ((sys_ctrl & (CMX655_SYSCTRL_MICR | CMX655_SYSCTRL_MICL)) > 0) { // Turn on mic(s)
+ snd_soc_component_update_bits(component, CMX655_SYSCTRL,
+ CMX655_SYSCTRL_MICR |
+ CMX655_SYSCTRL_MICL, sys_ctrl);
+ // Wait for filters to settle
+ if (snd_soc_component_test_bits
+ (component, CMX655_RVF, CMX655_VF_DCBLOCK,
+ CMX655_VF_DCBLOCK) == 0) {
+ // DC blocking filter off, Shorter wait
+ usleep_range(3500, 4000);
+ } else {
+ // This allows time for Mics and DC blocking filter to settle
+ msleep(320);
+ }
+ }
+ if ((sys_ctrl & (CMX655_SYSCTRL_PAMP | CMX655_SYSCTRL_LOUT)) > 0) { // Turn output(s) on
+ // Store volume
+ vol = snd_soc_component_read(component, CMX655_VOLUME);
+ // Lower volume with smooth on
+ snd_soc_component_write(component, CMX655_VOLUME, 0x80);
+ snd_soc_component_update_bits(component, CMX655_SYSCTRL,
+ CMX655_SYSCTRL_PAMP |
+ CMX655_SYSCTRL_LOUT, sys_ctrl);
+ // Restore volume
+ snd_soc_component_write(component, CMX655_VOLUME, vol);
+ }
+
+ return 0;
+};
+
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End of internal functions
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Define DAI (Digital Audio Interface) component
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/*
+ * Callback to set serial port format
+ */
+static int cmx655_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct snd_soc_component *component = dai->component;
+ unsigned int reg_val = 0;
+ // Set master bit
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBM_CFM:
+ reg_val = reg_val | CMX655_SAI_MSTR;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFS:
+ // Could or in 0 but no need
+ break;
+ default:
+ dev_err(component->dev,
+ "Unsupported digital audio interface master mode\n");
+ return -EINVAL;
+ }
+ // Set data format
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_I2S:
+ reg_val = reg_val | CMX655_SAI_DLY | CMX655_SAI_POL;
+ break;
+ case SND_SOC_DAIFMT_LEFT_J:
+ // Could or in 0 but no need
+ break;
+ default:
+ dev_err(component->dev,
+ "Unsupported digital audio interface data format\n");
+ return -EINVAL;
+ }
+ // Change invert bits if required
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ // No inverts do nothing
+ break;
+ case SND_SOC_DAIFMT_NB_IF:
+ reg_val = reg_val ^ CMX655_SAI_POL;
+ break;
+ case SND_SOC_DAIFMT_IB_NF:
+ reg_val = reg_val | CMX655_SAI_BINV;
+ break;
+ case SND_SOC_DAIFMT_IB_IF:
+ reg_val = (reg_val | CMX655_SAI_BINV) ^ CMX655_SAI_POL;
+ break;
+ default:
+ dev_err(component->dev,
+ "Unknown digital audio interface polarity\n");
+ return -EINVAL;
+ }
+
+ // Write value to codec
+ snd_soc_component_write(component, CMX655_SAICTRL, reg_val);
+ return 0;
+}
+
+/*
+ * Save and check requested clk_id is valid.
+ * Clock is setup as part of hw params
+ */
+static int cmx655_set_dai_sysclk(struct snd_soc_dai *dai,
+ int clk_id, unsigned int freq, int dir)
+{
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(dai->component);
+ struct cmx655_dai_data *cmx655_dai_data = &cmx655_data->dai_data;
+
+ switch (clk_id) {
+ case CMX655_SYSCLK_MIN ... CMX655_SYSCLK_MAX:
+ break;
+ default:
+ return -EINVAL;
+ }
+ cmx655_dai_data->sys_clk = clk_id;
+
+ return 0;
+};
+
+/*
+ * Callback to prepare, if running from the LRCLK we will need to
+ * swap to it here.
+ * Cannot do it in hw_params as the CPU's port was not setup
+ */
+static int cmx655_dai_prepare(struct snd_pcm_substream *stream,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct snd_soc_component *component = dai->component;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ struct cmx655_dai_data *cmx655_dai_data = &cmx655_data->dai_data;
+
+ if (!cmx655_dai_data->best_clk_running) {
+ // Stop the clock change over to the correct one an start it again
+ ret = cmx655_stop_sys_clk(component);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to stop clock %d\n",
+ ret);
+ goto get_out;
+ }
+ ret =
+ snd_soc_component_update_bits(component, CMX655_CLKCTRL,
+ CMX655_CLKCTRL_CLRSRC_MASK,
+ cmx655_dai_data->clk_src);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "Failed to set new clock setup %d\n", ret);
+ goto get_out;
+ }
+ ret = cmx655_start_sys_clk(component);
+ if (ret < 0) {
+ dev_warn(component->dev, "Failed to restart clock\n");
+ ret = 0;
+ // This will happen if the CPU driver does not start the LRCLK
+ // until the last point.
+ // For now we will assume the clock will start
+ }
+ cmx655_dai_data->best_clk_running = true;
+ }
+get_out:
+ return ret;
+};
+
+/*
+ * Callback to setup codec params
+ */
+static int cmx655_hw_params(struct snd_pcm_substream *stream,
+ struct snd_pcm_hw_params *hw_params,
+ struct snd_soc_dai *dai)
+{
+ int ret;
+ struct snd_soc_component *component = dai->component;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ struct cmx655_dai_data *cmx655_dai_data = &cmx655_data->dai_data;
+ unsigned int enabled_streams = cmx655_dai_data->enabled_streams;
+
+ if (cmx655_dai_data->best_clk_running) {
+ // Will get here if the clock is in use so don't go stopping it
+ dev_dbg(component->dev, "Clock running. Skipping setup\n");
+ } else {
+ // Setup clock and sample rate
+ ret = cmx655_setup_rate(component, hw_params);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to set rates %d\n",
+ ret);
+ return ret;
+ }
+ }
+ // Set mono bit based on channel count
+ if (params_channels(hw_params) == 1) {
+ dev_dbg(component->dev, "Switching into mono mode\n");
+ snd_soc_component_update_bits(component, CMX655_SAICTRL,
+ CMX655_SAI_MONO, CMX655_SAI_MONO);
+ } else {
+ snd_soc_component_update_bits(component, CMX655_SAICTRL,
+ CMX655_SAI_MONO, 0);
+ }
+
+ if (cmx655_data->irq)
+ cmx655_data->oc_cnt = 0; // Reset overcurrent count
+ if (enabled_streams == 0) {
+ dev_dbg(component->dev,
+ "First stream to enable, enabling SAI\n");
+ // If first stream to be enabled
+ // Enable SAI (serial audio interface) port
+ // We need it running before the platform starts.
+ // to avoid I2S sync errors
+ snd_soc_component_update_bits(component, CMX655_SYSCTRL,
+ CMX655_SYSCTRL_SAI,
+ CMX655_SYSCTRL_SAI);
+ } else {
+ dev_dbg(component->dev,
+ "Not first stream to enable, skipping SAI enable\n");
+ }
+
+ // Inc enabled streams by 1
+ cmx655_dai_data->enabled_streams = enabled_streams + 1;
+
+ return ret;
+}
+
+/*
+ * Shutdown DAI link
+ */
+static void cmx655_dai_shutdown(struct snd_pcm_substream *stream,
+ struct snd_soc_dai *dai)
+{
+ struct snd_soc_component *component = dai->component;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ struct cmx655_dai_data *cmx655_dai_data = &cmx655_data->dai_data;
+ unsigned int enabled_streams = cmx655_dai_data->enabled_streams;
+
+ if (enabled_streams == 0) {
+ // Protect against shutdown getting called without a start.
+ // This was seen with audacity
+ dev_dbg(component->dev,
+ "Shutdown called when SAI not running\n");
+ return;
+ }
+ // Reduce enabled streams by 1
+ enabled_streams = enabled_streams - 1;
+ cmx655_dai_data->enabled_streams = enabled_streams;
+ if (enabled_streams == 0) {
+ dev_dbg(component->dev,
+ "Last stream to disable, disabling SAI\n");
+ // If no streams left
+ // Disable SAI port
+ snd_soc_component_update_bits(component, CMX655_SYSCTRL,
+ CMX655_SYSCTRL_SAI, 0);
+ // Setup the clock again next time around
+ cmx655_dai_data->best_clk_running = false;
+ } else {
+ dev_dbg(component->dev,
+ "Not last stream to disable, skipping SAI disable\n");
+ }
+}
+
+/*
+ * Define CMX655's DAI operations
+ */
+static const struct snd_soc_dai_ops cmx655_dai_ops = {
+ .set_sysclk = cmx655_set_dai_sysclk,
+ .set_fmt = cmx655_set_dai_fmt,
+
+ .prepare = cmx655_dai_prepare,
+ .hw_params = cmx655_hw_params,
+ .shutdown = cmx655_dai_shutdown,
+};
+
+/*
+ * Define CMX655's DAI driver
+ */
+static struct snd_soc_dai_driver cmx655_dai_driver = {
+ .name = "cmx655",
+ .playback = {
+ .stream_name = "CMX655 Playback",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CMX655_RATES,
+ .formats = CMX655_FMTS,
+ },
+ .capture = {
+ .stream_name = "CMX655 Record",
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = CMX655_RATES,
+ .formats = CMX655_FMTS,
+ },
+ .ops = &cmx655_dai_ops,
+ .symmetric_rate = 1
+};
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End of DAI component
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Define component/codec driver
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+/*
+ * CMX655 IRQ handler thread (runs with interrupts enable)
+ * Read status register and take required action
+ */
+static irqreturn_t cmx655_irq_thread(int irq, void *data)
+{
+ struct snd_soc_component *component = data;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ unsigned int status;
+
+ status = snd_soc_component_read(component, CMX655_ISR);
+ if (status == 0) { // Event was not trigged by CMX655 so let the higher level know
+ return IRQ_NONE;
+ }
+ // Thermal protection event ++++++++++++++++++++++++++++++++++++++++++++++++
+ if (status & CMX655_ISR_THERM) {
+ // Do not reset CMX655 codec on over temperature event
+ dev_err(component->dev,
+ "CMX655 class-D over temperature detected\n");
+ }
+ // Over current event +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ if (status & CMX655_ISR_AMPOC) {
+ dev_warn(component->dev,
+ "CMX655 class-D over current detected\n");
+ if (cmx655_data->oc_cnt < cmx655_data->oc_cnt_max) {
+ // Re enable class-D
+ snd_soc_component_update_bits(component,
+ CMX655_SYSCTRL,
+ CMX655_SYSCTRL_PAMP,
+ CMX655_SYSCTRL_PAMP);
+ if (cmx655_data->oc_cnt_max <= 10000) {
+ // If overcurrent retries not set to > 10000 keep track of number
+ // of restarts
+ cmx655_data->oc_cnt = cmx655_data->oc_cnt + 1;
+ }
+ } else {
+ // Re enable count reached, do not try again
+ dev_err(component->dev,
+ "Class-D over current restart attempts exceeded\n");
+ }
+ }
+ // End of status bit handling ++++++++++++++++++++++++++++++++++++++++++++++
+ return IRQ_HANDLED;
+}
+
+/*
+ * Method to initailise CMX655 component
+ */
+static int cmx655_component_probe(struct snd_soc_component *component)
+{
+ int ret;
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+
+ // We need the clock running to write to most of the registers
+ // so lets start that now
+ ret = cmx655_start_sys_clk(component);
+ if (ret < 0) {
+ dev_err(component->dev, "Failed to start system clock %d\n",
+ ret);
+ goto codec_err;
+ }
+ cmx655_data->oc_cnt = 0; // Set overcurrent count
+ // Enable interrupt if supplied
+ if (cmx655_data->irq) {
+ ret =
+ request_threaded_irq(cmx655_data->irq, NULL,
+ cmx655_irq_thread, IRQF_ONESHOT,
+ "cmx655", component);
+ if (ret < 0) {
+ dev_err(component->dev,
+ "Failed to setup interrupt %d\n", ret);
+ goto interrupt_err;
+ }
+ // Setup interrupts on cmx655
+ snd_soc_component_write(component, CMX655_ISM,
+ (CMX655_ISM_AMPOC | CMX655_ISM_THERM));
+ }
+
+ return 0;
+
+interrupt_err:
+codec_err:
+ return ret;
+}
+
+/*
+ * Method to tidy up when codec driver removed
+ */
+static void cmx655_component_remove(struct snd_soc_component *component)
+{
+ struct cmx655_data *cmx655_data =
+ snd_soc_component_get_drvdata(component);
+ // disable interrupts if used
+ if (cmx655_data->irq) {
+ // Disable interrupts on cmx655
+ snd_soc_component_write(component, CMX655_ISM, 0);
+ // Free interrupt handler
+ free_irq(cmx655_data->irq, component);
+ }
+}
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Define ALSA SoC controls
+ */
+/*
+ * Define required TLV (type-length-value) ranges
+ */
+static const DECLARE_TLV_DB_SCALE(cmx655_level, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(cmx655_ng_thresh, -6300, 100, 0);
+static const DECLARE_TLV_DB_SCALE(cmx655_vol, -9100, 100, 1);
+static const DECLARE_TLV_DB_SCALE(cmx655_pre_amp, 0, 600, 0);
+static const DECLARE_TLV_DB_SCALE(cmx655_dst_gain, -6200, 200, 0);
+static const DECLARE_TLV_DB_SCALE(cmx655_alc_gain, 0, 100, 0);
+static const DECLARE_TLV_DB_SCALE(cmx655_alc_thresh, -3100, 100, 0);
+
+/*
+ * Define Enums
+ */
+static const char *const cmx655_ngratio_text[] = {
+ "1:2",
+ "1:3",
+ "1:4"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_ngratio_enum, CMX655_NGCTRL, 5,
+ cmx655_ngratio_text);
+static const char *const cmx655_ngattack_text[] = {
+ "1.5ms",
+ "3ms",
+ "4.5ms",
+ "6ms",
+ "12ms",
+ "24ms",
+ "48ms",
+ "96ms"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_ngattack_enum, CMX655_NGTIME, 4,
+ cmx655_ngattack_text);
+static const char *const cmx655_ngrelease_text[] = {
+ "0.06s",
+ "0.12s",
+ "0.24s",
+ "0.48s",
+ "0.96s",
+ "1.92s",
+ "3.84s",
+ "7.68s"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_ngrelease_enum, CMX655_NGTIME, 0,
+ cmx655_ngrelease_text);
+static const char *const cmx655_hpf_text[] = {
+ "Disabled",
+ "SRate/320",
+ "SRate/53.3",
+ "SRate/26.7"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_hpf_capture_enum, CMX655_RVF, 0,
+ cmx655_hpf_text);
+static SOC_ENUM_SINGLE_DECL(cmx655_hpf_playback_enum, CMX655_PVF, 0,
+ cmx655_hpf_text);
+static const char *const cmx655_companding_text[] = {
+ "u-Law",
+ "a-Law"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_companding_enum, CMX655_SAIMUX, 5,
+ cmx655_companding_text);
+static const char *const cmx655_alc_ratio_text[] = {
+ "1.5:1",
+ "2:1",
+ "4:1",
+ "Inf:1"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_alc_ratio_enum, CMX655_ALCCTRL, 5,
+ cmx655_alc_ratio_text);
+// Note: The attack and release times are the same as the Noise gate's.
+static SOC_ENUM_SINGLE_DECL(cmx655_alc_attack_enum, CMX655_ALCTIME, 4,
+ cmx655_ngattack_text);
+static SOC_ENUM_SINGLE_DECL(cmx655_alc_release_enum, CMX655_ALCTIME, 0,
+ cmx655_ngrelease_text);
+
+/*
+ * Define controls for codec/component
+ */
+static const struct snd_kcontrol_new cmx655_snd_controls[] = {
+ // Capture ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SOC_DOUBLE_TLV("Master Capture Volume", CMX655_LEVEL, 4, 0, 15, 0,
+ cmx655_level),
+
+ SOC_SINGLE("DC_Block Capture Switch", CMX655_RVF,
+ CMX655_VF_DCBLOCK_SHIFT,
+ 1, 0),
+
+ SOC_SINGLE("LPF Capture Switch", CMX655_RVF, 3, 1, 0),
+ SOC_ENUM("Cap_HPF Capture Switch", cmx655_hpf_capture_enum),
+ // Noise gate
+ SOC_SINGLE("Noise_Gate Capture Switch", CMX655_NGCTRL, 7, 1, 0),
+ SOC_SINGLE_TLV("NG_Threshold Capture Volume", CMX655_NGCTRL, 0, 31, 0,
+ cmx655_ng_thresh),
+ SOC_ENUM("NG_Ratio Capture Switch", cmx655_ngratio_enum),
+ SOC_ENUM("NG_Attack Capture Switch", cmx655_ngattack_enum),
+ SOC_ENUM("NG_Release Capture Switch", cmx655_ngrelease_enum),
+
+ // Playback +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SOC_SINGLE_TLV("Master Playback Volume", CMX655_VOLUME, 0, 91, 0,
+ cmx655_vol),
+ SOC_SINGLE_TLV("Pre_Amp Playback Volume", CMX655_PREAMP, 0, 3, 0,
+ cmx655_pre_amp),
+ SOC_SINGLE("Smooth Playback Switch", CMX655_VOLUME, 7, 0x01, 0),
+ SOC_SINGLE("DC_Block Playback Switch", CMX655_PVF,
+ CMX655_VF_DCBLOCK_SHIFT,
+ 1, 0),
+ SOC_SINGLE("LPF Playback Switch", CMX655_PVF, 3, 1, 0),
+ SOC_ENUM("Play_HPF Playback Switch", cmx655_hpf_playback_enum),
+ SOC_SINGLE("Soft_Mute Playback Switch", CMX655_CPR, 0, 1, 0),
+ SOC_SINGLE("ALC Playback Switch", CMX655_ALCCTRL, 7, 1, 0),
+ SOC_ENUM("ALC_Ratio Playback Switch", cmx655_alc_ratio_enum),
+ SOC_SINGLE_TLV("ALC_Threshold Playback Volume", CMX655_ALCCTRL,
+ 0, 31, 0,
+ cmx655_alc_thresh),
+ SOC_SINGLE_TLV("ALC_Gain Playback Volume", CMX655_ALCGAIN, 0, 12, 0,
+ cmx655_alc_gain),
+ SOC_ENUM("ALC_Attack Playback Switch", cmx655_alc_attack_enum),
+ SOC_ENUM("ALC_Release Playback Switch", cmx655_alc_release_enum),
+
+ // Digital Sidetone +++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SOC_SINGLE_TLV("Sidetone Playback Volume", CMX655_DST, 0, 31, 0,
+ cmx655_dst_gain),
+ // Companding +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SOC_SINGLE("Companding_En Switch", CMX655_SAIMUX, 4, 1, 0),
+ SOC_ENUM("Companding_Type Switch", cmx655_companding_enum),
+};
+
+/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Define dynamic audio power management (DAPM) widget, controls and routes
+ */
+/*
+ * Define Enums
+ */
+static const char *const cmx655_mic_mux_text[] = {
+ "Normal",
+ "Swapped",
+ "Left only",
+ "Right only"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_mic_mux_enum, CMX655_SAIMUX, 0,
+ cmx655_mic_mux_text);
+
+static const char *const cmx655_amp_mux_text[] = {
+ "Left",
+ "Right",
+ "Mean"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_amp_mux_enum, CMX655_SAIMUX, 2,
+ cmx655_amp_mux_text);
+
+static const char *const cmx655_digital_sidetone_text[] = {
+ "Left",
+ "Right",
+ "Mean"
+};
+
+static SOC_ENUM_SINGLE_DECL(cmx655_sidetone_enum, CMX655_DST, 5,
+ cmx655_digital_sidetone_text);
+/*
+ * Define controls for DAPM
+ */
+static const struct snd_kcontrol_new cmx655_mic_mux =
+SOC_DAPM_ENUM("Cap_SAI Capture Route",
+ cmx655_mic_mux_enum);
+
+static const struct snd_kcontrol_new cmx655_amp_mux =
+SOC_DAPM_ENUM("Play_SAI Playback Route",
+ cmx655_amp_mux_enum);
+
+static const struct snd_kcontrol_new cmx655_spkr_en[] = {
+ SOC_DAPM_SINGLE_VIRT("Playback Switch", 1)
+};
+
+static const struct snd_kcontrol_new cmx655_lout_en[] = {
+ SOC_DAPM_SINGLE_VIRT("Playback Switch", 1)
+};
+
+static const struct snd_kcontrol_new cmx655_sidetone_mux =
+SOC_DAPM_ENUM("DST Route", cmx655_sidetone_enum);
+
+static const struct snd_kcontrol_new cmx655_dst_en[] = {
+ SOC_DAPM_SINGLE_VIRT("Playback Switch", 1)
+};
+
+/*
+ * Define DAPM custom events
+ */
+/*
+ * Special event triggered after mics are enabled.
+ * Wait for a bit to allow the MIC filters time to settle
+ */
+static int cmx655_mic_dapm_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *control, int event)
+{
+ struct snd_soc_component *component =
+ snd_soc_dapm_to_component(widget->dapm);
+ int reg_val;
+
+ switch (event) {
+ case (SND_SOC_DAPM_POST_PMU):
+ // After turn on give MIC filters time
+ // Time can be shorter if the DC blocking filter is not enabled
+ reg_val = snd_soc_component_read(component, CMX655_RVF);
+ if ((reg_val && CMX655_VF_DCBLOCK) > 0) {
+ // This allows time for Mics and DC blocking filter to settle
+ msleep(320);
+ } else {
+ usleep_range(3500, 4000);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Define DAPM widgets for codec/component
+ */
+static const struct snd_soc_dapm_widget cmx655_dapm_widgets[] = {
+ // Input path widgets ++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SND_SOC_DAPM_INPUT("MICL"),
+ SND_SOC_DAPM_INPUT("MICR"),
+
+ // Custom widgets for Mics to get them to turn on before switches
+ {.id = snd_soc_dapm_mic,
+ .name = "Left Mic",
+ .kcontrol_news = NULL,
+ .num_kcontrols = 0,
+ .reg = CMX655_SYSCTRL,
+ .shift = 1,
+ .mask = 1,
+ .on_val = 1,
+ .off_val = 0,
+ .event = cmx655_mic_dapm_event,
+ .event_flags = SND_SOC_DAPM_POST_PMU },
+ {.id = snd_soc_dapm_mic,
+ .name = "Right Mic",
+ .kcontrol_news = NULL,
+ .num_kcontrols = 0,
+ .reg = CMX655_SYSCTRL,
+ .shift = 0,
+ .mask = 1,
+ .on_val = 1,
+ .off_val = 0,
+ .event = cmx655_mic_dapm_event,
+ .event_flags = SND_SOC_DAPM_POST_PMU },
+
+ SND_SOC_DAPM_MUX("SAI_L Capture Mux", SND_SOC_NOPM, 0, 0,
+ &cmx655_mic_mux),
+ SND_SOC_DAPM_MUX("SAI_R Capture Mux", SND_SOC_NOPM, 0, 0,
+ &cmx655_mic_mux),
+
+ // Note: SAI enable is controlled by DAI's HWparams and shutdown. Using
+ // DAPM control resulted in I2S sync errors on the platform driver
+ SND_SOC_DAPM_AIF_OUT("SAI Left Out", "CMX655 Record", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_OUT("SAI Right Out", "CMX655 Record", 1,
+ SND_SOC_NOPM, 0, 0),
+
+ // Output path widgets +++++++++++++++++++++++++++++++++++++++++++++++++++++
+ SND_SOC_DAPM_AIF_IN("SAI Left In", "CMX655 Playback", 0,
+ SND_SOC_NOPM, 0, 0),
+ SND_SOC_DAPM_AIF_IN("SAI Right In", "CMX655 Playback", 1,
+ SND_SOC_NOPM, 0, 0),
+
+ SND_SOC_DAPM_MUX("Play_SAI Playback Route", SND_SOC_NOPM, 0, 0,
+ &cmx655_amp_mux),
+
+ SND_SOC_DAPM_DAC("Power Amp", "CMX655 Playback", CMX655_SYSCTRL, 3, 0),
+ SND_SOC_DAPM_DAC("Line Out", "CMX655 Playback", CMX655_SYSCTRL, 4, 0),
+
+ SND_SOC_DAPM_SWITCH("SPKR_EN", SND_SOC_NOPM, 0, 0, cmx655_spkr_en),
+ SND_SOC_DAPM_SWITCH("LOUT_EN", SND_SOC_NOPM, 0, 0, cmx655_lout_en),
+
+ SND_SOC_DAPM_OUTPUT("SPKR"),
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+
+ // Digital side tone widgets +++++++++++++++++++++++++++++++++++++++++++++++
+ SND_SOC_DAPM_SWITCH("DST_EN", SND_SOC_NOPM, 0, 0, cmx655_dst_en),
+ SND_SOC_DAPM_MUX("DST", CMX655_DST, 7, 0, &cmx655_sidetone_mux),
+
+};
+
+/*
+ * Define dynamic audio power management routes for codec/component
+ */
+static const struct snd_soc_dapm_route cmx655_dapm_routes[] = {
+ // Main output path
+ { "SPKR", NULL, "SPKR_EN" },
+ { "LOUT", NULL, "LOUT_EN" },
+ { "SPKR_EN", "Playback Switch", "Power Amp" },
+ { "LOUT_EN", "Playback Switch", "Line Out" },
+ { "Power Amp", NULL, "Play_SAI Playback Route" },
+ { "Line Out", NULL, "Play_SAI Playback Route" },
+ { "Play_SAI Playback Route", "Left", "SAI Left In" },
+ { "Play_SAI Playback Route", "Right", "SAI Right In" },
+ { "Play_SAI Playback Route", "Mean", "SAI Left In" },
+ { "Play_SAI Playback Route", "Mean", "SAI Right In" },
+ // Main input path
+ { "SAI Right Out", NULL, "SAI_R Capture Mux" },
+ { "SAI Left Out", NULL, "SAI_L Capture Mux" },
+ { "SAI_L Capture Mux", "Normal", "Left Mic" },
+ { "SAI_R Capture Mux", "Normal", "Right Mic" },
+ { "SAI_L Capture Mux", "Swapped", "Right Mic" },
+ { "SAI_R Capture Mux", "Swapped", "Left Mic" },
+ { "SAI_L Capture Mux", "Left only", "Left Mic" },
+ { "SAI_R Capture Mux", "Left only", "Left Mic" },
+ { "SAI_L Capture Mux", "Right only", "Right Mic" },
+ { "SAI_R Capture Mux", "Right only", "Right Mic" },
+ { "Right Mic", NULL, "MICR" },
+ { "Left Mic", NULL, "MICL" },
+ // Digital side tone
+ { "DST", "Left", "Left Mic" },
+ { "DST", "Right", "Right Mic" },
+ { "DST", "Mean", "Left Mic" },
+ { "DST", "Mean", "Right Mic" },
+
+ { "DST_EN", "Playback Switch", "DST" },
+
+ { "Power Amp", NULL, "DST_EN" },
+ { "Line Out", NULL, "DST_EN" },
+
+};
+
+/*
+ * Create component driver structure
+ */
+static const struct snd_soc_component_driver cmx655_component_driver = {
+ .controls = cmx655_snd_controls,
+ .num_controls = ARRAY_SIZE(cmx655_snd_controls),
+ .dapm_widgets = cmx655_dapm_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(cmx655_dapm_widgets),
+ .dapm_routes = cmx655_dapm_routes,
+ .num_dapm_routes = ARRAY_SIZE(cmx655_dapm_routes),
+
+ .probe = cmx655_component_probe,
+ .remove = cmx655_component_remove
+};
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End of component/codec driver
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+/*
+ * Read custom setting from device tree
+ */
+int cmx655_parse_data_from_of(const struct device_node *device_node,
+ struct cmx655_data *cmx655_data)
+{
+ int ret;
+ unsigned int val;
+
+ ret =
+ of_property_read_u32(device_node,
+ "cmx655,classd-oc-reset-attempts", &val);
+ if (ret >= 0)
+ cmx655_data->oc_cnt_max = val;
+ else
+ cmx655_data->oc_cnt_max = 5;
+
+ return 0;
+}
+
+EXPORT_SYMBOL(cmx655_parse_data_from_of);
+/*
+ * Method for component registration
+ */
+int cmx655_common_register_component(struct device *dev)
+{
+ return devm_snd_soc_register_component(dev,
+ &cmx655_component_driver,
+ &cmx655_dai_driver, 1);
+}
+
+EXPORT_SYMBOL(cmx655_common_register_component);
+
+void cmx655_common_unregister_component(struct device *dev)
+{
+ snd_soc_unregister_component(dev);
+}
+
+EXPORT_SYMBOL(cmx655_common_unregister_component);
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Module info
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+MODULE_DESCRIPTION("ASoC CMX655 driver");
+MODULE_AUTHOR("CML");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#ifndef CMX655_H
+#define CMX655_H
+
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <sound/pcm.h>
+
+#define CMX655_ISR (0x00)
+#define CMX655_ISR_MICR (1 << 0)
+#define CMX655_ISR_MICL (1 << 1)
+#define CMX655_ISR_AMPOC (1 << 2)
+#define CMX655_ISR_AMPCLIP (1 << 3)
+#define CMX655_ISR_CLKRDY (1 << 4)
+#define CMX655_ISR_THERM (1 << 5)
+#define CMX655_ISR_VOL (1 << 6)
+#define CMX655_ISR_CAL (1 << 7)
+
+#define CMX655_ISM (0x01)
+#define CMX655_ISM_MICR (1 << 0)
+#define CMX655_ISM_MICL (1 << 1)
+#define CMX655_ISM_AMPOC (1 << 2)
+#define CMX655_ISM_AMPCLIP (1 << 3)
+#define CMX655_ISM_CLKRDY (1 << 4)
+#define CMX655_ISM_THERM (1 << 5)
+#define CMX655_ISM_VOL (1 << 6)
+#define CMX655_ISM_CAL (1 << 7)
+#define CMX655_ISE (0x02)
+#define CMX655_CLKCTRL (0x03)
+#define CMX655_CLKCTRL_PREDIV_SHIFT (0)
+#define CMX655_CLKCTRL_PREDIV_VALUE (0x3)
+#define CMX655_CLKCTRL_PREDIV_MASK (CMX655_CLKCTRL_PREDIV_VALUE << \
+ CMX655_CLKCTRL_PREDIV_SHIFT)
+#define CMX655_CLKCTRL_CLRSRC_SHIFT (2)
+#define CMX655_CLKCTRL_CLRSRC_VALUE (0x7)
+#define CMX655_CLKCTRL_CLRSRC_MASK (CMX655_CLKCTRL_CLRSRC_VALUE << \
+ CMX655_CLKCTRL_CLRSRC_SHIFT)
+#define CMX655_CLKCTRL_CLRSRC_RCLK (0 << CMX655_CLKCTRL_CLRSRC_SHIFT)
+#define CMX655_CLKCTRL_CLRSRC_LPO (1 << CMX655_CLKCTRL_CLRSRC_SHIFT)
+#define CMX655_CLKCTRL_CLRSRC_LRCLK (7 << CMX655_CLKCTRL_CLRSRC_SHIFT)
+#define CMX655_CLKCTRL_SR_SHIFT (5)
+#define CMX655_CLKCTRL_SR_VALUE (0x3)
+#define CMX655_CLKCTRL_SR_MASK (CMX655_CLKCTRL_SR_VALUE << \
+ CMX655_CLKCTRL_SR_SHIFT)
+#define CMX655_CLKCTRL_SR_8K (0 << CMX655_CLKCTRL_SR_SHIFT)
+#define CMX655_CLKCTRL_SR_16K (1 << CMX655_CLKCTRL_SR_SHIFT)
+#define CMX655_CLKCTRL_SR_32K (2 << CMX655_CLKCTRL_SR_SHIFT)
+#define CMX655_CLKCTRL_SR_48K (3 << CMX655_CLKCTRL_SR_SHIFT)
+
+#define CMX655_RDIVHI (0x04)
+#define CMX655_RDIVLO (0x05)
+#define CMX655_NDIVHI (0x06)
+#define CMX655_NDIVLO (0x07)
+#define CMX655_PLLCTRL (0x08)
+#define CMX655_PLLCTRL_CPI_SHIFT (0)
+#define CMX655_PLLCTRL_LFILT_SHIFT (4)
+#define CMX655_SAICTRL (0x09)
+#define CMX655_SAI_PCM (1 << 0)
+#define CMX655_SAI_BINV (1 << 2)
+#define CMX655_SAI_POL (1 << 3)
+#define CMX655_SAI_DLY (1 << 4)
+#define CMX655_SAI_MONO (1 << 5)
+#define CMX655_SAI_WL (1 << 6)
+#define CMX655_SAI_MSTR (1 << 7)
+
+#define CMX655_SAIMUX (0x0a)
+#define CMX655_RVF (0x0c)
+#define CMX655_VF_DCBLOCK_SHIFT (2)
+#define CMX655_VF_DCBLOCK (1 << CMX655_VF_DCBLOCK_SHIFT)
+#define CMX655_LDCTRL (0x0d)
+#define CMX655_RDCTRL (0x0e)
+#define CMX655_LEVEL (0x0f)
+#define CMX655_NGCTRL (0x1c)
+#define CMX655_NGTIME (0x1d)
+#define CMX655_NGLSTAT (0x1e)
+#define CMX655_NGRSTAT (0x1f)
+#define CMX655_PVF (0x28)
+#define CMX655_PREAMP (0x29)
+#define CMX655_VOLUME (0x2a)
+#define CMX655_ALCCTRL (0x2b)
+#define CMX655_ALCTIME (0x2c)
+#define CMX655_ALCGAIN (0x2d)
+#define CMX655_ALCSTAT (0x2e)
+#define CMX655_DST (0x2f)
+#define CMX655_CPR (0x30)
+#define CMX655_SYSCTRL (0x32)
+#define CMX655_SYSCTRL_MICR (1 << 0)
+#define CMX655_SYSCTRL_MICL (1 << 1)
+#define CMX655_SYSCTRL_PAMP (1 << 3)
+#define CMX655_SYSCTRL_LOUT (1 << 4)
+#define CMX655_SYSCTRL_SAI (1 << 5)
+
+#define CMX655_COMMAND (0x33)
+#define CMX655_CMD_CLOCK_STOP (0x00)
+#define CMX655_CMD_CLOCK_START (0x01)
+#define CMX655_CMD_SOFT_RESET (0xff)
+
+/* GPIO connection for reset and irq */
+#define CMX655_RESETN (24)
+#define CMX655_IRQN (25)
+#define CMX655_CS (8)
+
+#define CMX655_RATES ( SNDRV_PCM_RATE_8000 |\
+ SNDRV_PCM_RATE_16000 |\
+ SNDRV_PCM_RATE_32000 |\
+ SNDRV_PCM_RATE_48000 )
+
+#define CMX655_FMTS ( SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE )
+
+// clock id's when calling set sysclk
+// Auto = Use RCLK when in DAI master mode. Use LRCLK in Slave mode.
+// DO NOT use CMX655_SYSCLK_LRCLK when in DAI master mode
+#define CMX655_SYSCLK_AUTO (0)
+#define CMX655_SYSCLK_RCLK (1)
+#define CMX655_SYSCLK_LRCLK (2)
+#define CMX655_SYSCLK_LPO (3)
+#define CMX655_SYSCLK_MIN (CMX655_SYSCLK_AUTO)
+#define CMX655_SYSCLK_MAX (CMX655_SYSCLK_LPO)
+
+/*
+ * Structure to hold info on cmx655 setup
+ *
+ */
+struct cmx655_dai_data {
+ int sys_clk;
+ unsigned int enabled_streams;
+ bool best_clk_running; // Clear if prepare needs to setup the clock
+ int clk_src;
+
+};
+
+struct cmx655_data {
+ struct regmap *regmap;
+ struct cmx655_dai_data dai_data;
+ struct gpio_desc *reset_gpio;
+ int irq;
+ // Number of times the class-D overcurrent has been reset
+ unsigned int oc_cnt;
+ // Max times the class-D overcurrent should be reset
+ unsigned int oc_cnt_max;
+};
+
+extern const struct regmap_config cmx655_regmap;
+
+int cmx655_parse_data_from_of(const struct device_node *device_node,
+ struct cmx655_data *cmx655_data);
+int cmx655_common_register_component(struct device *dev);
+void cmx655_common_unregister_component(struct device *dev);
+
+#endif /* CMX655_H */
new file mode 100644
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/of.h>
+
+#include "cmx655.h"
+
+/*
+ * I2C device bind stage used by i2c driver
+ */
+static int cmx655_i2c_probe(struct i2c_client *client)
+{
+ int ret;
+ struct cmx655_data *cmx655_data;
+ struct cmx655_dai_data *cmx655_dai_data =
+ dev_get_platdata(&client->dev);
+ // Init CMX655 data area
+ cmx655_data = devm_kzalloc(&client->dev, sizeof(*cmx655_data),
+ GFP_KERNEL);
+ if (!cmx655_data)
+ return -ENOMEM;
+
+ cmx655_data->regmap = devm_regmap_init_i2c(client, &cmx655_regmap);
+ if (IS_ERR(cmx655_data->regmap))
+ return PTR_ERR(cmx655_data->regmap);
+ cmx655_data->irq = client->irq;
+ // Use existing DAI data if found
+ if (cmx655_dai_data) {
+ memcpy(&cmx655_data->dai_data, cmx655_dai_data,
+ sizeof(*cmx655_dai_data));
+ }
+ // Set-up value following the codec reset that will happen in a bit
+ cmx655_data->dai_data.enabled_streams = 0;
+ cmx655_data->dai_data.best_clk_running = false;
+ // Extract data from Device tree
+ if (client->dev.of_node) {
+ ret =
+ cmx655_parse_data_from_of(client->dev.of_node, cmx655_data);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Failed to extract data from device tree%d\n",
+ ret);
+ return ret;
+ }
+ }
+ // Find gpios
+ // Reset
+ // Find and set low
+ cmx655_data->reset_gpio = devm_gpiod_get_optional(&client->dev,
+ "reset",
+ GPIOD_OUT_LOW);
+ // Reset codec
+ if (cmx655_data->reset_gpio) {
+ // Hold reset line
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 1);
+ // Time of reset pulse must be greater than 1us
+ // sleep for 10us to 1ms, speed is not critical here
+ usleep_range(10, 1000);
+ // release reset line
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 0);
+ } else {
+ dev_dbg(&client->dev, "No reset GPIO, using reset command\n");
+ regmap_write(cmx655_data->regmap, CMX655_COMMAND,
+ CMX655_CMD_SOFT_RESET);
+ }
+ i2c_set_clientdata(client, cmx655_data);
+
+ // Register codec component
+ ret = cmx655_common_register_component(&client->dev);
+
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: Register component failed %d\n", __func__, ret);
+ }
+
+ return ret;
+};
+
+/*
+ * I2C device removal stage used by i2c driver
+ */
+static void cmx655_i2c_remove(struct i2c_client *client)
+{
+ struct cmx655_data *cmx655_data = i2c_get_clientdata(client);
+ // put codec into reset in GPIO given
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 1);
+ // unregister codec
+ cmx655_common_unregister_component(&client->dev);
+};
+
+static const struct i2c_device_id cmx655_device_id[] = {
+ { "cmx655", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, cmx655_device_id);
+/*
+ * Define Open Firmware (OF) match table. Supportr for device tree
+ */
+static const struct of_device_id cmx655_of_match[] = {
+ {.compatible = "cml,cmx655d" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, cmx655_of_match);
+
+/*
+ * Define i2c driver struct
+ */
+static struct i2c_driver cmx655_i2c_driver = {
+ .probe = cmx655_i2c_probe,
+ .remove = cmx655_i2c_remove,
+ .driver = {
+ .name = "cmx655",
+ .of_match_table = cmx655_of_match,
+ },
+ .id_table = cmx655_device_id
+};
+
+module_i2c_driver(cmx655_i2c_driver);
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End if I2C define
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Module info
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+MODULE_DESCRIPTION("ASoC CMX655 driver, I2C adapter");
+MODULE_AUTHOR("CML");
+MODULE_LICENSE("GPL");
new file mode 100644
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/of.h>
+
+#include "cmx655.h"
+
+/*
+ * SPI device bind stage used by spi driver
+ */
+static int cmx655_spi_probe(struct spi_device *spi)
+{
+ int ret;
+ struct cmx655_data *cmx655_data;
+ struct cmx655_dai_data *cmx655_dai_data = dev_get_drvdata(&spi->dev);
+ // Init CMX655 data area
+ cmx655_data = devm_kzalloc(&spi->dev, sizeof(*cmx655_data), GFP_KERNEL);
+ if (!cmx655_data)
+ return -ENOMEM;
+
+ cmx655_data->regmap = devm_regmap_init_spi(spi, &cmx655_regmap);
+ if (IS_ERR(cmx655_data->regmap))
+ return PTR_ERR(cmx655_data->regmap);
+ cmx655_data->irq = spi->irq;
+ // Use existing DAI data if found
+ if (cmx655_dai_data) {
+ memcpy(&cmx655_data->dai_data, cmx655_dai_data,
+ sizeof(*cmx655_dai_data));
+ }
+ // Set-up value following the codec reset that will happen in a bit
+ cmx655_data->dai_data.enabled_streams = 0;
+ cmx655_data->dai_data.best_clk_running = false;
+ // Extract data from Device tree
+ if (spi->dev.of_node) {
+ ret = cmx655_parse_data_from_of(spi->dev.of_node, cmx655_data);
+ if (ret < 0) {
+ dev_err(&spi->dev,
+ "Failed to extract data from device tree%d\n",
+ ret);
+ return ret;
+ }
+ }
+ // Find gpios
+ // Reset
+ // Find and set low
+ cmx655_data->reset_gpio = devm_gpiod_get_optional(&spi->dev,
+ "reset",
+ GPIOD_OUT_LOW);
+ // Reset codec
+ if (cmx655_data->reset_gpio) {
+ // Hold reset line
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 1);
+ // Time of reset pulse must be greater than 1us
+ // sleep for 10us to 1ms, speed is not critical here
+ usleep_range(10, 1000);
+ // release reset line
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 0);
+ } else {
+ dev_dbg(&spi->dev, "No reset GPIO, using reset command\n");
+ regmap_write(cmx655_data->regmap, CMX655_COMMAND,
+ CMX655_CMD_SOFT_RESET);
+ }
+ spi_set_drvdata(spi, cmx655_data);
+
+ // Register codec component
+ ret = cmx655_common_register_component(&spi->dev);
+
+ if (ret < 0) {
+ dev_err(&spi->dev,
+ "%s: Register component failed %d\n", __func__, ret);
+ }
+
+ return ret;
+};
+
+/*
+ * SPI device removal stage used by spi driver
+ */
+static void cmx655_spi_remove(struct spi_device *spi)
+{
+ struct cmx655_data *cmx655_data = spi_get_drvdata(spi);
+ // put codec into reset in GPIO given
+ gpiod_set_value_cansleep(cmx655_data->reset_gpio, 1);
+ // unregister codec
+ cmx655_common_unregister_component(&spi->dev);
+};
+
+static const struct spi_device_id cmx655_device_id[] = {
+ { "cmx655" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(spi, cmx655_device_id);
+/*
+ * Define Open Firmware (OF) match table. Supportr for device tree
+ */
+static const struct of_device_id cmx655_of_match[] = {
+ {.compatible = "cml,cmx655d" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, cmx655_of_match);
+
+/*
+ * Define spi driver struct
+ */
+static struct spi_driver cmx655_spi_driver = {
+ .probe = cmx655_spi_probe,
+ .remove = cmx655_spi_remove,
+ .driver = {
+ .name = "cmx655",
+ .of_match_table = cmx655_of_match,
+ },
+ .id_table = cmx655_device_id
+};
+
+module_spi_driver(cmx655_spi_driver);
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * End if SPI define
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+
+/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * Module info
+ * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ */
+MODULE_DESCRIPTION("ASoC CMX655 driver, SPI adapter");
+MODULE_AUTHOR("CML");
+MODULE_LICENSE("GPL");
Signed-off-by: Nikola Jelic <nikola.jelic83@gmail.com> --- sound/soc/codecs/Kconfig | 27 + sound/soc/codecs/Makefile | 4 + sound/soc/codecs/cmx655.c | 1179 +++++++++++++++++++++++++++++++++ sound/soc/codecs/cmx655.h | 151 +++++ sound/soc/codecs/cmx655_i2c.c | 135 ++++ sound/soc/codecs/cmx655_spi.c | 132 ++++ 6 files changed, 1628 insertions(+) create mode 100644 sound/soc/codecs/cmx655.c create mode 100644 sound/soc/codecs/cmx655.h create mode 100644 sound/soc/codecs/cmx655_i2c.c create mode 100644 sound/soc/codecs/cmx655_spi.c