Received: by 2002:a25:ab43:0:0:0:0:0 with SMTP id u61csp4693738ybi; Tue, 11 Jun 2019 10:51:28 -0700 (PDT) X-Google-Smtp-Source: APXvYqyvkyFEJMDjBFpg1r5qDLkzGVZFb5ojwHlSQRl+YacnC08yu+JuufKtlPtStnpR4XQmZm/d X-Received: by 2002:a63:b1d:: with SMTP id 29mr21314377pgl.103.1560275488468; Tue, 11 Jun 2019 10:51:28 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1560275488; cv=none; d=google.com; s=arc-20160816; b=AQX9XLAV5GehWLE57x45J9KLRj/ah9Z28FjYF1tTBsmt/1+11CZ06ptX2F9qoWz68n Jtl9BbX9PoRTLf12j67Z0NPXZ5q5IT9YOWB1SJM+Riy3CmlxheEdD5B7AUMlv7q+EZmI xmi/KOjHiAaStW1wo6h4rZ71/t6563HtBPHtbymiO4UPHbWSHohKVO/Q9USOQGl+STyk PULp8NaOl3hX77mdFXar9FAorVvMSGdqbxMeqQntY9AxvjOqUvwwBGba/rFbUeSrgnIl Rjpa/PiG+7Hx5ev+KyGbwH+BDWviKfDlLrP4tteBWVIh79imUv+Q02JZMiUMZSbWiIYV t6aQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:to:from; bh=g8ZGgAhHEpSpU63A3oMRTl4ncXXCnA18zv0V23QUBVI=; b=NYJJ7TBDuwAObOHDgRucRZX1ByhNxKlXtAb9wcOIQiIVf5Ak54wL4U3Wt+nZT5ymmk lD0/Pxred9WUl2dPb+vb9Z/TwLcO0yWhljr9qDHQidUmWXec1N/18V3rsVVbh18nDXL3 DXe8NVhlfaE5fFCbSW0RgYF2CMX016iHTXGJQsg1iwLhXQHVMOzNk/t1IOiXrEaZgaSi zMnfbllKHtl07pF6i6pdkoxisGGAGw3PihkNmcDG5N7kWbpAC/rLrSfqkYY9wgHXd3Wn 8zFglRVUNhgLZWeMGhUyMzBVZvH3aXEd5RtAWrY+dWxKLsw+x6T+Ugp4lij70c2fWoTc pK3A== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=codethink.co.uk Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id e14si12592999pfd.141.2019.06.11.10.51.13; Tue, 11 Jun 2019 10:51:28 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=codethink.co.uk Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2406645AbfFKRuA (ORCPT + 99 others); Tue, 11 Jun 2019 13:50:00 -0400 Received: from imap1.codethink.co.uk ([176.9.8.82]:36929 "EHLO imap1.codethink.co.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2406594AbfFKRt5 (ORCPT ); Tue, 11 Jun 2019 13:49:57 -0400 Received: from [167.98.27.226] (helo=ct-lt-1124.office.codethink.co.uk) by imap1.codethink.co.uk with esmtpsa (Exim 4.84_2 #1 (Debian)) id 1hakte-0001PN-Oq; Tue, 11 Jun 2019 18:49:22 +0100 From: Thomas Preston To: Liam Girdwood , Mark Brown , Rob Herring , Mark Rutland , Jaroslav Kysela , Takashi Iwai , Charles Keepax , Jerome Brunet , Srinivas Kandagatla , Marco Felsch , Paul Cercueil , Kirill Marinushkin , Cheng-Yi Chiang , Kuninori Morimoto , Vinod Koul , Annaliese McDermond , Thomas Preston , alsa-devel@alsa-project.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH v1 2/4] ASoC: Add codec driver for ST TDA7802 Date: Tue, 11 Jun 2019 18:49:07 +0100 Message-Id: <20190611174909.12162-3-thomas.preston@codethink.co.uk> X-Mailer: git-send-email 2.11.0 In-Reply-To: <20190611174909.12162-1-thomas.preston@codethink.co.uk> References: <20190611174909.12162-1-thomas.preston@codethink.co.uk> Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 Cc: Patrick Glaser Cc: Rob Duncan Cc: Nate Case --- 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 +#include +#include +#include +#include +#include +#include +#include + +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) + +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: + 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, +}; + +#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, + + 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); + if (err < 0) + dev_err(dev, "Cannot mute amp %d\n", err); + + /* 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); + 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 "); +MODULE_AUTHOR("Thomas Preston "); -- 2.11.0