ed5c2f5fd1
The value returned by an i2c driver's remove function is mostly ignored. (Only an error message is printed if the value is non-zero that the error is ignored.) So change the prototype of the remove function to return no value. This way driver authors are not tempted to assume that passing an error to the upper layer is a good idea. All drivers are adapted accordingly. There is no intended change of behaviour, all callbacks were prepared to return 0 before. Reviewed-by: Peter Senna Tschudin <peter.senna@gmail.com> Reviewed-by: Jeremy Kerr <jk@codeconstruct.com.au> Reviewed-by: Benjamin Mugnier <benjamin.mugnier@foss.st.com> Reviewed-by: Javier Martinez Canillas <javierm@redhat.com> Reviewed-by: Crt Mori <cmo@melexis.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Acked-by: Marek Behún <kabel@kernel.org> # for leds-turris-omnia Acked-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Reviewed-by: Petr Machata <petrm@nvidia.com> # for mlxsw Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com> # for surface3_power Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> # for bmc150-accel-i2c + kxcjk-1013 Reviewed-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> # for media/* + staging/media/* Acked-by: Miguel Ojeda <ojeda@kernel.org> # for auxdisplay/ht16k33 + auxdisplay/lcd2s Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com> # for versaclock5 Reviewed-by: Ajay Gupta <ajayg@nvidia.com> # for ucsi_ccg Acked-by: Jonathan Cameron <Jonathan.Cameron@huawei.com> # for iio Acked-by: Peter Rosin <peda@axentia.se> # for i2c-mux-*, max9860 Acked-by: Adrien Grassein <adrien.grassein@gmail.com> # for lontium-lt8912b Reviewed-by: Jean Delvare <jdelvare@suse.de> # for hwmon, i2c-core and i2c/muxes Acked-by: Corey Minyard <cminyard@mvista.com> # for IPMI Reviewed-by: Vladimir Oltean <olteanv@gmail.com> Acked-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> Acked-by: Sebastian Reichel <sebastian.reichel@collabora.com> # for drivers/power Acked-by: Krzysztof Hałasa <khalasa@piap.pl> Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Wolfram Sang <wsa@kernel.org>
774 lines
20 KiB
C
774 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* tas2552.c - ALSA SoC Texas Instruments TAS2552 Mono Audio Amplifier
|
|
*
|
|
* Copyright (C) 2014 Texas Instruments Incorporated - https://www.ti.com
|
|
*
|
|
* Author: Dan Murphy <dmurphy@ti.com>
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/device.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/soc-dapm.h>
|
|
#include <sound/tlv.h>
|
|
#include <sound/tas2552-plat.h>
|
|
#include <dt-bindings/sound/tas2552.h>
|
|
|
|
#include "tas2552.h"
|
|
|
|
static const struct reg_default tas2552_reg_defs[] = {
|
|
{TAS2552_CFG_1, 0x22},
|
|
{TAS2552_CFG_3, 0x80},
|
|
{TAS2552_DOUT, 0x00},
|
|
{TAS2552_OUTPUT_DATA, 0xc0},
|
|
{TAS2552_PDM_CFG, 0x01},
|
|
{TAS2552_PGA_GAIN, 0x00},
|
|
{TAS2552_BOOST_APT_CTRL, 0x0f},
|
|
{TAS2552_RESERVED_0D, 0xbe},
|
|
{TAS2552_LIMIT_RATE_HYS, 0x08},
|
|
{TAS2552_CFG_2, 0xef},
|
|
{TAS2552_SER_CTRL_1, 0x00},
|
|
{TAS2552_SER_CTRL_2, 0x00},
|
|
{TAS2552_PLL_CTRL_1, 0x10},
|
|
{TAS2552_PLL_CTRL_2, 0x00},
|
|
{TAS2552_PLL_CTRL_3, 0x00},
|
|
{TAS2552_BTIP, 0x8f},
|
|
{TAS2552_BTS_CTRL, 0x80},
|
|
{TAS2552_LIMIT_RELEASE, 0x04},
|
|
{TAS2552_LIMIT_INT_COUNT, 0x00},
|
|
{TAS2552_EDGE_RATE_CTRL, 0x40},
|
|
{TAS2552_VBAT_DATA, 0x00},
|
|
};
|
|
|
|
#define TAS2552_NUM_SUPPLIES 3
|
|
static const char *tas2552_supply_names[TAS2552_NUM_SUPPLIES] = {
|
|
"vbat", /* vbat voltage */
|
|
"iovdd", /* I/O Voltage */
|
|
"avdd", /* Analog DAC Voltage */
|
|
};
|
|
|
|
struct tas2552_data {
|
|
struct snd_soc_component *component;
|
|
struct regmap *regmap;
|
|
struct i2c_client *tas2552_client;
|
|
struct regulator_bulk_data supplies[TAS2552_NUM_SUPPLIES];
|
|
struct gpio_desc *enable_gpio;
|
|
unsigned char regs[TAS2552_VBAT_DATA];
|
|
unsigned int pll_clkin;
|
|
int pll_clk_id;
|
|
unsigned int pdm_clk;
|
|
int pdm_clk_id;
|
|
|
|
unsigned int dai_fmt;
|
|
unsigned int tdm_delay;
|
|
};
|
|
|
|
static int tas2552_post_event(struct snd_soc_dapm_widget *w,
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
|
|
|
|
switch (event) {
|
|
case SND_SOC_DAPM_POST_PMU:
|
|
snd_soc_component_write(component, TAS2552_RESERVED_0D, 0xc0);
|
|
snd_soc_component_update_bits(component, TAS2552_LIMIT_RATE_HYS, (1 << 5),
|
|
(1 << 5));
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, 1, 0);
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS, 0);
|
|
break;
|
|
case SND_SOC_DAPM_POST_PMD:
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS,
|
|
TAS2552_SWS);
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, 1, 1);
|
|
snd_soc_component_update_bits(component, TAS2552_LIMIT_RATE_HYS, (1 << 5), 0);
|
|
snd_soc_component_write(component, TAS2552_RESERVED_0D, 0xbe);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Input mux controls */
|
|
static const char * const tas2552_input_texts[] = {
|
|
"Digital", "Analog" };
|
|
static SOC_ENUM_SINGLE_DECL(tas2552_input_mux_enum, TAS2552_CFG_3, 7,
|
|
tas2552_input_texts);
|
|
|
|
static const struct snd_kcontrol_new tas2552_input_mux_control =
|
|
SOC_DAPM_ENUM("Route", tas2552_input_mux_enum);
|
|
|
|
static const struct snd_soc_dapm_widget tas2552_dapm_widgets[] =
|
|
{
|
|
SND_SOC_DAPM_INPUT("IN"),
|
|
|
|
/* MUX Controls */
|
|
SND_SOC_DAPM_MUX("Input selection", SND_SOC_NOPM, 0, 0,
|
|
&tas2552_input_mux_control),
|
|
|
|
SND_SOC_DAPM_AIF_IN("DAC IN", "DAC Playback", 0, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_OUT_DRV("ClassD", TAS2552_CFG_2, 7, 0, NULL, 0),
|
|
SND_SOC_DAPM_SUPPLY("PLL", TAS2552_CFG_2, 3, 0, NULL, 0),
|
|
SND_SOC_DAPM_POST("Post Event", tas2552_post_event),
|
|
|
|
SND_SOC_DAPM_OUTPUT("OUT")
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route tas2552_audio_map[] = {
|
|
{"DAC", NULL, "DAC IN"},
|
|
{"Input selection", "Digital", "DAC"},
|
|
{"Input selection", "Analog", "IN"},
|
|
{"ClassD", NULL, "Input selection"},
|
|
{"OUT", NULL, "ClassD"},
|
|
{"ClassD", NULL, "PLL"},
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static void tas2552_sw_shutdown(struct tas2552_data *tas2552, int sw_shutdown)
|
|
{
|
|
u8 cfg1_reg = 0;
|
|
|
|
if (!tas2552->component)
|
|
return;
|
|
|
|
if (sw_shutdown)
|
|
cfg1_reg = TAS2552_SWS;
|
|
|
|
snd_soc_component_update_bits(tas2552->component, TAS2552_CFG_1, TAS2552_SWS,
|
|
cfg1_reg);
|
|
}
|
|
#endif
|
|
|
|
static int tas2552_setup_pll(struct snd_soc_component *component,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
|
bool bypass_pll = false;
|
|
unsigned int pll_clk = params_rate(params) * 512;
|
|
unsigned int pll_clkin = tas2552->pll_clkin;
|
|
u8 pll_enable;
|
|
|
|
if (!pll_clkin) {
|
|
if (tas2552->pll_clk_id != TAS2552_PLL_CLKIN_BCLK)
|
|
return -EINVAL;
|
|
|
|
pll_clkin = snd_soc_params_to_bclk(params);
|
|
pll_clkin += tas2552->tdm_delay;
|
|
}
|
|
|
|
pll_enable = snd_soc_component_read(component, TAS2552_CFG_2) & TAS2552_PLL_ENABLE;
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE, 0);
|
|
|
|
if (pll_clkin == pll_clk)
|
|
bypass_pll = true;
|
|
|
|
if (bypass_pll) {
|
|
/* By pass the PLL configuration */
|
|
snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_2,
|
|
TAS2552_PLL_BYPASS, TAS2552_PLL_BYPASS);
|
|
} else {
|
|
/* Fill in the PLL control registers for J & D
|
|
* pll_clk = (.5 * pll_clkin * J.D) / 2^p
|
|
* Need to fill in J and D here based on incoming freq
|
|
*/
|
|
unsigned int d, q, t;
|
|
u8 j;
|
|
u8 pll_sel = (tas2552->pll_clk_id << 3) & TAS2552_PLL_SRC_MASK;
|
|
u8 p = snd_soc_component_read(component, TAS2552_PLL_CTRL_1);
|
|
|
|
p = (p >> 7);
|
|
|
|
recalc:
|
|
t = (pll_clk * 2) << p;
|
|
j = t / pll_clkin;
|
|
d = t % pll_clkin;
|
|
t = pll_clkin / 10000;
|
|
q = d / (t + 1);
|
|
d = q + ((9999 - pll_clkin % 10000) * (d / t - q)) / 10000;
|
|
|
|
if (d && (pll_clkin < 512000 || pll_clkin > 9200000)) {
|
|
if (tas2552->pll_clk_id == TAS2552_PLL_CLKIN_BCLK) {
|
|
pll_clkin = 1800000;
|
|
pll_sel = (TAS2552_PLL_CLKIN_1_8_FIXED << 3) &
|
|
TAS2552_PLL_SRC_MASK;
|
|
} else {
|
|
pll_clkin = snd_soc_params_to_bclk(params);
|
|
pll_clkin += tas2552->tdm_delay;
|
|
pll_sel = (TAS2552_PLL_CLKIN_BCLK << 3) &
|
|
TAS2552_PLL_SRC_MASK;
|
|
}
|
|
goto recalc;
|
|
}
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_PLL_SRC_MASK,
|
|
pll_sel);
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_1,
|
|
TAS2552_PLL_J_MASK, j);
|
|
/* Will clear the PLL_BYPASS bit */
|
|
snd_soc_component_write(component, TAS2552_PLL_CTRL_2,
|
|
TAS2552_PLL_D_UPPER(d));
|
|
snd_soc_component_write(component, TAS2552_PLL_CTRL_3,
|
|
TAS2552_PLL_D_LOWER(d));
|
|
}
|
|
|
|
/* Restore PLL status */
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE,
|
|
pll_enable);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
|
int cpf;
|
|
u8 ser_ctrl1_reg, wclk_rate;
|
|
|
|
switch (params_width(params)) {
|
|
case 16:
|
|
ser_ctrl1_reg = TAS2552_WORDLENGTH_16BIT;
|
|
cpf = 32 + tas2552->tdm_delay;
|
|
break;
|
|
case 20:
|
|
ser_ctrl1_reg = TAS2552_WORDLENGTH_20BIT;
|
|
cpf = 64 + tas2552->tdm_delay;
|
|
break;
|
|
case 24:
|
|
ser_ctrl1_reg = TAS2552_WORDLENGTH_24BIT;
|
|
cpf = 64 + tas2552->tdm_delay;
|
|
break;
|
|
case 32:
|
|
ser_ctrl1_reg = TAS2552_WORDLENGTH_32BIT;
|
|
cpf = 64 + tas2552->tdm_delay;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "Not supported sample size: %d\n",
|
|
params_width(params));
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (cpf <= 32)
|
|
ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_32;
|
|
else if (cpf <= 64)
|
|
ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_64;
|
|
else if (cpf <= 128)
|
|
ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_128;
|
|
else
|
|
ser_ctrl1_reg |= TAS2552_CLKSPERFRAME_256;
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1,
|
|
TAS2552_WORDLENGTH_MASK | TAS2552_CLKSPERFRAME_MASK,
|
|
ser_ctrl1_reg);
|
|
|
|
switch (params_rate(params)) {
|
|
case 8000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_8KHZ;
|
|
break;
|
|
case 11025:
|
|
case 12000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_11_12KHZ;
|
|
break;
|
|
case 16000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_16KHZ;
|
|
break;
|
|
case 22050:
|
|
case 24000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_22_24KHZ;
|
|
break;
|
|
case 32000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_32KHZ;
|
|
break;
|
|
case 44100:
|
|
case 48000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_44_48KHZ;
|
|
break;
|
|
case 88200:
|
|
case 96000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_88_96KHZ;
|
|
break;
|
|
case 176400:
|
|
case 192000:
|
|
wclk_rate = TAS2552_WCLK_FREQ_176_192KHZ;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "Not supported sample rate: %d\n",
|
|
params_rate(params));
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_3, TAS2552_WCLK_FREQ_MASK,
|
|
wclk_rate);
|
|
|
|
return tas2552_setup_pll(component, params);
|
|
}
|
|
|
|
#define TAS2552_DAI_FMT_MASK (TAS2552_BCLKDIR | \
|
|
TAS2552_WCLKDIR | \
|
|
TAS2552_DATAFORMAT_MASK)
|
|
static int tas2552_prepare(struct snd_pcm_substream *substream,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
int delay = 0;
|
|
|
|
/* TDM slot selection only valid in DSP_A/_B mode */
|
|
if (tas2552->dai_fmt == SND_SOC_DAIFMT_DSP_A)
|
|
delay += (tas2552->tdm_delay + 1);
|
|
else if (tas2552->dai_fmt == SND_SOC_DAIFMT_DSP_B)
|
|
delay += tas2552->tdm_delay;
|
|
|
|
/* Configure data delay */
|
|
snd_soc_component_write(component, TAS2552_SER_CTRL_2, delay);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
|
u8 serial_format;
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
|
|
case SND_SOC_DAIFMT_CBC_CFC:
|
|
serial_format = 0x00;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBC_CFP:
|
|
serial_format = TAS2552_WCLKDIR;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBP_CFC:
|
|
serial_format = TAS2552_BCLKDIR;
|
|
break;
|
|
case SND_SOC_DAIFMT_CBP_CFP:
|
|
serial_format = (TAS2552_BCLKDIR | TAS2552_WCLKDIR);
|
|
break;
|
|
default:
|
|
dev_vdbg(component->dev, "DAI Format master is not found\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
|
|
SND_SOC_DAIFMT_INV_MASK)) {
|
|
case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
|
|
break;
|
|
case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF):
|
|
case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
|
|
serial_format |= TAS2552_DATAFORMAT_DSP;
|
|
break;
|
|
case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
|
|
serial_format |= TAS2552_DATAFORMAT_RIGHT_J;
|
|
break;
|
|
case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
|
|
serial_format |= TAS2552_DATAFORMAT_LEFT_J;
|
|
break;
|
|
default:
|
|
dev_vdbg(component->dev, "DAI Format is not found\n");
|
|
return -EINVAL;
|
|
}
|
|
tas2552->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1, TAS2552_DAI_FMT_MASK,
|
|
serial_format);
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
unsigned int freq, int dir)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
|
u8 reg, mask, val;
|
|
|
|
switch (clk_id) {
|
|
case TAS2552_PLL_CLKIN_MCLK:
|
|
case TAS2552_PLL_CLKIN_IVCLKIN:
|
|
if (freq < 512000 || freq > 24576000) {
|
|
/* out of range PLL_CLKIN, fall back to use BCLK */
|
|
dev_warn(component->dev, "Out of range PLL_CLKIN: %u\n",
|
|
freq);
|
|
clk_id = TAS2552_PLL_CLKIN_BCLK;
|
|
freq = 0;
|
|
}
|
|
fallthrough;
|
|
case TAS2552_PLL_CLKIN_BCLK:
|
|
case TAS2552_PLL_CLKIN_1_8_FIXED:
|
|
mask = TAS2552_PLL_SRC_MASK;
|
|
val = (clk_id << 3) & mask; /* bit 4:5 in the register */
|
|
reg = TAS2552_CFG_1;
|
|
tas2552->pll_clk_id = clk_id;
|
|
tas2552->pll_clkin = freq;
|
|
break;
|
|
case TAS2552_PDM_CLK_PLL:
|
|
case TAS2552_PDM_CLK_IVCLKIN:
|
|
case TAS2552_PDM_CLK_BCLK:
|
|
case TAS2552_PDM_CLK_MCLK:
|
|
mask = TAS2552_PDM_CLK_SEL_MASK;
|
|
val = (clk_id >> 1) & mask; /* bit 0:1 in the register */
|
|
reg = TAS2552_PDM_CFG;
|
|
tas2552->pdm_clk_id = clk_id;
|
|
tas2552->pdm_clk = freq;
|
|
break;
|
|
default:
|
|
dev_err(component->dev, "Invalid clk id: %d\n", clk_id);
|
|
return -EINVAL;
|
|
}
|
|
|
|
snd_soc_component_update_bits(component, reg, mask, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_set_dai_tdm_slot(struct snd_soc_dai *dai,
|
|
unsigned int tx_mask, unsigned int rx_mask,
|
|
int slots, int slot_width)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
unsigned int lsb;
|
|
|
|
if (unlikely(!tx_mask)) {
|
|
dev_err(component->dev, "tx masks need to be non 0\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* TDM based on DSP mode requires slots to be adjacent */
|
|
lsb = __ffs(tx_mask);
|
|
if ((lsb + 1) != __fls(tx_mask)) {
|
|
dev_err(component->dev, "Invalid mask, slots must be adjacent\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tas2552->tdm_delay = lsb * slot_width;
|
|
|
|
/* DOUT in high-impedance on inactive bit clocks */
|
|
snd_soc_component_update_bits(component, TAS2552_DOUT,
|
|
TAS2552_SDOUT_TRISTATE, TAS2552_SDOUT_TRISTATE);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_mute(struct snd_soc_dai *dai, int mute, int direction)
|
|
{
|
|
u8 cfg1_reg = 0;
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
if (mute)
|
|
cfg1_reg |= TAS2552_MUTE;
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_MUTE, cfg1_reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int tas2552_runtime_suspend(struct device *dev)
|
|
{
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(dev);
|
|
|
|
tas2552_sw_shutdown(tas2552, 1);
|
|
|
|
regcache_cache_only(tas2552->regmap, true);
|
|
regcache_mark_dirty(tas2552->regmap);
|
|
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas2552_runtime_resume(struct device *dev)
|
|
{
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(dev);
|
|
|
|
gpiod_set_value(tas2552->enable_gpio, 1);
|
|
|
|
tas2552_sw_shutdown(tas2552, 0);
|
|
|
|
regcache_cache_only(tas2552->regmap, false);
|
|
regcache_sync(tas2552->regmap);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static const struct dev_pm_ops tas2552_pm = {
|
|
SET_RUNTIME_PM_OPS(tas2552_runtime_suspend, tas2552_runtime_resume,
|
|
NULL)
|
|
};
|
|
|
|
static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = {
|
|
.hw_params = tas2552_hw_params,
|
|
.prepare = tas2552_prepare,
|
|
.set_sysclk = tas2552_set_dai_sysclk,
|
|
.set_fmt = tas2552_set_dai_fmt,
|
|
.set_tdm_slot = tas2552_set_dai_tdm_slot,
|
|
.mute_stream = tas2552_mute,
|
|
.no_capture_mute = 1,
|
|
};
|
|
|
|
/* Formats supported by TAS2552 driver. */
|
|
#define TAS2552_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\
|
|
SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE)
|
|
|
|
/* TAS2552 dai structure. */
|
|
static struct snd_soc_dai_driver tas2552_dai[] = {
|
|
{
|
|
.name = "tas2552-amplifier",
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_8000_192000,
|
|
.formats = TAS2552_FORMATS,
|
|
},
|
|
.ops = &tas2552_speaker_dai_ops,
|
|
},
|
|
};
|
|
|
|
/*
|
|
* DAC digital volumes. From -7 to 24 dB in 1 dB steps
|
|
*/
|
|
static DECLARE_TLV_DB_SCALE(dac_tlv, -700, 100, 0);
|
|
|
|
static const char * const tas2552_din_source_select[] = {
|
|
"Muted",
|
|
"Left",
|
|
"Right",
|
|
"Left + Right average",
|
|
};
|
|
static SOC_ENUM_SINGLE_DECL(tas2552_din_source_enum,
|
|
TAS2552_CFG_3, 3,
|
|
tas2552_din_source_select);
|
|
|
|
static const struct snd_kcontrol_new tas2552_snd_controls[] = {
|
|
SOC_SINGLE_TLV("Speaker Driver Playback Volume",
|
|
TAS2552_PGA_GAIN, 0, 0x1f, 0, dac_tlv),
|
|
SOC_ENUM("DIN source", tas2552_din_source_enum),
|
|
};
|
|
|
|
static int tas2552_component_probe(struct snd_soc_component *component)
|
|
{
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
int ret;
|
|
|
|
tas2552->component = component;
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies),
|
|
tas2552->supplies);
|
|
|
|
if (ret != 0) {
|
|
dev_err(component->dev, "Failed to enable supplies: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
gpiod_set_value(tas2552->enable_gpio, 1);
|
|
|
|
ret = pm_runtime_resume_and_get(component->dev);
|
|
if (ret < 0) {
|
|
dev_err(component->dev, "Enabling device failed: %d\n",
|
|
ret);
|
|
goto probe_fail;
|
|
}
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_MUTE, TAS2552_MUTE);
|
|
snd_soc_component_write(component, TAS2552_CFG_3, TAS2552_I2S_OUT_SEL |
|
|
TAS2552_DIN_SRC_SEL_AVG_L_R);
|
|
snd_soc_component_write(component, TAS2552_OUTPUT_DATA,
|
|
TAS2552_PDM_DATA_SEL_V_I |
|
|
TAS2552_R_DATA_OUT(TAS2552_DATA_OUT_V_DATA));
|
|
snd_soc_component_write(component, TAS2552_BOOST_APT_CTRL, TAS2552_APT_DELAY_200 |
|
|
TAS2552_APT_THRESH_20_17);
|
|
|
|
snd_soc_component_write(component, TAS2552_CFG_2, TAS2552_BOOST_EN | TAS2552_APT_EN |
|
|
TAS2552_LIM_EN);
|
|
|
|
return 0;
|
|
|
|
probe_fail:
|
|
pm_runtime_put_noidle(component->dev);
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
|
|
|
regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies),
|
|
tas2552->supplies);
|
|
return ret;
|
|
}
|
|
|
|
static void tas2552_component_remove(struct snd_soc_component *component)
|
|
{
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
|
|
pm_runtime_put(component->dev);
|
|
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
static int tas2552_suspend(struct snd_soc_component *component)
|
|
{
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
int ret;
|
|
|
|
ret = regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies),
|
|
tas2552->supplies);
|
|
|
|
if (ret != 0)
|
|
dev_err(component->dev, "Failed to disable supplies: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
static int tas2552_resume(struct snd_soc_component *component)
|
|
{
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
|
int ret;
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies),
|
|
tas2552->supplies);
|
|
|
|
if (ret != 0) {
|
|
dev_err(component->dev, "Failed to enable supplies: %d\n",
|
|
ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#else
|
|
#define tas2552_suspend NULL
|
|
#define tas2552_resume NULL
|
|
#endif
|
|
|
|
static const struct snd_soc_component_driver soc_component_dev_tas2552 = {
|
|
.probe = tas2552_component_probe,
|
|
.remove = tas2552_component_remove,
|
|
.suspend = tas2552_suspend,
|
|
.resume = tas2552_resume,
|
|
.controls = tas2552_snd_controls,
|
|
.num_controls = ARRAY_SIZE(tas2552_snd_controls),
|
|
.dapm_widgets = tas2552_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(tas2552_dapm_widgets),
|
|
.dapm_routes = tas2552_audio_map,
|
|
.num_dapm_routes = ARRAY_SIZE(tas2552_audio_map),
|
|
.idle_bias_on = 1,
|
|
.endianness = 1,
|
|
};
|
|
|
|
static const struct regmap_config tas2552_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 8,
|
|
|
|
.max_register = TAS2552_MAX_REG,
|
|
.reg_defaults = tas2552_reg_defs,
|
|
.num_reg_defaults = ARRAY_SIZE(tas2552_reg_defs),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
};
|
|
|
|
static int tas2552_probe(struct i2c_client *client)
|
|
{
|
|
struct device *dev;
|
|
struct tas2552_data *data;
|
|
int ret;
|
|
int i;
|
|
|
|
dev = &client->dev;
|
|
data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
|
|
if (data == NULL)
|
|
return -ENOMEM;
|
|
|
|
data->enable_gpio = devm_gpiod_get_optional(dev, "enable",
|
|
GPIOD_OUT_LOW);
|
|
if (IS_ERR(data->enable_gpio))
|
|
return PTR_ERR(data->enable_gpio);
|
|
|
|
data->tas2552_client = client;
|
|
data->regmap = devm_regmap_init_i2c(client, &tas2552_regmap_config);
|
|
if (IS_ERR(data->regmap)) {
|
|
ret = PTR_ERR(data->regmap);
|
|
dev_err(&client->dev, "Failed to allocate register map: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(data->supplies); i++)
|
|
data->supplies[i].supply = tas2552_supply_names[i];
|
|
|
|
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(data->supplies),
|
|
data->supplies);
|
|
if (ret != 0) {
|
|
dev_err(dev, "Failed to request supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pm_runtime_set_active(&client->dev);
|
|
pm_runtime_set_autosuspend_delay(&client->dev, 1000);
|
|
pm_runtime_use_autosuspend(&client->dev);
|
|
pm_runtime_enable(&client->dev);
|
|
pm_runtime_mark_last_busy(&client->dev);
|
|
pm_runtime_put_sync_autosuspend(&client->dev);
|
|
|
|
dev_set_drvdata(&client->dev, data);
|
|
|
|
ret = devm_snd_soc_register_component(&client->dev,
|
|
&soc_component_dev_tas2552,
|
|
tas2552_dai, ARRAY_SIZE(tas2552_dai));
|
|
if (ret < 0) {
|
|
dev_err(&client->dev, "Failed to register component: %d\n", ret);
|
|
pm_runtime_get_noresume(&client->dev);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void tas2552_i2c_remove(struct i2c_client *client)
|
|
{
|
|
pm_runtime_disable(&client->dev);
|
|
}
|
|
|
|
static const struct i2c_device_id tas2552_id[] = {
|
|
{ "tas2552", 0 },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, tas2552_id);
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
static const struct of_device_id tas2552_of_match[] = {
|
|
{ .compatible = "ti,tas2552", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tas2552_of_match);
|
|
#endif
|
|
|
|
static struct i2c_driver tas2552_i2c_driver = {
|
|
.driver = {
|
|
.name = "tas2552",
|
|
.of_match_table = of_match_ptr(tas2552_of_match),
|
|
.pm = &tas2552_pm,
|
|
},
|
|
.probe_new = tas2552_probe,
|
|
.remove = tas2552_i2c_remove,
|
|
.id_table = tas2552_id,
|
|
};
|
|
|
|
module_i2c_driver(tas2552_i2c_driver);
|
|
|
|
MODULE_AUTHOR("Dan Muprhy <dmurphy@ti.com>");
|
|
MODULE_DESCRIPTION("TAS2552 Audio amplifier driver");
|
|
MODULE_LICENSE("GPL");
|