On 06/11/19 19:49, Thomas Preston wrote: > Add an I2C based codec driver for ST TDA7802 amplifier. By default, the > amplifier supports 4 audio channels but can support up to 16 with > multiple devices. Input is configurable for I2S or TDM. > > The unified device properties API is used to get board-specific config from > device tree / ACPI. > > Signed-off-by: Thomas Preston <thomas.preston@xxxxxxxxxxxxxxx> > Cc: Patrick Glaser <pglaser@xxxxxxxxx> > Cc: Rob Duncan <rduncan@xxxxxxxxx> > Cc: Nate Case <ncase@xxxxxxxxx> > --- > sound/soc/codecs/Kconfig | 6 + > sound/soc/codecs/Makefile | 2 + > sound/soc/codecs/tda7802.c | 580 +++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 588 insertions(+) > create mode 100644 sound/soc/codecs/tda7802.c > > diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig > index e730d47ac85b..1d30c2333cb1 100644 > --- a/sound/soc/codecs/Kconfig > +++ b/sound/soc/codecs/Kconfig > @@ -176,6 +176,7 @@ config SND_SOC_ALL_CODECS > select SND_SOC_TAS5720 if I2C > select SND_SOC_TAS6424 if I2C > select SND_SOC_TDA7419 if I2C > + select SND_SOC_TDA7802 if I2C > select SND_SOC_TFA9879 if I2C > select SND_SOC_TLV320AIC23_I2C if I2C > select SND_SOC_TLV320AIC23_SPI if SPI_MASTER > @@ -1078,6 +1079,11 @@ config SND_SOC_TDA7419 > depends on I2C > select REGMAP_I2C > > +config SND_SOC_TDA7802 > + tristate "ST TDA7802 audio processor" > + depends on I2C > + select REGMAP_I2C > + > config SND_SOC_TFA9879 > tristate "NXP Semiconductors TFA9879 amplifier" > depends on I2C > diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile > index aa7720a7a0aa..fc3fc672bc4b 100644 > --- a/sound/soc/codecs/Makefile > +++ b/sound/soc/codecs/Makefile > @@ -187,6 +187,7 @@ snd-soc-tas571x-objs := tas571x.o > snd-soc-tas5720-objs := tas5720.o > snd-soc-tas6424-objs := tas6424.o > snd-soc-tda7419-objs := tda7419.o > +snd-soc-tda7802-objs := tda7802.o > snd-soc-tfa9879-objs := tfa9879.o > snd-soc-tlv320aic23-objs := tlv320aic23.o > snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o > @@ -460,6 +461,7 @@ obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o > obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o > obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o > obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o > +obj-$(CONFIG_SND_SOC_TDA7802) += snd-soc-tda7802.o > obj-$(CONFIG_SND_SOC_TFA9879) += snd-soc-tfa9879.o > obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o > obj-$(CONFIG_SND_SOC_TLV320AIC23_I2C) += snd-soc-tlv320aic23-i2c.o > diff --git a/sound/soc/codecs/tda7802.c b/sound/soc/codecs/tda7802.c > new file mode 100644 > index 000000000000..38ca52de85f0 > --- /dev/null > +++ b/sound/soc/codecs/tda7802.c > @@ -0,0 +1,580 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * tda7802.c -- codec driver for ST TDA7802 > + * > + * Copyright (C) 2016-2019 Tesla Motors, Inc. > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License as > + * published by the Free Software Foundation; either version 2 of the > + * License, or (at your option) any later version. > + * > + */ > + > +#include <linux/regulator/consumer.h> > +#include <linux/of_device.h> > +#include <linux/module.h> > +#include <linux/i2c.h> > +#include <linux/delay.h> > +#include <linux/regmap.h> > +#include <linux/string.h> > +#include <sound/soc.h> > + > +enum tda7802_type { > + tda7802_base, > +}; > + > +#define I2C_BUS 4 > + > +#define CHANNELS_PER_AMP 4 > +#define MAX_NUM_AMPS 4 > + > +#define ENABLE_DELAY_MS 10 > + > +#define TDA7802_IB0 0x00 > +#define TDA7802_IB1 0x01 > +#define TDA7802_IB2 0x02 > +#define TDA7802_IB3 0x03 > +#define TDA7802_IB4 0x04 > +#define TDA7802_IB5 0x05 > + > +#define TDA7802_DB0 0x10 > +#define TDA7802_DB1 0x11 > +#define TDA7802_DB2 0x12 > +#define TDA7802_DB3 0x13 > +#define TDA7802_DB4 0x14 > +#define TDA7802_DB5 0x15 > + > +#define IB0_CH4_TRISTATE_ON (1 << 7) > +#define IB0_CH3_TRISTATE_ON (1 << 6) > +#define IB0_CH2_TRISTATE_ON (1 << 5) > +#define IB0_CH1_TRISTATE_ON (1 << 4) > +#define IB0_CH4_HIGH_EFF (0 << 3) > +#define IB0_CH4_CLASS_AB (1 << 3) > +#define IB0_CH3_HIGH_EFF (0 << 2) > +#define IB0_CH3_CLASS_AB (1 << 2) > +#define IB0_CH2_HIGH_EFF (0 << 0) > +#define IB0_CH2_CLASS_AB (1 << 1) > +#define IB0_CH1_HIGH_EFF (0 << 0) > +#define IB0_CH1_CLASS_AB (1 << 0) > + > +#define IB1_REAR_IMPEDANCE_2_OHM (0 << 7) > +#define IB1_REAR_IMPEDANCE_4_OHM (1 << 7) > +#define IB1_LONG_DIAG_TIMING_x1 (0 << 5) > +#define IB1_LONG_DIAG_TIMING_x2 (1 << 5) > +#define IB1_LONG_DIAG_TIMING_x4 (2 << 5) > +#define IB1_LONG_DIAG_TIMING_x8 (3 << 5) > +#define IB1_GAIN_CH13_GV1 (0 << 3) > +#define IB1_GAIN_CH13_GV2 (1 << 3) > +#define IB1_GAIN_CH13_GV3 (2 << 3) > +#define IB1_GAIN_CH13_GV4 (3 << 3) > +#define IB1_GAIN_CH24_GV1 (0 << 1) > +#define IB1_GAIN_CH24_GV2 (1 << 1) > +#define IB1_GAIN_CH24_GV3 (2 << 1) > +#define IB1_GAIN_CH24_GV4 (3 << 1) > +#define IB1_DIGITAL_GAIN_BOOST (1 << 0) > + > +#define IB2_MUTE_TIME_1_MS (0 << 5) > +#define IB2_MUTE_TIME_5_MS (1 << 5) > +#define IB2_MUTE_TIME_11_MS (2 << 5) > +#define IB2_MUTE_TIME_23_MS (3 << 5) > +#define IB2_MUTE_TIME_46_MS (4 << 5) > +#define IB2_MUTE_TIME_92_MS (5 << 5) > +#define IB2_MUTE_TIME_185_MS (6 << 5) > +#define IB2_MUTE_TIME_371_MS (7 << 5) > +#define IB2_CH13_UNMUTED (1 << 4) > +#define IB2_CH24_UNMUTED (1 << 3) > +#define IB2_DIGITAL_MUTE_DISABLED (1 << 2) > +#define IB2_AUTOMUTE_THRESHOLD_5V3 (0 << 1) > +#define IB2_AUTOMUTE_THRESHOLD_7V3 (1 << 1) > +#define IB2_FRONT_IMPEDANCE_2_OHM (0 << 0) > +#define IB2_FRONT_IMPEDANCE_4_OHM (1 << 0) > + > +#define IB3_SAMPLE_RATE_44_KHZ (0 << 6) > +#define IB3_SAMPLE_RATE_48_KHZ (1 << 6) > +#define IB3_SAMPLE_RATE_96_KHZ (2 << 6) > +#define IB3_SAMPLE_RATE_192_KHZ (3 << 6) > +#define IB3_FORMAT_I2S (0 << 3) > +#define IB3_FORMAT_TDM4 (1 << 3) > +#define IB3_FORMAT_TDM8_DEV1 (2 << 3) > +#define IB3_FORMAT_TDM8_DEV2 (3 << 3) > +#define IB3_FORMAT_TDM16_DEV1 (4 << 3) > +#define IB3_FORMAT_TDM16_DEV2 (5 << 3) > +#define IB3_FORMAT_TDM16_DEV3 (6 << 3) > +#define IB3_FORMAT_TDM16_DEV4 (7 << 3) > +#define IB3_I2S_FRAME_PERIOD_64 (0 << 2) > +#define IB3_I2S_FRAME_PERIOD_50 (1 << 2) > +#define IB3_PLL_CLOCK_DITHER_ENABLE (1 << 1) > +#define IB3_HIGH_PASS_FILTER_ENABLE (1 << 0) More unused defines - do you want them? > + > +static const u8 IB3_FORMAT[4][4] = { > + /* 1x amp, 4 channels */ > + { IB3_FORMAT_TDM4 }, > + /* 2x amp, 8 channels */ > + { IB3_FORMAT_TDM8_DEV1, > + IB3_FORMAT_TDM8_DEV2 }, > + /* 3x amp not supported */ > + { }, > + /* 4x amp, 16 channels */ > + { IB3_FORMAT_TDM16_DEV1, > + IB3_FORMAT_TDM16_DEV2, > + IB3_FORMAT_TDM16_DEV3, > + IB3_FORMAT_TDM16_DEV4 }, > +}; > + > +#define IB4_NOISE_GATE_DISABLE (1 << 7) > +#define IB4_SHORT_FAULT_ON_CDDIAG_YES (0 << 6) > +#define IB4_SHORT_FAULT_ON_CDDIAG_NO (1 << 6) > +#define IB4_OFFSET_ON_CDDIAG_YES (0 << 5) > +#define IB4_OFFSET_ON_CDDIAG_NO (1 << 5) > +#define IB4_AC_DIAG_CURRENT_THRESHOLD_HIGH (0 << 4) > +#define IB4_AC_DIAG_CURRENT_THRESHOLD_LOW (1 << 4) > +#define IB4_AC_DIAG_ENABLE (1 << 3) > +#define IB4_CH13_DIAG_MODE_SPEAKER (0 << 2) > +#define IB4_CH13_DIAG_MODE_BOOSTER (1 << 2) > +#define IB4_CH24_DIAG_MODE_SPEAKER (0 << 1) > +#define IB4_CH24_DIAG_MODE_BOOSTER (1 << 1) > +#define IB4_DIAG_MODE_ENABLE (1 << 0) > + > +#define IB5_TEMP_WARNING_ON_CDDIAG_TW1 (0 << 5) > +#define IB5_TEMP_WARNING_ON_CDDIAG_TW2 (1 << 5) > +#define IB5_TEMP_WARNING_ON_CDDIAG_TW3 (2 << 5) > +#define IB5_TEMP_WARNING_ON_CDDIAG_TW4 (3 << 5) > +#define IB5_TEMP_WARNING_ON_CDDIAG_NONE (4 << 5) > +#define IB5_CLIP_DETECT_FRONT_1PC (0 << 3) > +#define IB5_CLIP_DETECT_FRONT_5PC (1 << 3) > +#define IB5_CLIP_DETECT_FRONT_10PC (2 << 3) > +#define IB5_CLIP_DETECT_FRONT_NONE (3 << 3) > +#define IB5_CLIP_DETECT_REAR_1PC (0 << 1) > +#define IB5_CLIP_DETECT_REAR_5PC (1 << 1) > +#define IB5_CLIP_DETECT_REAR_10PC (2 << 1) > +#define IB5_CLIP_DETECT_REAR_NONE (3 << 1) > +#define IB5_AMPLIFIER_ON (1 << 0) > + > +#define DB0_STARTUP_DIAG_STATUS 0x40 > + > +#define DUMP_NUM_BLOCK 6 > +#define DUMP_NUM_REGS (DUMP_NUM_BLOCK * 2) > + > +struct tda7802_priv { > + struct i2c_client *i2c; > + struct regmap *regmap; > + struct regulator *enable_reg; > + const char *diag_mode_ch13, *diag_mode_ch24; > + u8 gain_ch13, gain_ch24; > +}; > + > +static bool tda7802_readable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case TDA7802_IB0 ... TDA7802_IB5:` Oh, I didn't know it is possible to use such `...` syntax in switch cases. This is interesting! > + case TDA7802_DB0 ... TDA7802_DB5: > + return true; > + default: > + return false; > + } > +} > + > +static bool tda7802_writeable_reg(struct device *dev, unsigned int reg) > +{ > + switch (reg) { > + case TDA7802_IB0 ... TDA7802_IB5: > + return true; > + default: > + return false; > + } > +} > + > +static const struct regmap_config tda7802_regmap_config = { > + .val_bits = 8, > + .reg_bits = 8, > + .max_register = TDA7802_DB5, > + .use_single_read = 1, > + .use_single_write = 1, > + > + .readable_reg = tda7802_readable_reg, > + .writeable_reg = tda7802_writeable_reg, Maybe add default values? > +}; > + > +#ifdef DEBUG > +static int tda7802_dump_regs(struct tda7802_priv *tda7802) > +{ > + u8 regs[DUMP_NUM_REGS]; > + const char *prefix; > + int err = 0; > + > + prefix = kasprintf(GFP_KERNEL, "%s ", dev_name(&tda7802->i2c->dev)); > + if (!prefix) > + return -ENOMEM; > + > + err = regmap_bulk_read(tda7802->regmap, TDA7802_IB0, ®s[0], > + DUMP_NUM_BLOCK); > + if (err < 0) > + goto cleanup_dump_regs; > + > + err = regmap_bulk_read(tda7802->regmap, TDA7802_DB0, > + ®s[DUMP_NUM_BLOCK], DUMP_NUM_BLOCK); > + if (err < 0) > + goto cleanup_dump_regs; > + > + print_hex_dump_bytes(prefix, DUMP_PREFIX_NONE, ®s[0], DUMP_NUM_REGS); > + > +cleanup_dump_regs: > + kfree(prefix); > + return err; > +} > +#else > +static int tda7802_dump_regs(struct tda7802_priv *tda7802) > +{ > + return 0; > +} > +#endif > + > +static int tda7802_dai_startup(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct tda7802_priv *tda7802 = > + snd_soc_component_get_drvdata(dai->component); > + struct device *dev = dai->dev; > + u8 data[6] = { 0 }; > + int err; > + > + dev_dbg(dev, "%s\n", __func__); > + > + /* enable the device */ > + err = regulator_enable(tda7802->enable_reg); > + if (err < 0) { > + dev_err(dev, "Could not enable (startup).\n"); > + return err; > + } > + msleep(ENABLE_DELAY_MS); > + > + /* All channels out of tri-state mode, all channels in Standard Class > + * AB mode (not High-efficiency) > + */ > + data[0] = IB0_CH4_CLASS_AB | IB0_CH3_CLASS_AB | IB0_CH2_CLASS_AB | > + IB0_CH1_CLASS_AB; > + > + /* Rear channel load impedance set to 2-Ohm, default diagnostic timing, > + * GV1 gain on all channels (default), no digital gain increase > + */ > + data[1] = IB1_REAR_IMPEDANCE_2_OHM | IB1_LONG_DIAG_TIMING_x1; > + switch (tda7802->gain_ch13) { > + case 4: > + data[1] |= IB1_GAIN_CH13_GV4; > + break; > + case 3: > + data[1] |= IB1_GAIN_CH13_GV3; > + break; > + case 2: > + data[1] |= IB1_GAIN_CH13_GV2; > + break; > + default: > + dev_err(dev, "Unknown gain for channel 1,3 %d, setting to 1\n", > + tda7802->gain_ch13); > + case 1: > + data[1] |= IB1_GAIN_CH13_GV1; > + break; > + } > + switch (tda7802->gain_ch24) { > + case 4: > + data[1] |= IB1_GAIN_CH24_GV4; > + break; > + case 3: > + data[1] |= IB1_GAIN_CH24_GV3; > + break; > + case 2: > + data[1] |= IB1_GAIN_CH24_GV2; > + break; > + default: > + dev_err(dev, "Unknown gain for channel 2,4 %d, setting to 1\n", > + tda7802->gain_ch24); > + case 1: > + data[1] |= IB1_GAIN_CH24_GV1; > + break; > + } > + > + /* Mute timing 1.45ms, all channels un-muted, digital mute enabled, > + * 5.3V undervoltage threshold, front-channel load impedance set to > + * 2-Ohms > + */ > + data[2] = IB2_MUTE_TIME_1_MS | IB2_CH13_UNMUTED | IB2_CH24_UNMUTED | > + IB2_AUTOMUTE_THRESHOLD_5V3 | IB2_FRONT_IMPEDANCE_2_OHM> + > + /* Don't set IB3 here, we should set it in set_tdm_slot > + * data[3] = 0; > + */ > + > + /* Noise gating enabled, short and offset info on CD-Diag (fault) pin, > + * diagnostics disabled. Default to speaker. > + */ > + if (strcmp(tda7802->diag_mode_ch13, "Booster") == 0) > + data[4] |= IB4_CH13_DIAG_MODE_BOOSTER; > + else > + data[4] |= IB4_CH13_DIAG_MODE_SPEAKER; > + > + if (strcmp(tda7802->diag_mode_ch24, "Booster") == 0) > + data[4] |= IB4_CH24_DIAG_MODE_BOOSTER; > + else > + data[4] |= IB4_CH24_DIAG_MODE_SPEAKER; > + > + /* Temperature warning on diag pin set to TW1 (highest setting), clip > + * detection set to 1% on all channels > + */ > + data[5] = IB5_TEMP_WARNING_ON_CDDIAG_TW1 | IB5_CLIP_DETECT_FRONT_1PC | > + IB5_CLIP_DETECT_REAR_1PC, Would be nicer to have `;` in the end of the line, instead of `,` > + > + err = regmap_bulk_write(tda7802->regmap, TDA7802_IB0, data, > + ARRAY_SIZE(data)); > + if (err < 0) { > + dev_err(dev, "Cannot configure amp %d\n", err); > + return err; > + } > + > + return 0; > +} > + > +static void tda7802_dai_shutdown(struct snd_pcm_substream *substream, > + struct snd_soc_dai *dai) > +{ > + struct tda7802_priv *tda7802 = > + snd_soc_component_get_drvdata(dai->component); > + struct device *dev = dai->dev; > + int err; > + > + dev_dbg(dev, "%s\n", __func__); > + > + err = regulator_disable(tda7802->enable_reg); > + if (err < 0) > + dev_err(dev, "Could not disable (shutdown)\n"); > +} > + > +static int tda7802_digital_mute(struct snd_soc_dai *dai, int mute) > +{ > + struct device *dev = dai->dev; > + int err; > + > + dev_dbg(dev, "%s mute=%d\n", __func__, mute); > + > + if (mute) { > + /* digital mute */ > + err = snd_soc_component_update_bits(dai->component, TDA7802_IB2, > + IB2_DIGITAL_MUTE_DISABLED, > + ~IB2_DIGITAL_MUTE_DISABLED); In such situations, the last argument is usually `0`, not `~(mask)` > + if (err < 0) > + dev_err(dev, "Cannot mute amp %d\n", err); I think you want to `return err` here, right? > + > + /* amp off */ > + err = snd_soc_component_update_bits(dai->component, TDA7802_IB5, > + IB5_AMPLIFIER_ON, ~IB5_AMPLIFIER_ON); > + if (err < 0) > + dev_err(dev, "Cannot amp off %d\n", err); > + > + } else { > + /* amp on */ > + err = snd_soc_component_update_bits(dai->component, TDA7802_IB5, > + IB5_AMPLIFIER_ON, IB5_AMPLIFIER_ON); > + if (err < 0) > + dev_err(dev, "Cannot amp on %d\n", err); > + > + /* digital unmute */ > + err = snd_soc_component_update_bits(dai->component, TDA7802_IB2, > + IB2_DIGITAL_MUTE_DISABLED, > + IB2_DIGITAL_MUTE_DISABLED); > + if (err < 0) > + dev_err(dev, "Cannot unmute amp %d\n", err); > + } > + > + return 0; > +} > + > +static int tda7802_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, > + unsigned int rx_mask, int slots, int slot_width) > +{ > + struct tda7802_priv *tda7802 = > + snd_soc_component_get_drvdata(dai->component); > + int width_index, slot_index, ret; > + struct device *dev = dai->dev; > + u8 data; > + > + if (!(slots == 4 || slots == 8 || slots == 16)) { > + dev_err(dev, "Failed to set %d slots, supported: 4, 8, 16\n", > + slots); > + return -ENOTSUPP; > + } > + width_index = (slots / 4) - 1; > + > + switch (tx_mask) { > + case 0x000f: > + slot_index = 0; > + break; > + case 0x00f0: > + slot_index = 1; > + break; > + case 0x0f00: > + slot_index = 2; > + break; > + case 0xf000: > + slot_index = 3; > + break; > + default: > + /* must be contigious nibble */ > + dev_err(dev, "Failed to set tx_mask %08x\n", tx_mask); > + return -ENOTSUPP; > + } > + > + /* 48kHz sample rate, TDM configuration, 64-bit I2S frame period, PLL > + * clock dither disabled, high-pass filter enabled (blocks DC output) > + */ > + data = IB3_SAMPLE_RATE_48_KHZ | IB3_FORMAT[width_index][slot_index] | > + IB3_I2S_FRAME_PERIOD_64 | IB3_HIGH_PASS_FILTER_ENABLE; > + ret = snd_soc_component_write(dai->component, TDA7802_IB3, data); > + if (ret < 0) { > + dev_err(dev, "Failed to write IB3 config %d\n", ret); > + return ret; > + } > + > + tda7802_dump_regs(tda7802); > + > + return 0; > +} > + > +static const struct snd_soc_dai_ops tda7802_dai_ops = { > + .startup = tda7802_dai_startup, > + .shutdown = tda7802_dai_shutdown, > + .digital_mute = tda7802_digital_mute, > + .set_tdm_slot = tda7802_set_tdm_slot, > +}; > + > +static struct snd_soc_dai_driver tda7802_dai_driver = { > + .name = "tda7802", > + .playback = { > + .stream_name = "Playback", > + .channels_min = 4, > + .channels_max = 4, > + .rates = SNDRV_PCM_RATE_48000, > + .formats = SNDRV_PCM_FMTBIT_S32_LE, > + }, > + .ops = &tda7802_dai_ops, > +}; > + > +/* read device tree or ACPI properties from device */ > +static int tda7802_read_properties(struct tda7802_priv *tda7802) > +{ > + struct device *dev = &tda7802->i2c->dev; > + int err = 0; > + > + tda7802->enable_reg = devm_regulator_get(dev, "enable"); > + if (IS_ERR(tda7802->enable_reg)) { > + dev_err(dev, "Failed to get enable regulator\n"); > + return PTR_ERR(tda7802->enable_reg); > + } > + > + err = device_property_read_u8(dev, "st,gain-ch13", &tda7802->gain_ch13); > + if (err < 0) > + dev_err(dev, "Failed to read gain, channel 1,3 %d\n", err); > + > + err = device_property_read_u8(dev, "st,gain-ch24", &tda7802->gain_ch24); > + if (err < 0) > + dev_err(dev, "Failed to read gain, channel 2,4 %d\n", err); > + > + err = device_property_read_string(dev, "st,diagnostic-mode-ch13", > + &tda7802->diag_mode_ch13); Why is diagnostic mode in dtb? Looks like it should be configurable on the run-time, so a module parameter would be a more proper place for it > + if (err < 0) > + dev_err(dev, "Failed to read diagnostic mode, channel 1,3 %d\n", > + err); > + > + err = device_property_read_string(dev, "st,diagnostic-mode-ch24", > + &tda7802->diag_mode_ch24); > + if (err < 0) > + dev_err(dev, "Failed to read diagnostic mode, channel 2,4 %d\n", > + err); > + > + return err; > +} > + > +static const struct snd_soc_component_driver tda7802_component_driver; > + > +static int tda7802_i2c_probe(struct i2c_client *i2c, > + const struct i2c_device_id *id) > +{ > + struct device *dev = &i2c->dev; > + struct tda7802_priv *tda7802; > + int status, err, err2; > + > + dev_dbg(dev, "%s addr=0x%02hx, id %p\n", __func__, i2c->addr, id); > + > + tda7802 = devm_kmalloc(dev, sizeof(*tda7802), GFP_KERNEL); > + if (!tda7802) > + return -ENOMEM; > + > + i2c_set_clientdata(i2c, tda7802); > + tda7802->i2c = i2c; > + > + err = tda7802_read_properties(tda7802); > + if (err < 0) > + return err; > + > + tda7802->regmap = devm_regmap_init_i2c(tda7802->i2c, > + &tda7802_regmap_config); > + if (IS_ERR(tda7802->regmap)) > + return PTR_ERR(tda7802->regmap); > + > + /* Enable and verify the connection */ > + err = regulator_enable(tda7802->enable_reg); > + if (err < 0) { > + dev_err(dev, "Failed to enable (init)\n"); > + return err; > + } > + msleep(ENABLE_DELAY_MS); > + > + err = regmap_read(tda7802->regmap, TDA7802_DB1, &status); > + > + /* always disable the regulator, even if the read fails */ > + err2 = regulator_disable(tda7802->enable_reg); > + if (err2 < 0) { > + dev_err(dev, "Failed to disable (init)\n"); > + return err2; > + } > + /* now check for regmap_read errors */ > + if (err < 0) { > + dev_err(dev, "Could not read from device\n"); > + return -ENODEV; > + } > + > + err = devm_snd_soc_register_component(dev, &tda7802_component_driver, > + &tda7802_dai_driver, 1); > + if (err < 0) > + dev_err(dev, "Failed to register codec: %d\n", err); > + return err; > +} > + > +#ifdef CONFIG_OF > +static const struct of_device_id tda7802_of_match[] = { > + { .compatible = "st,tda7802" }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, tda7802_of_match); > +#endif > + > +static const struct i2c_device_id tda7802_i2c_id[] = { > + { "tda7802", tda7802_base }, > + {}, > +}; > +MODULE_DEVICE_TABLE(i2c, tda7802_i2c_id); > + > +static struct i2c_driver tda7802_i2c_driver = { > + .driver = { > + .name = "tda7802-codec", > + .owner = THIS_MODULE, > + .of_match_table = of_match_ptr(tda7802_of_match), > + }, > + .probe = tda7802_i2c_probe, > + .id_table = tda7802_i2c_id, > +}; > +module_i2c_driver(tda7802_i2c_driver); > + > +MODULE_DESCRIPTION("ASoC ST TDA7802 driver"); > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("Rob Duncan <rduncan@xxxxxxxxx>"); > +MODULE_AUTHOR("Thomas Preston <thomas.preston@xxxxxxxxxxxxxxx>"); >