2014-07-15 04:10:45 +08:00
|
|
|
/*
|
|
|
|
* tas2552.c - ALSA SoC Texas Instruments TAS2552 Mono Audio Amplifier
|
|
|
|
*
|
|
|
|
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com
|
|
|
|
*
|
|
|
|
* Author: Dan Murphy <dmurphy@ti.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* 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/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>
|
2015-06-04 21:04:22 +08:00
|
|
|
#include <dt-bindings/sound/tas2552.h>
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
#include "tas2552.h"
|
|
|
|
|
2015-07-05 17:48:29 +08:00
|
|
|
static const struct reg_default tas2552_reg_defs[] = {
|
2014-07-15 04:10:45 +08:00
|
|
|
{TAS2552_CFG_1, 0x22},
|
|
|
|
{TAS2552_CFG_3, 0x80},
|
|
|
|
{TAS2552_DOUT, 0x00},
|
|
|
|
{TAS2552_OUTPUT_DATA, 0xc0},
|
|
|
|
{TAS2552_PDM_CFG, 0x01},
|
|
|
|
{TAS2552_PGA_GAIN, 0x00},
|
2015-06-08 20:19:51 +08:00
|
|
|
{TAS2552_BOOST_APT_CTRL, 0x0f},
|
2015-06-04 21:04:28 +08:00
|
|
|
{TAS2552_RESERVED_0D, 0xbe},
|
2014-07-15 04:10:45 +08:00
|
|
|
{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 {
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component;
|
2014-07-15 04:10:45 +08:00
|
|
|
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];
|
2015-06-04 21:04:20 +08:00
|
|
|
unsigned int pll_clkin;
|
2015-06-08 20:19:48 +08:00
|
|
|
int pll_clk_id;
|
2015-06-04 21:04:22 +08:00
|
|
|
unsigned int pdm_clk;
|
2015-06-08 20:19:48 +08:00
|
|
|
int pdm_clk_id;
|
2015-06-04 21:04:25 +08:00
|
|
|
|
|
|
|
unsigned int dai_fmt;
|
|
|
|
unsigned int tdm_delay;
|
2014-07-15 04:10:45 +08:00
|
|
|
};
|
|
|
|
|
2015-06-04 21:04:28 +08:00
|
|
|
static int tas2552_post_event(struct snd_soc_dapm_widget *w,
|
|
|
|
struct snd_kcontrol *kcontrol, int event)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
|
2015-06-04 21:04:28 +08:00
|
|
|
|
|
|
|
switch (event) {
|
|
|
|
case SND_SOC_DAPM_POST_PMU:
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_RESERVED_0D, 0xc0);
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_LIMIT_RATE_HYS, (1 << 5),
|
2015-06-04 21:04:28 +08:00
|
|
|
(1 << 5));
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, 1, 0);
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS, 0);
|
2015-06-04 21:04:28 +08:00
|
|
|
break;
|
|
|
|
case SND_SOC_DAPM_POST_PMD:
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_SWS,
|
2015-06-04 21:04:28 +08:00
|
|
|
TAS2552_SWS);
|
2018-01-29 12:23:20 +08:00
|
|
|
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);
|
2015-06-04 21:04:28 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2014-08-01 23:57:04 +08:00
|
|
|
|
2015-06-04 21:04:26 +08:00
|
|
|
/* Input mux controls */
|
|
|
|
static const char * const tas2552_input_texts[] = {
|
|
|
|
"Digital", "Analog" };
|
2014-08-01 23:57:04 +08:00
|
|
|
static SOC_ENUM_SINGLE_DECL(tas2552_input_mux_enum, TAS2552_CFG_3, 7,
|
|
|
|
tas2552_input_texts);
|
|
|
|
|
2015-06-04 21:04:26 +08:00
|
|
|
static const struct snd_kcontrol_new tas2552_input_mux_control =
|
|
|
|
SOC_DAPM_ENUM("Route", tas2552_input_mux_enum);
|
2014-08-01 23:57:04 +08:00
|
|
|
|
|
|
|
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,
|
2015-06-04 21:04:26 +08:00
|
|
|
&tas2552_input_mux_control),
|
2014-08-01 23:57:04 +08:00
|
|
|
|
|
|
|
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),
|
2015-06-04 21:04:28 +08:00
|
|
|
SND_SOC_DAPM_POST("Post Event", tas2552_post_event),
|
2014-08-01 23:57:04 +08:00
|
|
|
|
|
|
|
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"},
|
|
|
|
};
|
|
|
|
|
2014-12-13 07:42:18 +08:00
|
|
|
#ifdef CONFIG_PM
|
2015-06-08 20:19:55 +08:00
|
|
|
static void tas2552_sw_shutdown(struct tas2552_data *tas2552, int sw_shutdown)
|
2014-07-15 04:10:45 +08:00
|
|
|
{
|
2015-06-04 21:04:19 +08:00
|
|
|
u8 cfg1_reg = 0;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
if (!tas2552->component)
|
2015-06-04 21:04:14 +08:00
|
|
|
return;
|
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
if (sw_shutdown)
|
2015-06-04 21:04:17 +08:00
|
|
|
cfg1_reg = TAS2552_SWS;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(tas2552->component, TAS2552_CFG_1, TAS2552_SWS,
|
2015-06-04 21:04:17 +08:00
|
|
|
cfg1_reg);
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
2014-10-02 15:28:00 +08:00
|
|
|
#endif
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
static int tas2552_setup_pll(struct snd_soc_component *component,
|
2015-06-08 20:19:48 +08:00
|
|
|
struct snd_pcm_hw_params *params)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
2015-06-08 20:19:48 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
pll_enable = snd_soc_component_read32(component, TAS2552_CFG_2) & TAS2552_PLL_ENABLE;
|
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE, 0);
|
2015-06-08 20:19:48 +08:00
|
|
|
|
|
|
|
if (pll_clkin == pll_clk)
|
|
|
|
bypass_pll = true;
|
|
|
|
|
|
|
|
if (bypass_pll) {
|
|
|
|
/* By pass the PLL configuration */
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_2,
|
2015-06-08 20:19:48 +08:00
|
|
|
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
|
|
|
|
*/
|
ASoC: tas2552: Fix fraction overflow in PLL calculation
Setting the PLL involves the calculation of a fixed point ratio
with 4 decimal digits fraction, referred to as "J.D". The
fraction "D" is stored separately from the integer part "J"
and is limited to 0..9999.
The current algorithm uses integer registers to calculate the
fraction part, but failed to compensate for rounding errors,
resulting in values larger than 9999 for the fraction part
occasionally, e.g. for 44.1kHz audio rate and pll_clkin =
3763400 it would set J to 11 and D to 10002, which will at
best result in wrong pitch.
The critical part is the "pll_clkin / 10000", which would be
ok with real numbers, but using integer arithmetic the rounding
decreases the divisor, thus increasing the final quotient.
The issue is solved by linear interpolation over the reciprocal
function between the two adjacent points with integer divisor,
i.e. pll_clkin / 10000 and pll_clkin / 10000 + 1, and doing
all rounding to the lower result.
As a side effect to the bug fix, the approximation to the
desired frequency is much better, for the above mentioned
example we get 11.9993, while the true ratio is 11.9993623.
Signed-off-by: Oskar Schirmer <oskar@scara.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
2017-08-26 05:36:56 +08:00
|
|
|
unsigned int d, q, t;
|
2015-06-08 20:19:48 +08:00
|
|
|
u8 j;
|
|
|
|
u8 pll_sel = (tas2552->pll_clk_id << 3) & TAS2552_PLL_SRC_MASK;
|
2018-01-29 12:23:20 +08:00
|
|
|
u8 p = snd_soc_component_read32(component, TAS2552_PLL_CTRL_1);
|
2015-06-08 20:19:48 +08:00
|
|
|
|
|
|
|
p = (p >> 7);
|
|
|
|
|
|
|
|
recalc:
|
ASoC: tas2552: Fix fraction overflow in PLL calculation
Setting the PLL involves the calculation of a fixed point ratio
with 4 decimal digits fraction, referred to as "J.D". The
fraction "D" is stored separately from the integer part "J"
and is limited to 0..9999.
The current algorithm uses integer registers to calculate the
fraction part, but failed to compensate for rounding errors,
resulting in values larger than 9999 for the fraction part
occasionally, e.g. for 44.1kHz audio rate and pll_clkin =
3763400 it would set J to 11 and D to 10002, which will at
best result in wrong pitch.
The critical part is the "pll_clkin / 10000", which would be
ok with real numbers, but using integer arithmetic the rounding
decreases the divisor, thus increasing the final quotient.
The issue is solved by linear interpolation over the reciprocal
function between the two adjacent points with integer divisor,
i.e. pll_clkin / 10000 and pll_clkin / 10000 + 1, and doing
all rounding to the lower result.
As a side effect to the bug fix, the approximation to the
desired frequency is much better, for the above mentioned
example we get 11.9993, while the true ratio is 11.9993623.
Signed-off-by: Oskar Schirmer <oskar@scara.com>
Signed-off-by: Mark Brown <broonie@kernel.org>
2017-08-26 05:36:56 +08:00
|
|
|
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;
|
2015-06-08 20:19:48 +08:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_PLL_SRC_MASK,
|
2015-06-08 20:19:48 +08:00
|
|
|
pll_sel);
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_PLL_CTRL_1,
|
2015-06-08 20:19:48 +08:00
|
|
|
TAS2552_PLL_J_MASK, j);
|
|
|
|
/* Will clear the PLL_BYPASS bit */
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_PLL_CTRL_2,
|
2015-06-08 20:19:48 +08:00
|
|
|
TAS2552_PLL_D_UPPER(d));
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_PLL_CTRL_3,
|
2015-06-08 20:19:48 +08:00
|
|
|
TAS2552_PLL_D_LOWER(d));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Restore PLL status */
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_2, TAS2552_PLL_ENABLE,
|
2015-06-08 20:19:48 +08:00
|
|
|
pll_enable);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
static int tas2552_hw_params(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_pcm_hw_params *params,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
2015-06-04 21:04:29 +08:00
|
|
|
int cpf;
|
2015-06-04 21:04:30 +08:00
|
|
|
u8 ser_ctrl1_reg, wclk_rate;
|
2015-06-04 21:04:29 +08:00
|
|
|
|
|
|
|
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:
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Not supported sample size: %d\n",
|
2015-06-04 21:04:29 +08:00
|
|
|
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;
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1,
|
2015-06-04 21:04:29 +08:00
|
|
|
TAS2552_WORDLENGTH_MASK | TAS2552_CLKSPERFRAME_MASK,
|
|
|
|
ser_ctrl1_reg);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2015-06-04 21:04:30 +08:00
|
|
|
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:
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Not supported sample rate: %d\n",
|
2015-06-04 21:04:30 +08:00
|
|
|
params_rate(params));
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_3, TAS2552_WCLK_FREQ_MASK,
|
2015-06-04 21:04:30 +08:00
|
|
|
wclk_rate);
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
return tas2552_setup_pll(component, params);
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
|
|
|
|
2015-06-04 21:04:24 +08:00
|
|
|
#define TAS2552_DAI_FMT_MASK (TAS2552_BCLKDIR | \
|
|
|
|
TAS2552_WCLKDIR | \
|
|
|
|
TAS2552_DATAFORMAT_MASK)
|
2015-06-04 21:04:25 +08:00
|
|
|
static int tas2552_prepare(struct snd_pcm_substream *substream,
|
|
|
|
struct snd_soc_dai *dai)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2015-06-04 21:04:25 +08:00
|
|
|
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 */
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_SER_CTRL_2, delay);
|
2015-06-04 21:04:25 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
static int tas2552_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
2014-07-15 04:10:45 +08:00
|
|
|
u8 serial_format;
|
|
|
|
|
|
|
|
switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFS:
|
|
|
|
serial_format = 0x00;
|
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBS_CFM:
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format = TAS2552_WCLKDIR;
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBM_CFS:
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format = TAS2552_BCLKDIR;
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
|
|
|
case SND_SOC_DAIFMT_CBM_CFM:
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format = (TAS2552_BCLKDIR | TAS2552_WCLKDIR);
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
|
|
|
default:
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_vdbg(component->dev, "DAI Format master is not found\n");
|
2014-07-15 04:10:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-06-04 21:04:23 +08:00
|
|
|
switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK |
|
|
|
|
SND_SOC_DAIFMT_INV_MASK)) {
|
|
|
|
case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF):
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
2015-06-04 21:04:23 +08:00
|
|
|
case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF):
|
|
|
|
case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF):
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format |= TAS2552_DATAFORMAT_DSP;
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
2015-06-04 21:04:23 +08:00
|
|
|
case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF):
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format |= TAS2552_DATAFORMAT_RIGHT_J;
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
2015-06-04 21:04:23 +08:00
|
|
|
case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF):
|
2015-06-04 21:04:24 +08:00
|
|
|
serial_format |= TAS2552_DATAFORMAT_LEFT_J;
|
2014-07-15 04:10:45 +08:00
|
|
|
break;
|
|
|
|
default:
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_vdbg(component->dev, "DAI Format is not found\n");
|
2014-07-15 04:10:45 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2015-06-04 21:04:25 +08:00
|
|
|
tas2552->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_SER_CTRL_1, TAS2552_DAI_FMT_MASK,
|
2015-06-04 21:04:23 +08:00
|
|
|
serial_format);
|
2014-07-15 04:10:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tas2552_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id,
|
|
|
|
unsigned int freq, int dir)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(component->dev);
|
2015-06-04 21:04:22 +08:00
|
|
|
u8 reg, mask, val;
|
|
|
|
|
|
|
|
switch (clk_id) {
|
|
|
|
case TAS2552_PLL_CLKIN_MCLK:
|
|
|
|
case TAS2552_PLL_CLKIN_IVCLKIN:
|
2015-06-08 20:19:48 +08:00
|
|
|
if (freq < 512000 || freq > 24576000) {
|
|
|
|
/* out of range PLL_CLKIN, fall back to use BCLK */
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_warn(component->dev, "Out of range PLL_CLKIN: %u\n",
|
2015-06-08 20:19:48 +08:00
|
|
|
freq);
|
|
|
|
clk_id = TAS2552_PLL_CLKIN_BCLK;
|
|
|
|
freq = 0;
|
|
|
|
}
|
|
|
|
/* fall through */
|
|
|
|
case TAS2552_PLL_CLKIN_BCLK:
|
2015-06-04 21:04:22 +08:00
|
|
|
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;
|
2015-06-08 20:19:48 +08:00
|
|
|
tas2552->pll_clk_id = clk_id;
|
2015-06-04 21:04:22 +08:00
|
|
|
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;
|
2015-06-08 20:19:48 +08:00
|
|
|
tas2552->pdm_clk_id = clk_id;
|
2015-06-04 21:04:22 +08:00
|
|
|
tas2552->pdm_clk = freq;
|
|
|
|
break;
|
|
|
|
default:
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Invalid clk id: %d\n", clk_id);
|
2015-06-04 21:04:22 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, reg, mask, val);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-06-04 21:04:25 +08:00
|
|
|
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)
|
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2015-06-04 21:04:25 +08:00
|
|
|
unsigned int lsb;
|
|
|
|
|
|
|
|
if (unlikely(!tx_mask)) {
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "tx masks need to be non 0\n");
|
2015-06-04 21:04:25 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* TDM based on DSP mode requires slots to be adjacent */
|
|
|
|
lsb = __ffs(tx_mask);
|
|
|
|
if ((lsb + 1) != __fls(tx_mask)) {
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Invalid mask, slots must be adjacent\n");
|
2015-06-04 21:04:25 +08:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
tas2552->tdm_delay = lsb * slot_width;
|
|
|
|
|
|
|
|
/* DOUT in high-impedance on inactive bit clocks */
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_DOUT,
|
2015-06-04 21:04:25 +08:00
|
|
|
TAS2552_SDOUT_TRISTATE, TAS2552_SDOUT_TRISTATE);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
static int tas2552_mute(struct snd_soc_dai *dai, int mute)
|
|
|
|
{
|
2015-06-04 21:04:18 +08:00
|
|
|
u8 cfg1_reg = 0;
|
2018-01-29 12:23:20 +08:00
|
|
|
struct snd_soc_component *component = dai->component;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
if (mute)
|
2015-06-04 21:04:18 +08:00
|
|
|
cfg1_reg |= TAS2552_MUTE;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_update_bits(component, TAS2552_CFG_1, TAS2552_MUTE, cfg1_reg);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2014-12-13 07:42:18 +08:00
|
|
|
#ifdef CONFIG_PM
|
2014-07-15 04:10:45 +08:00
|
|
|
static int tas2552_runtime_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(dev);
|
|
|
|
|
2015-06-04 21:04:19 +08:00
|
|
|
tas2552_sw_shutdown(tas2552, 1);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
regcache_cache_only(tas2552->regmap, true);
|
|
|
|
regcache_mark_dirty(tas2552->regmap);
|
|
|
|
|
2015-07-25 15:32:16 +08:00
|
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
2014-07-19 01:31:07 +08:00
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int tas2552_runtime_resume(struct device *dev)
|
|
|
|
{
|
|
|
|
struct tas2552_data *tas2552 = dev_get_drvdata(dev);
|
|
|
|
|
2015-07-25 15:32:16 +08:00
|
|
|
gpiod_set_value(tas2552->enable_gpio, 1);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2015-06-04 21:04:19 +08:00
|
|
|
tas2552_sw_shutdown(tas2552, 0);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
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)
|
|
|
|
};
|
|
|
|
|
2015-07-15 15:38:14 +08:00
|
|
|
static const struct snd_soc_dai_ops tas2552_speaker_dai_ops = {
|
2014-07-15 04:10:45 +08:00
|
|
|
.hw_params = tas2552_hw_params,
|
2015-06-04 21:04:25 +08:00
|
|
|
.prepare = tas2552_prepare,
|
2014-07-15 04:10:45 +08:00
|
|
|
.set_sysclk = tas2552_set_dai_sysclk,
|
|
|
|
.set_fmt = tas2552_set_dai_fmt,
|
2015-06-04 21:04:25 +08:00
|
|
|
.set_tdm_slot = tas2552_set_dai_tdm_slot,
|
2014-07-15 04:10:45 +08:00
|
|
|
.digital_mute = tas2552_mute,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* 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 = {
|
2014-08-01 23:57:04 +08:00
|
|
|
.stream_name = "Playback",
|
2014-07-15 04:10:45 +08:00
|
|
|
.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
|
|
|
|
*/
|
2015-10-06 04:00:14 +08:00
|
|
|
static DECLARE_TLV_DB_SCALE(dac_tlv, -700, 100, 0);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2015-06-08 20:19:49 +08:00
|
|
|
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);
|
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
static const struct snd_kcontrol_new tas2552_snd_controls[] = {
|
|
|
|
SOC_SINGLE_TLV("Speaker Driver Playback Volume",
|
2015-06-04 21:04:27 +08:00
|
|
|
TAS2552_PGA_GAIN, 0, 0x1f, 0, dac_tlv),
|
2015-06-08 20:19:49 +08:00
|
|
|
SOC_ENUM("DIN source", tas2552_din_source_enum),
|
2014-07-15 04:10:45 +08:00
|
|
|
};
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
static int tas2552_component_probe(struct snd_soc_component *component)
|
2014-07-15 04:10:45 +08:00
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2014-07-15 04:10:45 +08:00
|
|
|
int ret;
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
tas2552->component = component;
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies),
|
|
|
|
tas2552->supplies);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Failed to enable supplies: %d\n",
|
2014-07-15 04:10:45 +08:00
|
|
|
ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2015-07-25 15:32:16 +08:00
|
|
|
gpiod_set_value(tas2552->enable_gpio, 1);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
ret = pm_runtime_get_sync(component->dev);
|
2014-07-15 04:10:45 +08:00
|
|
|
if (ret < 0) {
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Enabling device failed: %d\n",
|
2014-07-15 04:10:45 +08:00
|
|
|
ret);
|
|
|
|
goto probe_fail;
|
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
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 |
|
2015-06-04 21:04:30 +08:00
|
|
|
TAS2552_DIN_SRC_SEL_AVG_L_R);
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_OUTPUT_DATA,
|
2015-06-08 20:19:50 +08:00
|
|
|
TAS2552_PDM_DATA_SEL_V_I |
|
|
|
|
TAS2552_R_DATA_OUT(TAS2552_DATA_OUT_V_DATA));
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_BOOST_APT_CTRL, TAS2552_APT_DELAY_200 |
|
2015-06-08 20:19:51 +08:00
|
|
|
TAS2552_APT_THRESH_20_17);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
snd_soc_component_write(component, TAS2552_CFG_2, TAS2552_BOOST_EN | TAS2552_APT_EN |
|
2015-06-08 20:19:52 +08:00
|
|
|
TAS2552_LIM_EN);
|
2014-08-01 23:57:04 +08:00
|
|
|
|
2014-07-15 04:10:45 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
probe_fail:
|
2015-07-25 15:32:16 +08:00
|
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies),
|
|
|
|
tas2552->supplies);
|
2017-04-11 21:42:02 +08:00
|
|
|
return ret;
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
static void tas2552_component_remove(struct snd_soc_component *component)
|
2014-07-15 04:10:45 +08:00
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
pm_runtime_put(component->dev);
|
2014-07-19 01:31:07 +08:00
|
|
|
|
2015-07-25 15:32:16 +08:00
|
|
|
gpiod_set_value(tas2552->enable_gpio, 0);
|
2014-07-15 04:10:45 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_PM
|
2018-01-29 12:23:20 +08:00
|
|
|
static int tas2552_suspend(struct snd_soc_component *component)
|
2014-07-15 04:10:45 +08:00
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2014-07-15 04:10:45 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regulator_bulk_disable(ARRAY_SIZE(tas2552->supplies),
|
|
|
|
tas2552->supplies);
|
|
|
|
|
|
|
|
if (ret != 0)
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Failed to disable supplies: %d\n",
|
2014-07-15 04:10:45 +08:00
|
|
|
ret);
|
2017-04-25 08:43:49 +08:00
|
|
|
return ret;
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
static int tas2552_resume(struct snd_soc_component *component)
|
2014-07-15 04:10:45 +08:00
|
|
|
{
|
2018-01-29 12:23:20 +08:00
|
|
|
struct tas2552_data *tas2552 = snd_soc_component_get_drvdata(component);
|
2014-07-15 04:10:45 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(tas2552->supplies),
|
|
|
|
tas2552->supplies);
|
|
|
|
|
|
|
|
if (ret != 0) {
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(component->dev, "Failed to enable supplies: %d\n",
|
2014-07-15 04:10:45 +08:00
|
|
|
ret);
|
|
|
|
}
|
|
|
|
|
2017-04-25 08:43:49 +08:00
|
|
|
return ret;
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define tas2552_suspend NULL
|
|
|
|
#define tas2552_resume NULL
|
|
|
|
#endif
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
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,
|
|
|
|
.non_legacy_dai_naming = 1,
|
2014-07-15 04:10:45 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
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,
|
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
2015-06-10 22:46:05 +08:00
|
|
|
data->enable_gpio = devm_gpiod_get_optional(dev, "enable",
|
|
|
|
GPIOD_OUT_LOW);
|
|
|
|
if (IS_ERR(data->enable_gpio))
|
|
|
|
return PTR_ERR(data->enable_gpio);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
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);
|
2014-07-23 16:42:21 +08:00
|
|
|
if (ret != 0) {
|
2014-07-15 04:10:45 +08:00
|
|
|
dev_err(dev, "Failed to request supplies: %d\n", ret);
|
2014-07-23 16:42:21 +08:00
|
|
|
return ret;
|
|
|
|
}
|
2014-07-15 04:10:45 +08:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2018-01-29 12:23:20 +08:00
|
|
|
ret = devm_snd_soc_register_component(&client->dev,
|
|
|
|
&soc_component_dev_tas2552,
|
2014-07-15 04:10:45 +08:00
|
|
|
tas2552_dai, ARRAY_SIZE(tas2552_dai));
|
|
|
|
if (ret < 0)
|
2018-01-29 12:23:20 +08:00
|
|
|
dev_err(&client->dev, "Failed to register component: %d\n", ret);
|
2014-07-15 04:10:45 +08:00
|
|
|
|
2014-07-23 16:42:21 +08:00
|
|
|
return ret;
|
2014-07-15 04:10:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int tas2552_i2c_remove(struct i2c_client *client)
|
|
|
|
{
|
2015-06-08 20:19:53 +08:00
|
|
|
pm_runtime_disable(&client->dev);
|
2014-07-15 04:10:45 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 = 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");
|