|
@@ -0,0 +1,767 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 and
|
|
|
+ * only version 2 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.
|
|
|
+ */
|
|
|
+#include <linux/init.h>
|
|
|
+#include <linux/delay.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/kernel.h>
|
|
|
+#include <linux/i2c.h>
|
|
|
+#include <linux/slab.h>
|
|
|
+#include <sound/core.h>
|
|
|
+#include <sound/pcm.h>
|
|
|
+#include <sound/pcm_params.h>
|
|
|
+#include <sound/tlv.h>
|
|
|
+#include <sound/soc.h>
|
|
|
+#include <linux/gpio.h>
|
|
|
+#include <linux/of_gpio.h>
|
|
|
+#include "csra66x0.h"
|
|
|
+
|
|
|
+/* CSRA66X0 register default values */
|
|
|
+static struct reg_default csra66x0_reg_defaults[] = {
|
|
|
+ {CSRA66X0_AUDIO_IF_RX_CONFIG1, 0x00},
|
|
|
+ {CSRA66X0_AUDIO_IF_RX_CONFIG2, 0x0B},
|
|
|
+ {CSRA66X0_AUDIO_IF_RX_CONFIG3, 0x0F},
|
|
|
+ {CSRA66X0_AUDIO_IF_TX_EN, 0x00},
|
|
|
+ {CSRA66X0_AUDIO_IF_TX_CONFIG1, 0x6B},
|
|
|
+ {CSRA66X0_AUDIO_IF_TX_CONFIG2, 0x02},
|
|
|
+ {CSRA66X0_I2C_DEVICE_ADDRESS, 0x0D},
|
|
|
+ {CSRA66X0_CHIP_ID_FA, 0x39},
|
|
|
+ {CSRA66X0_ROM_VER_FA, 0x08},
|
|
|
+ {CSRA66X0_CHIP_REV_0_FA, 0x05},
|
|
|
+ {CSRA66X0_CHIP_REV_1_FA, 0x03},
|
|
|
+ {CSRA66X0_CH1_MIX_SEL, 0x01},
|
|
|
+ {CSRA66X0_CH2_MIX_SEL, 0x10},
|
|
|
+ {CSRA66X0_CH1_SAMPLE1_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE1_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE3_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE3_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE5_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE5_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE7_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE7_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE2_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE2_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE4_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE4_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE6_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE6_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH1_SAMPLE8_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH1_SAMPLE8_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE1_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE1_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE3_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE3_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE5_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE5_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE7_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE7_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE2_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE2_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE4_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE4_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE6_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE6_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_CH2_SAMPLE8_SCALE_0, 0x00},
|
|
|
+ {CSRA66X0_CH2_SAMPLE8_SCALE_1, 0x20},
|
|
|
+ {CSRA66X0_VOLUME_CONFIG_FA, 0x26},
|
|
|
+ {CSRA66X0_STARTUP_DELAY_FA, 0x00},
|
|
|
+ {CSRA66X0_CH1_VOLUME_0_FA, 0x19},
|
|
|
+ {CSRA66X0_CH1_VOLUME_1_FA, 0x01},
|
|
|
+ {CSRA66X0_CH2_VOLUME_0_FA, 0x19},
|
|
|
+ {CSRA66X0_CH2_VOLUME_1_FA, 0x01},
|
|
|
+ {CSRA66X0_QUAD_ENC_COUNT_0_FA, 0x00},
|
|
|
+ {CSRA66X0_QUAD_ENC_COUNT_1_FA, 0x00},
|
|
|
+ {CSRA66X0_SOFT_CLIP_CONFIG, 0x00},
|
|
|
+ {CSRA66X0_CH1_HARD_CLIP_THRESH, 0x00},
|
|
|
+ {CSRA66X0_CH2_HARD_CLIP_THRESH, 0x00},
|
|
|
+ {CSRA66X0_SOFT_CLIP_THRESH, 0x00},
|
|
|
+ {CSRA66X0_DS_ENABLE_THRESH_0, 0x05},
|
|
|
+ {CSRA66X0_DS_ENABLE_THRESH_1, 0x00},
|
|
|
+ {CSRA66X0_DS_TARGET_COUNT_0, 0x00},
|
|
|
+ {CSRA66X0_DS_TARGET_COUNT_1, 0xFF},
|
|
|
+ {CSRA66X0_DS_TARGET_COUNT_2, 0xFF},
|
|
|
+ {CSRA66X0_DS_DISABLE_THRESH_0, 0x0F},
|
|
|
+ {CSRA66X0_DS_DISABLE_THRESH_1, 0x00},
|
|
|
+ {CSRA66X0_DCA_CTRL, 0x07},
|
|
|
+ {CSRA66X0_CH1_DCA_THRESH, 0x40},
|
|
|
+ {CSRA66X0_CH2_DCA_THRESH, 0x40},
|
|
|
+ {CSRA66X0_DCA_ATTACK_RATE, 0x00},
|
|
|
+ {CSRA66X0_DCA_RELEASE_RATE, 0x00},
|
|
|
+ {CSRA66X0_CH1_OUTPUT_INVERT_EN, 0x00},
|
|
|
+ {CSRA66X0_CH2_OUTPUT_INVERT_EN, 0x00},
|
|
|
+ {CSRA66X0_CH1_176P4K_DELAY, 0x00},
|
|
|
+ {CSRA66X0_CH2_176P4K_DELAY, 0x00},
|
|
|
+ {CSRA66X0_CH1_192K_DELAY, 0x00},
|
|
|
+ {CSRA66X0_CH2_192K_DELAY, 0x00},
|
|
|
+ {CSRA66X0_DEEMP_CONFIG_FA, 0x00},
|
|
|
+ {CSRA66X0_CH1_TREBLE_GAIN_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH2_TREBLE_GAIN_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH1_TREBLE_FC_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH2_TREBLE_FC_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH1_BASS_GAIN_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH2_BASS_GAIN_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH1_BASS_FC_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_CH2_BASS_FC_CTRL_FA, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_8K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_11P025K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_16K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_22P05K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_32K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_44P1K_48K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_88P2K_96K, 0x00},
|
|
|
+ {CSRA66X0_FILTER_SEL_176P4K_192K, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_USER_DSP_CTRL, 0x00},
|
|
|
+ {CSRA66X0_TEST_TONE_CTRL, 0x00},
|
|
|
+ {CSRA66X0_TEST_TONE_FREQ_0, 0x00},
|
|
|
+ {CSRA66X0_TEST_TONE_FREQ_1, 0x00},
|
|
|
+ {CSRA66X0_TEST_TONE_FREQ_2, 0x00},
|
|
|
+ {CSRA66X0_AUDIO_RATE_CTRL_FA, 0x08},
|
|
|
+ {CSRA66X0_MODULATION_INDEX_CTRL, 0x3F},
|
|
|
+ {CSRA66X0_MODULATION_INDEX_COUNT, 0x10},
|
|
|
+ {CSRA66X0_MIN_MODULATION_PULSE_WIDTH, 0x7A},
|
|
|
+ {CSRA66X0_DEAD_TIME_CTRL, 0x00},
|
|
|
+ {CSRA66X0_DEAD_TIME_THRESHOLD_0, 0xE7},
|
|
|
+ {CSRA66X0_DEAD_TIME_THRESHOLD_1, 0x26},
|
|
|
+ {CSRA66X0_DEAD_TIME_THRESHOLD_2, 0x40},
|
|
|
+ {CSRA66X0_CH1_LOW_SIDE_DLY, 0x00},
|
|
|
+ {CSRA66X0_CH2_LOW_SIDE_DLY, 0x00},
|
|
|
+ {CSRA66X0_SPECTRUM_CTRL, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_SPECTRUM_SPREAD_CTRL, 0x0C},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_EXT_PA_PROTECT_POLARITY, 0x03},
|
|
|
+ {CSRA66X0_TEMP0_BACKOFF_COMP_VALUE, 0x98},
|
|
|
+ {CSRA66X0_TEMP0_SHUTDOWN_COMP_VALUE, 0xA3},
|
|
|
+ {CSRA66X0_TEMP1_BACKOFF_COMP_VALUE, 0x98},
|
|
|
+ {CSRA66X0_TEMP1_SHUTDOWN_COMP_VALUE, 0xA3},
|
|
|
+ {CSRA66X0_TEMP_PROT_BACKOFF, 0x00},
|
|
|
+ {CSRA66X0_TEMP_READ0_FA, 0x00},
|
|
|
+ {CSRA66X0_TEMP_READ1_FA, 0x00},
|
|
|
+ {CSRA66X0_CHIP_STATE_CTRL_FA, 0x02},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_PWM_OUTPUT_CONFIG, 0x00},
|
|
|
+ {CSRA66X0_MISC_CONTROL_STATUS_0, 0x08},
|
|
|
+ {CSRA66X0_MISC_CONTROL_STATUS_1_FA, 0x40},
|
|
|
+ {CSRA66X0_PIO0_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO1_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO2_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO3_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO4_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO5_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO6_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO7_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO8_SELECT, 0x00},
|
|
|
+ {CSRA66X0_PIO_DIRN0, 0xFF},
|
|
|
+ {CSRA66X0_PIO_DIRN1, 0x01},
|
|
|
+ {CSRA66X0_PIO_PULL_EN0, 0xFF},
|
|
|
+ {CSRA66X0_PIO_PULL_EN1, 0x01},
|
|
|
+ {CSRA66X0_PIO_PULL_DIR0, 0x00},
|
|
|
+ {CSRA66X0_PIO_PULL_DIR1, 0x00},
|
|
|
+ {CSRA66X0_PIO_DRIVE_OUT0_FA, 0x00},
|
|
|
+ {CSRA66X0_PIO_DRIVE_OUT1_FA, 0x00},
|
|
|
+ {CSRA66X0_PIO_STATUS_IN0_FA, 0x00},
|
|
|
+ {CSRA66X0_PIO_STATUS_IN1_FA, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_IRQ_OUTPUT_ENABLE, 0x00},
|
|
|
+ {CSRA66X0_IRQ_OUTPUT_POLARITY, 0x01},
|
|
|
+ {CSRA66X0_IRQ_OUTPUT_STATUS_FA, 0x00},
|
|
|
+ {CSRA66X0_CLIP_DCA_STATUS_FA, 0x00},
|
|
|
+ {CSRA66X0_CHIP_STATE_STATUS_FA, 0x02},
|
|
|
+ {CSRA66X0_FAULT_STATUS_FA, 0x00},
|
|
|
+ {CSRA66X0_OTP_STATUS_FA, 0x00},
|
|
|
+ {CSRA66X0_AUDIO_IF_STATUS_FA, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_DSP_SATURATION_STATUS_FA, 0x00},
|
|
|
+ {CSRA66X0_AUDIO_RATE_STATUS_FA, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_DISABLE_PWM_OUTPUT, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_OTP_VER_FA, 0x03},
|
|
|
+ {CSRA66X0_RAM_VER_FA, 0x02},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_AUDIO_SATURATION_FLAGS_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_1_01_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_1_02_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_1_03_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_2_01_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_2_02_FA, 0x00},
|
|
|
+ {CSRA66X0_DCOFFSET_CHAN_2_03_FA, 0x00},
|
|
|
+ {CSRA66X0_FORCED_PA_SWITCHING_CTRL, 0x90},
|
|
|
+ {CSRA66X0_PA_FORCE_PULSE_WIDTH, 0x07},
|
|
|
+ {CSRA66X0_PA_HIGH_MODULATION_CTRL_CH1, 0x00},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_HIGH_MODULATION_THRESHOLD_LOW, 0xD4},
|
|
|
+ {CSRA66X0_HIGH_MODULATION_THRESHOLD_HIGH, 0x78},
|
|
|
+ /* RESERVED */
|
|
|
+ {CSRA66X0_PA_FREEZE_CTRL, 0x00},
|
|
|
+ {CSRA66X0_DCA_FREEZE_CTRL, 0x3C},
|
|
|
+ /* RESERVED */
|
|
|
+};
|
|
|
+
|
|
|
+static bool csra66x0_volatile_register(struct device *dev, unsigned int reg)
|
|
|
+{
|
|
|
+ switch (reg) {
|
|
|
+ case CSRA66X0_CHIP_ID_FA:
|
|
|
+ case CSRA66X0_TEMP_READ0_FA:
|
|
|
+ case CSRA66X0_TEMP_READ1_FA:
|
|
|
+ case CSRA66X0_MISC_CONTROL_STATUS_1_FA:
|
|
|
+ case CSRA66X0_IRQ_OUTPUT_STATUS_FA:
|
|
|
+ case CSRA66X0_CLIP_DCA_STATUS_FA:
|
|
|
+ case CSRA66X0_CHIP_STATE_STATUS_FA:
|
|
|
+ case CSRA66X0_FAULT_STATUS_FA:
|
|
|
+ case CSRA66X0_OTP_STATUS_FA:
|
|
|
+ case CSRA66X0_AUDIO_IF_STATUS_FA:
|
|
|
+ case CSRA66X0_DSP_SATURATION_STATUS_FA:
|
|
|
+ case CSRA66X0_AUDIO_RATE_STATUS_FA:
|
|
|
+ return true;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static bool csra66x0_writeable_registers(struct device *dev, unsigned int reg)
|
|
|
+{
|
|
|
+ if ((reg >= CSRA66X0_AUDIO_IF_RX_CONFIG1)
|
|
|
+ && (reg <= CSRA66X0_MAX_REGISTER_ADDR))
|
|
|
+ return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static bool csra66x0_readable_registers(struct device *dev, unsigned int reg)
|
|
|
+{
|
|
|
+ if ((reg >= CSRA66X0_AUDIO_IF_RX_CONFIG1)
|
|
|
+ && (reg <= CSRA66X0_MAX_REGISTER_ADDR))
|
|
|
+ return true;
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+/* codec private data */
|
|
|
+struct csra66x0_priv {
|
|
|
+ struct regmap *regmap;
|
|
|
+ struct snd_soc_codec *codec;
|
|
|
+ int spk_volume_ch1;
|
|
|
+ int spk_volume_ch2;
|
|
|
+ int irq;
|
|
|
+ int vreg_gpio;
|
|
|
+ u32 irq_active_low;
|
|
|
+ u32 in_cluster;
|
|
|
+ u32 is_master;
|
|
|
+};
|
|
|
+
|
|
|
+/*
|
|
|
+ * CSRA66X0 Controls
|
|
|
+ */
|
|
|
+static const DECLARE_TLV_DB_SCALE(csra66x0_volume_tlv, -9000, 25, 0);
|
|
|
+static const DECLARE_TLV_DB_RANGE(csra66x0_bass_treble_tlv,
|
|
|
+ 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0),
|
|
|
+ 1, 15, TLV_DB_SCALE_ITEM(-1500, 100, 0),
|
|
|
+ 16, 30, TLV_DB_SCALE_ITEM(100, 100, 0)
|
|
|
+);
|
|
|
+
|
|
|
+static int csra66x0_get_volume(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct soc_mixer_control *mc =
|
|
|
+ (struct soc_mixer_control *)kcontrol->private_value;
|
|
|
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
|
|
|
+ unsigned int reg_l = mc->reg;
|
|
|
+ unsigned int reg_r = mc->rreg;
|
|
|
+ unsigned int val_l, val_r;
|
|
|
+
|
|
|
+ val_l = (snd_soc_read(codec, reg_l) & 0xff) |
|
|
|
+ ((snd_soc_read(codec,
|
|
|
+ CSRA66X0_CH1_VOLUME_1_FA) & (0x01)) << 8);
|
|
|
+ val_r = (snd_soc_read(codec, reg_r) & 0xff) |
|
|
|
+ ((snd_soc_read(codec,
|
|
|
+ CSRA66X0_CH2_VOLUME_1_FA) & (0x01)) << 8);
|
|
|
+ ucontrol->value.integer.value[0] = val_l;
|
|
|
+ ucontrol->value.integer.value[1] = val_r;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_set_volume(struct snd_kcontrol *kcontrol,
|
|
|
+ struct snd_ctl_elem_value *ucontrol)
|
|
|
+{
|
|
|
+ struct soc_mixer_control *mc =
|
|
|
+ (struct soc_mixer_control *)kcontrol->private_value;
|
|
|
+ struct snd_soc_codec *codec = snd_soc_kcontrol_codec(kcontrol);
|
|
|
+ struct csra66x0_priv *csra66x0 = snd_soc_codec_get_drvdata(codec);
|
|
|
+ unsigned int reg_l = mc->reg;
|
|
|
+ unsigned int reg_r = mc->rreg;
|
|
|
+ unsigned int val_l[2];
|
|
|
+ unsigned int val_r[2];
|
|
|
+
|
|
|
+ csra66x0->spk_volume_ch1 = (ucontrol->value.integer.value[0]);
|
|
|
+ csra66x0->spk_volume_ch2 = (ucontrol->value.integer.value[1]);
|
|
|
+ val_l[0] = csra66x0->spk_volume_ch1 & SPK_VOLUME_LSB_MSK;
|
|
|
+ val_l[1] = (csra66x0->spk_volume_ch1 & SPK_VOLUME_MSB_MSK) ? 1 : 0;
|
|
|
+ val_r[0] = csra66x0->spk_volume_ch2 & SPK_VOLUME_LSB_MSK;
|
|
|
+ val_r[1] = (csra66x0->spk_volume_ch2 & SPK_VOLUME_MSB_MSK) ? 1 : 0;
|
|
|
+ snd_soc_write(codec, reg_l, val_l[0]);
|
|
|
+ snd_soc_write(codec, reg_r, val_r[0]);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH1_VOLUME_1_FA, val_l[1]);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH2_VOLUME_1_FA, val_r[1]);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* enumerated controls */
|
|
|
+static const char * const csra66x0_mute_output_text[] = {"PLAY", "MUTE"};
|
|
|
+static const char * const csra66x0_output_invert_text[] = {
|
|
|
+ "UNCHANGED", "INVERTED"};
|
|
|
+static const char * const csra66x0_deemp_config_text[] = {
|
|
|
+ "DISABLED", "ENABLED"};
|
|
|
+
|
|
|
+SOC_ENUM_SINGLE_DECL(csra66x0_mute_output_enum,
|
|
|
+ CSRA66X0_MISC_CONTROL_STATUS_1_FA, 2,
|
|
|
+ csra66x0_mute_output_text);
|
|
|
+SOC_ENUM_SINGLE_DECL(csra66x0_ch1_output_invert_enum,
|
|
|
+ CSRA66X0_CH1_OUTPUT_INVERT_EN, 0,
|
|
|
+ csra66x0_output_invert_text);
|
|
|
+SOC_ENUM_SINGLE_DECL(csra66x0_ch2_output_invert_enum,
|
|
|
+ CSRA66X0_CH2_OUTPUT_INVERT_EN, 0,
|
|
|
+ csra66x0_output_invert_text);
|
|
|
+SOC_ENUM_DOUBLE_DECL(csra66x0_deemp_config_enum,
|
|
|
+ CSRA66X0_DEEMP_CONFIG_FA, 0, 1,
|
|
|
+ csra66x0_deemp_config_text);
|
|
|
+
|
|
|
+static const struct snd_kcontrol_new csra66x0_snd_controls[] = {
|
|
|
+ /* volume */
|
|
|
+ SOC_DOUBLE_R_EXT_TLV("PA VOLUME", CSRA66X0_CH1_VOLUME_0_FA,
|
|
|
+ CSRA66X0_CH2_VOLUME_0_FA, 0, 0x1C9, 0,
|
|
|
+ csra66x0_get_volume, csra66x0_set_volume,
|
|
|
+ csra66x0_volume_tlv),
|
|
|
+
|
|
|
+ /* bass treble */
|
|
|
+ SOC_DOUBLE_R_TLV("PA BASS GAIN", CSRA66X0_CH1_BASS_GAIN_CTRL_FA,
|
|
|
+ CSRA66X0_CH2_BASS_GAIN_CTRL_FA, 0, 0x1E, 0,
|
|
|
+ csra66x0_bass_treble_tlv),
|
|
|
+ SOC_DOUBLE_R_TLV("PA TREBLE GAIN", CSRA66X0_CH1_TREBLE_GAIN_CTRL_FA,
|
|
|
+ CSRA66X0_CH2_TREBLE_GAIN_CTRL_FA, 0, 0x1E, 0,
|
|
|
+ csra66x0_bass_treble_tlv),
|
|
|
+ SOC_DOUBLE_R("PA BASS_XOVER FREQ", CSRA66X0_CH1_BASS_FC_CTRL_FA,
|
|
|
+ CSRA66X0_CH2_BASS_FC_CTRL_FA, 0, 2, 0),
|
|
|
+ SOC_DOUBLE_R("PA TREBLE_XOVER FREQ", CSRA66X0_CH1_TREBLE_FC_CTRL_FA,
|
|
|
+ CSRA66X0_CH2_TREBLE_FC_CTRL_FA, 0, 2, 0),
|
|
|
+
|
|
|
+ /* switch */
|
|
|
+ SOC_ENUM("PA MUTE_OUTPUT SWITCH", csra66x0_mute_output_enum),
|
|
|
+ SOC_ENUM("PA DE-EMPHASIS SWITCH", csra66x0_deemp_config_enum),
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_kcontrol_new csra_mix_switch[] = {
|
|
|
+ SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0)
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_soc_dapm_widget csra66x0_dapm_widgets[] = {
|
|
|
+ SND_SOC_DAPM_INPUT("IN"),
|
|
|
+ SND_SOC_DAPM_MIXER("MIXER", SND_SOC_NOPM, 0, 0,
|
|
|
+ csra_mix_switch, ARRAY_SIZE(csra_mix_switch)),
|
|
|
+ SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
|
|
|
+ SND_SOC_DAPM_PGA("PGA", CSRA66X0_CHIP_STATE_CTRL_FA, 0, 0, NULL, 0),
|
|
|
+ SND_SOC_DAPM_OUTPUT("SPKR"),
|
|
|
+
|
|
|
+ SND_SOC_DAPM_SUPPLY("POWER", CSRA66X0_CHIP_STATE_CTRL_FA,
|
|
|
+ 1, 1, NULL, 0),
|
|
|
+};
|
|
|
+
|
|
|
+static const struct snd_soc_dapm_route csra66x0_dapm_routes[] = {
|
|
|
+ {"MIXER", "Switch", "IN"},
|
|
|
+ {"DAC", NULL, "MIXER"},
|
|
|
+ {"PGA", NULL, "DAC"},
|
|
|
+ {"SPKR", NULL, "PGA"},
|
|
|
+ {"SPKR", NULL, "POWER"},
|
|
|
+};
|
|
|
+
|
|
|
+static int csra66x0_init(struct snd_soc_codec *codec,
|
|
|
+ struct csra66x0_priv *csra66x0)
|
|
|
+{
|
|
|
+ /* config */
|
|
|
+ snd_soc_write(codec, CSRA66X0_CHIP_STATE_CTRL_FA, CONFIG_STATE);
|
|
|
+ /* settle time in HW is min. 500ms before proceeding */
|
|
|
+ msleep(500);
|
|
|
+
|
|
|
+ /* setup */
|
|
|
+ snd_soc_write(codec, CSRA66X0_MISC_CONTROL_STATUS_0, 0x09);
|
|
|
+ snd_soc_write(codec, CSRA66X0_TEMP_PROT_BACKOFF, 0x0C);
|
|
|
+ snd_soc_write(codec, CSRA66X0_EXT_PA_PROTECT_POLARITY, 0x03);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PWM_OUTPUT_CONFIG, 0xC8);
|
|
|
+ csra66x0->spk_volume_ch1 = SPK_VOLUME_M20DB;
|
|
|
+ csra66x0->spk_volume_ch2 = SPK_VOLUME_M20DB;
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH1_VOLUME_0_FA, SPK_VOLUME_M20DB_LSB);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH2_VOLUME_0_FA, SPK_VOLUME_M20DB_LSB);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH1_VOLUME_1_FA, SPK_VOLUME_M20DB_MSB);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH2_VOLUME_1_FA, SPK_VOLUME_M20DB_MSB);
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_DEAD_TIME_CTRL, 0x0);
|
|
|
+ snd_soc_write(codec, CSRA66X0_DEAD_TIME_THRESHOLD_0, 0xE7);
|
|
|
+ snd_soc_write(codec, CSRA66X0_DEAD_TIME_THRESHOLD_1, 0x26);
|
|
|
+ snd_soc_write(codec, CSRA66X0_DEAD_TIME_THRESHOLD_2, 0x40);
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_MIN_MODULATION_PULSE_WIDTH, 0x7A);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH1_HARD_CLIP_THRESH, 0x00);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH2_HARD_CLIP_THRESH, 0x00);
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH1_DCA_THRESH, 0x40);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CH2_DCA_THRESH, 0x40);
|
|
|
+ snd_soc_write(codec, CSRA66X0_DCA_ATTACK_RATE, 0x00);
|
|
|
+ snd_soc_write(codec, CSRA66X0_DCA_RELEASE_RATE, 0x00);
|
|
|
+
|
|
|
+ if (csra66x0->irq) {
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO0_SELECT, 0x1);
|
|
|
+ if (csra66x0->irq_active_low)
|
|
|
+ snd_soc_write(codec, CSRA66X0_IRQ_OUTPUT_POLARITY, 0x0);
|
|
|
+ else
|
|
|
+ snd_soc_write(codec, CSRA66X0_IRQ_OUTPUT_POLARITY, 0x1);
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_IRQ_OUTPUT_ENABLE, 0x01);
|
|
|
+ } else {
|
|
|
+ snd_soc_write(codec, CSRA66X0_IRQ_OUTPUT_ENABLE, 0x00);
|
|
|
+ }
|
|
|
+ /* settle time in HW is min. 500ms before slave initializing */
|
|
|
+ msleep(500);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_soc_probe(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ struct csra66x0_priv *csra66x0 = snd_soc_codec_get_drvdata(codec);
|
|
|
+ struct snd_soc_dapm_context *dapm;
|
|
|
+ char name[50];
|
|
|
+
|
|
|
+ if (csra66x0->in_cluster) {
|
|
|
+ dapm = snd_soc_codec_get_dapm(codec);
|
|
|
+ dev_dbg(codec->dev, "%s: setting %s to codec %s\n",
|
|
|
+ __func__, codec->component.name_prefix,
|
|
|
+ codec->component.name);
|
|
|
+ snd_soc_write(codec, CSRA66X0_CHIP_STATE_CTRL_FA,
|
|
|
+ CONFIG_STATE);
|
|
|
+ /* settle time in HW is min. 500ms before proceeding */
|
|
|
+ msleep(500);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO7_SELECT, 0x04);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO8_SELECT, 0x04);
|
|
|
+ if (csra66x0->is_master) {
|
|
|
+ /* Master specific config */
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_EN0, 0xFF);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_DIR0, 0x80);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_EN1, 0x01);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_DIR1, 0x01);
|
|
|
+ } else {
|
|
|
+ /* Slave specific config */
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_EN0, 0x7F);
|
|
|
+ snd_soc_write(codec, CSRA66X0_PIO_PULL_EN1, 0x00);
|
|
|
+ }
|
|
|
+ snd_soc_write(codec, CSRA66X0_DCA_CTRL, 0x05);
|
|
|
+ if (dapm->component) {
|
|
|
+ strlcpy(name, dapm->component->name_prefix,
|
|
|
+ sizeof(name));
|
|
|
+ strlcat(name, " IN", sizeof(name));
|
|
|
+ snd_soc_dapm_ignore_suspend(dapm, name);
|
|
|
+ strlcpy(name, dapm->component->name_prefix,
|
|
|
+ sizeof(name));
|
|
|
+ strlcat(name, " SPKR", sizeof(name));
|
|
|
+ snd_soc_dapm_ignore_suspend(dapm, name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ csra66x0->codec = codec;
|
|
|
+
|
|
|
+ /* common configuration */
|
|
|
+ csra66x0_init(codec, csra66x0);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_soc_remove(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ snd_soc_write(codec, CSRA66X0_CHIP_STATE_CTRL_FA, STDBY_STATE);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_soc_suspend(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ u16 state_reg = snd_soc_read(codec, CSRA66X0_CHIP_STATE_CTRL_FA) & 0xFC;
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_CHIP_STATE_CTRL_FA, state_reg |
|
|
|
+ STDBY_STATE);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_soc_resume(struct snd_soc_codec *codec)
|
|
|
+{
|
|
|
+ u16 state_reg = snd_soc_read(codec, CSRA66X0_CHIP_STATE_CTRL_FA) & 0xFC;
|
|
|
+
|
|
|
+ snd_soc_write(codec, CSRA66X0_CHIP_STATE_CTRL_FA, state_reg |
|
|
|
+ RUN_STATE);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static struct regmap *csra66x0_get_regmap(struct device *dev)
|
|
|
+{
|
|
|
+ struct csra66x0_priv *csra66x0_ctrl = dev_get_drvdata(dev);
|
|
|
+
|
|
|
+ if (!csra66x0_ctrl)
|
|
|
+ return NULL;
|
|
|
+ return csra66x0_ctrl->regmap;
|
|
|
+}
|
|
|
+
|
|
|
+static struct snd_soc_codec_driver soc_codec_drv_csra66x0 = {
|
|
|
+ .probe = csra66x0_soc_probe,
|
|
|
+ .remove = csra66x0_soc_remove,
|
|
|
+ .suspend = csra66x0_soc_suspend,
|
|
|
+ .resume = csra66x0_soc_resume,
|
|
|
+ .get_regmap = csra66x0_get_regmap,
|
|
|
+ .component_driver = {
|
|
|
+ .controls = csra66x0_snd_controls,
|
|
|
+ .num_controls = ARRAY_SIZE(csra66x0_snd_controls),
|
|
|
+ .dapm_widgets = csra66x0_dapm_widgets,
|
|
|
+ .num_dapm_widgets = ARRAY_SIZE(csra66x0_dapm_widgets),
|
|
|
+ .dapm_routes = csra66x0_dapm_routes,
|
|
|
+ .num_dapm_routes = ARRAY_SIZE(csra66x0_dapm_routes),
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+static struct regmap_config csra66x0_regmap_config = {
|
|
|
+ .reg_bits = 16,
|
|
|
+ .val_bits = 8,
|
|
|
+ .cache_type = REGCACHE_RBTREE,
|
|
|
+ .reg_defaults = csra66x0_reg_defaults,
|
|
|
+ .num_reg_defaults = ARRAY_SIZE(csra66x0_reg_defaults),
|
|
|
+ .max_register = CSRA66X0_MAX_REGISTER_ADDR,
|
|
|
+ .volatile_reg = csra66x0_volatile_register,
|
|
|
+ .writeable_reg = csra66x0_writeable_registers,
|
|
|
+ .readable_reg = csra66x0_readable_registers,
|
|
|
+};
|
|
|
+
|
|
|
+static irqreturn_t csra66x0_irq(int irq, void *data)
|
|
|
+{
|
|
|
+ struct csra66x0_priv *csra66x0 = (struct csra66x0_priv *) data;
|
|
|
+ struct snd_soc_codec *codec = csra66x0->codec;
|
|
|
+ u16 val;
|
|
|
+
|
|
|
+ /* Treat interrupt before codec is initialized as spurious */
|
|
|
+ if (codec == NULL)
|
|
|
+ return IRQ_NONE;
|
|
|
+
|
|
|
+ dev_dbg(codec->dev, "%s: csra66x0_interrupt\n", __func__);
|
|
|
+
|
|
|
+ /* fault indication */
|
|
|
+ val = snd_soc_read(codec, CSRA66X0_IRQ_OUTPUT_STATUS_FA) & 0x1;
|
|
|
+ if (val) {
|
|
|
+ val = snd_soc_read(codec, CSRA66X0_FAULT_STATUS_FA);
|
|
|
+ if (val & FAULT_STATUS_INTERNAL)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_INTERNAL 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+ if (val & FAULT_STATUS_OTP_INTEGRITY)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_OTP_INTEGRITY 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+ if (val & FAULT_STATUS_PADS2)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_PADS2 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+ if (val & FAULT_STATUS_SMPS)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_SMPS 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+ if (val & FAULT_STATUS_TEMP)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_TEMP 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+ if (val & FAULT_STATUS_PROTECT)
|
|
|
+ dev_dbg(codec->dev, "%s: FAULT_STATUS_PROTECT 0x%X\n",
|
|
|
+ __func__, val);
|
|
|
+
|
|
|
+ /* clear fault state and re-init */
|
|
|
+ snd_soc_write(codec, CSRA66X0_FAULT_STATUS_FA, 0x00);
|
|
|
+ csra66x0_init(codec, csra66x0);
|
|
|
+ } else {
|
|
|
+ return IRQ_NONE;
|
|
|
+ }
|
|
|
+ return IRQ_HANDLED;
|
|
|
+};
|
|
|
+
|
|
|
+static const struct of_device_id csra66x0_of_match[] = {
|
|
|
+ { .compatible = "qcom,csra66x0", },
|
|
|
+ { }
|
|
|
+};
|
|
|
+MODULE_DEVICE_TABLE(of, csra66x0_of_match);
|
|
|
+
|
|
|
+#if IS_ENABLED(CONFIG_I2C)
|
|
|
+static int csra66x0_i2c_probe(struct i2c_client *client_i2c,
|
|
|
+ const struct i2c_device_id *id)
|
|
|
+{
|
|
|
+ struct csra66x0_priv *csra66x0;
|
|
|
+ int ret, irq_trigger;
|
|
|
+
|
|
|
+ csra66x0 = devm_kzalloc(&client_i2c->dev, sizeof(struct csra66x0_priv),
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (csra66x0 == NULL)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ csra66x0->regmap = devm_regmap_init_i2c(client_i2c,
|
|
|
+ &csra66x0_regmap_config);
|
|
|
+ if (IS_ERR(csra66x0->regmap)) {
|
|
|
+ ret = PTR_ERR(csra66x0->regmap);
|
|
|
+ dev_err(&client_i2c->dev,
|
|
|
+ "%s %d: Failed to allocate register map for I2C device: %d\n",
|
|
|
+ __func__, __LINE__, ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ i2c_set_clientdata(client_i2c, csra66x0);
|
|
|
+
|
|
|
+ /* get data from device tree */
|
|
|
+ if (client_i2c->dev.of_node) {
|
|
|
+ /* cluster of multiple devices */
|
|
|
+ ret = of_property_read_u32(
|
|
|
+ client_i2c->dev.of_node, "qcom,csra-cluster",
|
|
|
+ &csra66x0->in_cluster);
|
|
|
+ if (ret) {
|
|
|
+ dev_info(&client_i2c->dev,
|
|
|
+ "%s: qcom,csra-cluster property not defined in DT\n", __func__);
|
|
|
+ csra66x0->in_cluster = 0;
|
|
|
+ }
|
|
|
+ /* master or slave device */
|
|
|
+ ret = of_property_read_u32(
|
|
|
+ client_i2c->dev.of_node, "qcom,csra-cluster-master",
|
|
|
+ &csra66x0->is_master);
|
|
|
+ if (ret) {
|
|
|
+ dev_info(&client_i2c->dev,
|
|
|
+ "%s: qcom,csra-cluster-master property not defined in DT\n", __func__);
|
|
|
+ csra66x0->is_master = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* gpio setup for vreg */
|
|
|
+ csra66x0->vreg_gpio = of_get_named_gpio(client_i2c->dev.of_node,
|
|
|
+ "qcom,csra-vreg-en-gpio", 0);
|
|
|
+ if (!gpio_is_valid(csra66x0->vreg_gpio)) {
|
|
|
+ dev_err(&client_i2c->dev, "%s: %s property is not found %d\n",
|
|
|
+ __func__, "qcom,csra-vreg-en-gpio",
|
|
|
+ csra66x0->vreg_gpio);
|
|
|
+ return -ENODEV;
|
|
|
+ }
|
|
|
+ dev_dbg(&client_i2c->dev, "%s: vreg_en gpio %d\n", __func__,
|
|
|
+ csra66x0->vreg_gpio);
|
|
|
+ ret = gpio_request(csra66x0->vreg_gpio, dev_name(&client_i2c->dev));
|
|
|
+ if (ret) {
|
|
|
+ if (ret == -EBUSY) {
|
|
|
+ /* GPIO was already requested */
|
|
|
+ dev_dbg(&client_i2c->dev,
|
|
|
+ "%s: gpio %d is already set\n",
|
|
|
+ __func__, csra66x0->vreg_gpio);
|
|
|
+ } else {
|
|
|
+ dev_err(&client_i2c->dev, "%s: Failed to request gpio %d, err: %d\n",
|
|
|
+ __func__, csra66x0->vreg_gpio, ret);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ gpio_direction_output(csra66x0->vreg_gpio, 1);
|
|
|
+ gpio_set_value(csra66x0->vreg_gpio, 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ /* register interrupt handle */
|
|
|
+ if (client_i2c->irq) {
|
|
|
+ csra66x0->irq = client_i2c->irq;
|
|
|
+ /* interrupt polarity */
|
|
|
+ ret = of_property_read_u32(
|
|
|
+ client_i2c->dev.of_node, "irq-active-low",
|
|
|
+ &csra66x0->irq_active_low);
|
|
|
+ if (ret) {
|
|
|
+ dev_info(&client_i2c->dev,
|
|
|
+ "%s: irq-active-low property not defined in DT\n", __func__);
|
|
|
+ csra66x0->irq_active_low = 0;
|
|
|
+ }
|
|
|
+ if (csra66x0->irq_active_low)
|
|
|
+ irq_trigger = IRQF_TRIGGER_LOW;
|
|
|
+ else
|
|
|
+ irq_trigger = IRQF_TRIGGER_HIGH;
|
|
|
+
|
|
|
+ ret = devm_request_threaded_irq(&client_i2c->dev,
|
|
|
+ csra66x0->irq, NULL, csra66x0_irq,
|
|
|
+ irq_trigger | IRQF_ONESHOT,
|
|
|
+ "csra66x0_irq", csra66x0);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(&client_i2c->dev,
|
|
|
+ "%s: Failed to request IRQ %d: %d\n",
|
|
|
+ __func__, csra66x0->irq, ret);
|
|
|
+ csra66x0->irq = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* register codec */
|
|
|
+ ret = snd_soc_register_codec(&client_i2c->dev,
|
|
|
+ &soc_codec_drv_csra66x0, NULL, 0);
|
|
|
+ if (ret != 0) {
|
|
|
+ dev_err(&client_i2c->dev, "%s %d: Failed to register CODEC: %d\n",
|
|
|
+ __func__, __LINE__, ret);
|
|
|
+ if (gpio_is_valid(csra66x0->vreg_gpio)) {
|
|
|
+ gpio_set_value(csra66x0->vreg_gpio, 0);
|
|
|
+ gpio_free(csra66x0->vreg_gpio);
|
|
|
+ }
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int csra66x0_i2c_remove(struct i2c_client *i2c_client)
|
|
|
+{
|
|
|
+ struct csra66x0_priv *csra66x0 = i2c_get_clientdata(i2c_client);
|
|
|
+
|
|
|
+ if (csra66x0) {
|
|
|
+ if (gpio_is_valid(csra66x0->vreg_gpio)) {
|
|
|
+ gpio_set_value(csra66x0->vreg_gpio, 0);
|
|
|
+ gpio_free(csra66x0->vreg_gpio);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ snd_soc_unregister_codec(&i2c_client->dev);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static const struct i2c_device_id csra66x0_i2c_id[] = {
|
|
|
+ { "csra66x0", 0},
|
|
|
+ { }
|
|
|
+};
|
|
|
+MODULE_DEVICE_TABLE(i2c, csra66x0_i2c_id);
|
|
|
+
|
|
|
+static struct i2c_driver csra66x0_i2c_driver = {
|
|
|
+ .probe = csra66x0_i2c_probe,
|
|
|
+ .remove = csra66x0_i2c_remove,
|
|
|
+ .id_table = csra66x0_i2c_id,
|
|
|
+ .driver = {
|
|
|
+ .name = "csra66x0",
|
|
|
+ .owner = THIS_MODULE,
|
|
|
+ .of_match_table = csra66x0_of_match
|
|
|
+ },
|
|
|
+};
|
|
|
+#endif
|
|
|
+
|
|
|
+static int __init csra66x0_codec_init(void)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+#if IS_ENABLED(CONFIG_I2C)
|
|
|
+ ret = i2c_add_driver(&csra66x0_i2c_driver);
|
|
|
+ if (ret != 0)
|
|
|
+ pr_err("%s: Failed to register CSRA66X0 I2C driver, ret = %d\n",
|
|
|
+ __func__, ret);
|
|
|
+#endif
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+module_init(csra66x0_codec_init);
|
|
|
+
|
|
|
+static void __exit csra66x0_codec_exit(void)
|
|
|
+{
|
|
|
+#if IS_ENABLED(CONFIG_I2C)
|
|
|
+ i2c_del_driver(&csra66x0_i2c_driver);
|
|
|
+#endif
|
|
|
+}
|
|
|
+module_exit(csra66x0_codec_exit);
|
|
|
+
|
|
|
+MODULE_DESCRIPTION("CSRA66X0 Codec driver");
|
|
|
+MODULE_LICENSE("GPL v2");
|