mirror of
https://github.com/torvalds/linux.git
synced 2024-12-04 01:51:34 +00:00
340d79a14d
The DT of_device.h and of_platform.h date back to the separate of_platform_bus_type before it was merged into the regular platform bus. As part of that merge prepping Arm DT support 13 years ago, they "temporarily" include each other. They also include platform_device.h and of.h. As a result, there's a pretty much random mix of those include files used throughout the tree. In order to detangle these headers and replace the implicit includes with struct declarations, users need to explicitly include the correct includes. Acked-by: Jernej Skrabec <jernej.skrabec@gmail.com> Reviewed-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> Acked-by: Charles Keepax <ckeepax@opensource.cirrus.com> Reviewed-by: Claudiu Beznea <claudiu.beznea@tuxon.dev> # for at91 Signed-off-by: Rob Herring <robh@kernel.org> Link: https://lore.kernel.org/r/20231006-dt-asoc-header-cleanups-v3-1-13a4f0f7fee6@kernel.org Signed-off-by: Mark Brown <broonie@kernel.org>
976 lines
28 KiB
C
976 lines
28 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* TAS571x amplifier audio driver
|
|
*
|
|
* Copyright (C) 2015 Google, Inc.
|
|
* Copyright (c) 2013 Daniel Mack <zonque@gmail.com>
|
|
*
|
|
* TAS5721 support:
|
|
* Copyright (C) 2016 Petr Kulhavy, Barix AG <petr@barix.com>
|
|
*
|
|
* TAS5707 support:
|
|
* Copyright (C) 2018 Jerome Brunet, Baylibre SAS <jbrunet@baylibre.com>
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/stddef.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/soc.h>
|
|
#include <sound/tlv.h>
|
|
#include <asm/unaligned.h>
|
|
|
|
#include "tas571x.h"
|
|
|
|
#define TAS571X_MAX_SUPPLIES 6
|
|
|
|
struct tas571x_chip {
|
|
const char *const *supply_names;
|
|
int num_supply_names;
|
|
const struct snd_kcontrol_new *controls;
|
|
int num_controls;
|
|
const struct regmap_config *regmap_config;
|
|
int vol_reg_size;
|
|
};
|
|
|
|
struct tas571x_private {
|
|
const struct tas571x_chip *chip;
|
|
struct regmap *regmap;
|
|
struct regulator_bulk_data supplies[TAS571X_MAX_SUPPLIES];
|
|
struct clk *mclk;
|
|
unsigned int format;
|
|
struct gpio_desc *reset_gpio;
|
|
struct gpio_desc *pdn_gpio;
|
|
struct snd_soc_component_driver component_driver;
|
|
};
|
|
|
|
static int tas571x_register_size(struct tas571x_private *priv, unsigned int reg)
|
|
{
|
|
switch (reg) {
|
|
case TAS571X_MVOL_REG:
|
|
case TAS571X_CH1_VOL_REG:
|
|
case TAS571X_CH2_VOL_REG:
|
|
return priv->chip->vol_reg_size;
|
|
case TAS571X_INPUT_MUX_REG:
|
|
case TAS571X_CH4_SRC_SELECT_REG:
|
|
case TAS571X_PWM_MUX_REG:
|
|
case TAS5717_CH1_RIGHT_CH_MIX_REG:
|
|
case TAS5717_CH1_LEFT_CH_MIX_REG:
|
|
case TAS5717_CH2_LEFT_CH_MIX_REG:
|
|
case TAS5717_CH2_RIGHT_CH_MIX_REG:
|
|
return 4;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int tas571x_reg_write(void *context, unsigned int reg,
|
|
unsigned int value)
|
|
{
|
|
struct i2c_client *client = context;
|
|
struct tas571x_private *priv = i2c_get_clientdata(client);
|
|
unsigned int i, size;
|
|
uint8_t buf[5];
|
|
int ret;
|
|
|
|
size = tas571x_register_size(priv, reg);
|
|
buf[0] = reg;
|
|
|
|
for (i = size; i >= 1; --i) {
|
|
buf[i] = value;
|
|
value >>= 8;
|
|
}
|
|
|
|
ret = i2c_master_send(client, buf, size + 1);
|
|
if (ret == size + 1)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
static int tas571x_reg_read(void *context, unsigned int reg,
|
|
unsigned int *value)
|
|
{
|
|
struct i2c_client *client = context;
|
|
struct tas571x_private *priv = i2c_get_clientdata(client);
|
|
uint8_t send_buf, recv_buf[4];
|
|
struct i2c_msg msgs[2];
|
|
unsigned int size;
|
|
unsigned int i;
|
|
int ret;
|
|
|
|
size = tas571x_register_size(priv, reg);
|
|
send_buf = reg;
|
|
|
|
msgs[0].addr = client->addr;
|
|
msgs[0].len = sizeof(send_buf);
|
|
msgs[0].buf = &send_buf;
|
|
msgs[0].flags = 0;
|
|
|
|
msgs[1].addr = client->addr;
|
|
msgs[1].len = size;
|
|
msgs[1].buf = recv_buf;
|
|
msgs[1].flags = I2C_M_RD;
|
|
|
|
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
|
if (ret < 0)
|
|
return ret;
|
|
else if (ret != ARRAY_SIZE(msgs))
|
|
return -EIO;
|
|
|
|
*value = 0;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
*value <<= 8;
|
|
*value |= recv_buf[i];
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* register write for 8- and 20-byte registers
|
|
*/
|
|
static int tas571x_reg_write_multiword(struct i2c_client *client,
|
|
unsigned int reg, const long values[], size_t len)
|
|
{
|
|
size_t i;
|
|
uint8_t *buf, *p;
|
|
int ret;
|
|
size_t send_size = 1 + len * sizeof(uint32_t);
|
|
|
|
buf = kzalloc(send_size, GFP_KERNEL | GFP_DMA);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
buf[0] = reg;
|
|
|
|
for (i = 0, p = buf + 1; i < len; i++, p += sizeof(uint32_t))
|
|
put_unaligned_be32(values[i], p);
|
|
|
|
ret = i2c_master_send(client, buf, send_size);
|
|
|
|
kfree(buf);
|
|
|
|
if (ret == send_size)
|
|
return 0;
|
|
else if (ret < 0)
|
|
return ret;
|
|
else
|
|
return -EIO;
|
|
}
|
|
|
|
/*
|
|
* register read for 8- and 20-byte registers
|
|
*/
|
|
static int tas571x_reg_read_multiword(struct i2c_client *client,
|
|
unsigned int reg, long values[], size_t len)
|
|
{
|
|
unsigned int i;
|
|
uint8_t send_buf;
|
|
uint8_t *recv_buf, *p;
|
|
struct i2c_msg msgs[2];
|
|
unsigned int recv_size = len * sizeof(uint32_t);
|
|
int ret;
|
|
|
|
recv_buf = kzalloc(recv_size, GFP_KERNEL | GFP_DMA);
|
|
if (!recv_buf)
|
|
return -ENOMEM;
|
|
|
|
send_buf = reg;
|
|
|
|
msgs[0].addr = client->addr;
|
|
msgs[0].len = sizeof(send_buf);
|
|
msgs[0].buf = &send_buf;
|
|
msgs[0].flags = 0;
|
|
|
|
msgs[1].addr = client->addr;
|
|
msgs[1].len = recv_size;
|
|
msgs[1].buf = recv_buf;
|
|
msgs[1].flags = I2C_M_RD;
|
|
|
|
ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
|
|
if (ret < 0)
|
|
goto err_ret;
|
|
else if (ret != ARRAY_SIZE(msgs)) {
|
|
ret = -EIO;
|
|
goto err_ret;
|
|
}
|
|
|
|
for (i = 0, p = recv_buf; i < len; i++, p += sizeof(uint32_t))
|
|
values[i] = get_unaligned_be32(p);
|
|
|
|
err_ret:
|
|
kfree(recv_buf);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Integer array controls for setting biquad, mixer, DRC coefficients.
|
|
* According to the datasheet each coefficient is effectively 26bits,
|
|
* i.e. stored as 32bits, where bits [31:26] are ignored.
|
|
* TI's TAS57xx Graphical Development Environment tool however produces
|
|
* coefficients with more than 26 bits. For this reason we allow values
|
|
* in the full 32-bits reange.
|
|
* The coefficients are ordered as given in the TAS571x data sheet:
|
|
* b0, b1, b2, a1, a2
|
|
*/
|
|
|
|
static int tas571x_coefficient_info(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_info *uinfo)
|
|
{
|
|
int numcoef = kcontrol->private_value >> 16;
|
|
|
|
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
|
|
uinfo->count = numcoef;
|
|
uinfo->value.integer.min = 0;
|
|
uinfo->value.integer.max = 0xffffffff;
|
|
return 0;
|
|
}
|
|
|
|
static int tas571x_coefficient_get(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct i2c_client *i2c = to_i2c_client(component->dev);
|
|
int numcoef = kcontrol->private_value >> 16;
|
|
int index = kcontrol->private_value & 0xffff;
|
|
|
|
return tas571x_reg_read_multiword(i2c, index,
|
|
ucontrol->value.integer.value, numcoef);
|
|
}
|
|
|
|
static int tas571x_coefficient_put(struct snd_kcontrol *kcontrol,
|
|
struct snd_ctl_elem_value *ucontrol)
|
|
{
|
|
struct snd_soc_component *component = snd_soc_kcontrol_component(kcontrol);
|
|
struct i2c_client *i2c = to_i2c_client(component->dev);
|
|
int numcoef = kcontrol->private_value >> 16;
|
|
int index = kcontrol->private_value & 0xffff;
|
|
|
|
return tas571x_reg_write_multiword(i2c, index,
|
|
ucontrol->value.integer.value, numcoef);
|
|
}
|
|
|
|
static int tas571x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format)
|
|
{
|
|
struct tas571x_private *priv = snd_soc_component_get_drvdata(dai->component);
|
|
|
|
priv->format = format;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tas571x_hw_params(struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params,
|
|
struct snd_soc_dai *dai)
|
|
{
|
|
struct tas571x_private *priv = snd_soc_component_get_drvdata(dai->component);
|
|
u32 val;
|
|
|
|
switch (priv->format & SND_SOC_DAIFMT_FORMAT_MASK) {
|
|
case SND_SOC_DAIFMT_RIGHT_J:
|
|
val = 0x00;
|
|
break;
|
|
case SND_SOC_DAIFMT_I2S:
|
|
val = 0x03;
|
|
break;
|
|
case SND_SOC_DAIFMT_LEFT_J:
|
|
val = 0x06;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (params_width(params) >= 24)
|
|
val += 2;
|
|
else if (params_width(params) >= 20)
|
|
val += 1;
|
|
|
|
return regmap_update_bits(priv->regmap, TAS571X_SDI_REG,
|
|
TAS571X_SDI_FMT_MASK, val);
|
|
}
|
|
|
|
static int tas571x_mute(struct snd_soc_dai *dai, int mute, int direction)
|
|
{
|
|
struct snd_soc_component *component = dai->component;
|
|
u8 sysctl2;
|
|
int ret;
|
|
|
|
sysctl2 = mute ? TAS571X_SYS_CTRL_2_SDN_MASK : 0;
|
|
|
|
ret = snd_soc_component_update_bits(component,
|
|
TAS571X_SYS_CTRL_2_REG,
|
|
TAS571X_SYS_CTRL_2_SDN_MASK,
|
|
sysctl2);
|
|
usleep_range(1000, 2000);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tas571x_set_bias_level(struct snd_soc_component *component,
|
|
enum snd_soc_bias_level level)
|
|
{
|
|
struct tas571x_private *priv = snd_soc_component_get_drvdata(component);
|
|
int ret;
|
|
|
|
switch (level) {
|
|
case SND_SOC_BIAS_ON:
|
|
break;
|
|
case SND_SOC_BIAS_PREPARE:
|
|
break;
|
|
case SND_SOC_BIAS_STANDBY:
|
|
if (snd_soc_component_get_bias_level(component) == SND_SOC_BIAS_OFF) {
|
|
if (!IS_ERR(priv->mclk)) {
|
|
ret = clk_prepare_enable(priv->mclk);
|
|
if (ret) {
|
|
dev_err(component->dev,
|
|
"Failed to enable master clock: %d\n",
|
|
ret);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case SND_SOC_BIAS_OFF:
|
|
if (!IS_ERR(priv->mclk))
|
|
clk_disable_unprepare(priv->mclk);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct snd_soc_dai_ops tas571x_dai_ops = {
|
|
.set_fmt = tas571x_set_dai_fmt,
|
|
.hw_params = tas571x_hw_params,
|
|
.mute_stream = tas571x_mute,
|
|
.no_capture_mute = 1,
|
|
};
|
|
|
|
|
|
#define BIQUAD_COEFS(xname, reg) \
|
|
{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
|
|
.info = tas571x_coefficient_info, \
|
|
.get = tas571x_coefficient_get,\
|
|
.put = tas571x_coefficient_put, \
|
|
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE, \
|
|
.private_value = reg | (5 << 16) }
|
|
|
|
static const char *const tas5711_supply_names[] = {
|
|
"AVDD",
|
|
"DVDD",
|
|
"PVDD_A",
|
|
"PVDD_B",
|
|
"PVDD_C",
|
|
"PVDD_D",
|
|
};
|
|
|
|
static const DECLARE_TLV_DB_SCALE(tas5711_volume_tlv, -10350, 50, 1);
|
|
|
|
static const struct snd_kcontrol_new tas5711_controls[] = {
|
|
SOC_SINGLE_TLV("Master Volume",
|
|
TAS571X_MVOL_REG,
|
|
0, 0xff, 1, tas5711_volume_tlv),
|
|
SOC_DOUBLE_R_TLV("Speaker Volume",
|
|
TAS571X_CH1_VOL_REG,
|
|
TAS571X_CH2_VOL_REG,
|
|
0, 0xff, 1, tas5711_volume_tlv),
|
|
SOC_DOUBLE("Speaker Switch",
|
|
TAS571X_SOFT_MUTE_REG,
|
|
TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
|
|
1, 1),
|
|
};
|
|
|
|
static const struct regmap_range tas571x_readonly_regs_range[] = {
|
|
regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_DEV_ID_REG),
|
|
};
|
|
|
|
static const struct regmap_range tas571x_volatile_regs_range[] = {
|
|
regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG),
|
|
regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG),
|
|
};
|
|
|
|
static const struct regmap_access_table tas571x_write_regs = {
|
|
.no_ranges = tas571x_readonly_regs_range,
|
|
.n_no_ranges = ARRAY_SIZE(tas571x_readonly_regs_range),
|
|
};
|
|
|
|
static const struct regmap_access_table tas571x_volatile_regs = {
|
|
.yes_ranges = tas571x_volatile_regs_range,
|
|
.n_yes_ranges = ARRAY_SIZE(tas571x_volatile_regs_range),
|
|
|
|
};
|
|
|
|
static const struct reg_default tas5711_reg_defaults[] = {
|
|
{ 0x04, 0x05 },
|
|
{ 0x05, 0x40 },
|
|
{ 0x06, 0x00 },
|
|
{ 0x07, 0xff },
|
|
{ 0x08, 0x30 },
|
|
{ 0x09, 0x30 },
|
|
{ 0x1b, 0x82 },
|
|
};
|
|
|
|
static const struct regmap_config tas5711_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.max_register = 0xff,
|
|
.reg_read = tas571x_reg_read,
|
|
.reg_write = tas571x_reg_write,
|
|
.reg_defaults = tas5711_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(tas5711_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.wr_table = &tas571x_write_regs,
|
|
.volatile_table = &tas571x_volatile_regs,
|
|
};
|
|
|
|
static const struct tas571x_chip tas5711_chip = {
|
|
.supply_names = tas5711_supply_names,
|
|
.num_supply_names = ARRAY_SIZE(tas5711_supply_names),
|
|
.controls = tas5711_controls,
|
|
.num_controls = ARRAY_SIZE(tas5711_controls),
|
|
.regmap_config = &tas5711_regmap_config,
|
|
.vol_reg_size = 1,
|
|
};
|
|
|
|
static const struct regmap_range tas5707_volatile_regs_range[] = {
|
|
regmap_reg_range(TAS571X_CLK_CTRL_REG, TAS571X_ERR_STATUS_REG),
|
|
regmap_reg_range(TAS571X_OSC_TRIM_REG, TAS571X_OSC_TRIM_REG),
|
|
regmap_reg_range(TAS5707_CH1_BQ0_REG, TAS5707_CH2_BQ6_REG),
|
|
};
|
|
|
|
static const struct regmap_access_table tas5707_volatile_regs = {
|
|
.yes_ranges = tas5707_volatile_regs_range,
|
|
.n_yes_ranges = ARRAY_SIZE(tas5707_volatile_regs_range),
|
|
|
|
};
|
|
|
|
static const DECLARE_TLV_DB_SCALE(tas5707_volume_tlv, -7900, 50, 1);
|
|
|
|
static const char * const tas5707_volume_slew_step_txt[] = {
|
|
"256", "512", "1024", "2048",
|
|
};
|
|
|
|
static const unsigned int tas5707_volume_slew_step_values[] = {
|
|
3, 0, 1, 2,
|
|
};
|
|
|
|
static SOC_VALUE_ENUM_SINGLE_DECL(tas5707_volume_slew_step_enum,
|
|
TAS571X_VOL_CFG_REG, 0, 0x3,
|
|
tas5707_volume_slew_step_txt,
|
|
tas5707_volume_slew_step_values);
|
|
|
|
static const struct snd_kcontrol_new tas5707_controls[] = {
|
|
SOC_SINGLE_TLV("Master Volume",
|
|
TAS571X_MVOL_REG,
|
|
0, 0xff, 1, tas5707_volume_tlv),
|
|
SOC_DOUBLE_R_TLV("Speaker Volume",
|
|
TAS571X_CH1_VOL_REG,
|
|
TAS571X_CH2_VOL_REG,
|
|
0, 0xff, 1, tas5707_volume_tlv),
|
|
SOC_DOUBLE("Speaker Switch",
|
|
TAS571X_SOFT_MUTE_REG,
|
|
TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
|
|
1, 1),
|
|
|
|
SOC_ENUM("Slew Rate Steps", tas5707_volume_slew_step_enum),
|
|
|
|
BIQUAD_COEFS("CH1 - Biquad 0", TAS5707_CH1_BQ0_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 1", TAS5707_CH1_BQ1_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 2", TAS5707_CH1_BQ2_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 3", TAS5707_CH1_BQ3_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 4", TAS5707_CH1_BQ4_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 5", TAS5707_CH1_BQ5_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 6", TAS5707_CH1_BQ6_REG),
|
|
|
|
BIQUAD_COEFS("CH2 - Biquad 0", TAS5707_CH2_BQ0_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 1", TAS5707_CH2_BQ1_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 2", TAS5707_CH2_BQ2_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 3", TAS5707_CH2_BQ3_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 4", TAS5707_CH2_BQ4_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 5", TAS5707_CH2_BQ5_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 6", TAS5707_CH2_BQ6_REG),
|
|
};
|
|
|
|
static const struct reg_default tas5707_reg_defaults[] = {
|
|
{TAS571X_CLK_CTRL_REG, 0x6c},
|
|
{TAS571X_DEV_ID_REG, 0x70},
|
|
{TAS571X_ERR_STATUS_REG, 0x00},
|
|
{TAS571X_SYS_CTRL_1_REG, 0xa0},
|
|
{TAS571X_SDI_REG, 0x05},
|
|
{TAS571X_SYS_CTRL_2_REG, 0x40},
|
|
{TAS571X_SOFT_MUTE_REG, 0x00},
|
|
{TAS571X_MVOL_REG, 0xff},
|
|
{TAS571X_CH1_VOL_REG, 0x30},
|
|
{TAS571X_CH2_VOL_REG, 0x30},
|
|
{TAS571X_VOL_CFG_REG, 0x91},
|
|
{TAS571X_MODULATION_LIMIT_REG, 0x02},
|
|
{TAS571X_IC_DELAY_CH1_REG, 0xac},
|
|
{TAS571X_IC_DELAY_CH2_REG, 0x54},
|
|
{TAS571X_IC_DELAY_CH3_REG, 0xac},
|
|
{TAS571X_IC_DELAY_CH4_REG, 0x54},
|
|
{TAS571X_START_STOP_PERIOD_REG, 0x0f},
|
|
{TAS571X_OSC_TRIM_REG, 0x82},
|
|
{TAS571X_BKND_ERR_REG, 0x02},
|
|
{TAS571X_INPUT_MUX_REG, 0x17772},
|
|
{TAS571X_PWM_MUX_REG, 0x1021345},
|
|
};
|
|
|
|
static const struct regmap_config tas5707_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.max_register = 0xff,
|
|
.reg_read = tas571x_reg_read,
|
|
.reg_write = tas571x_reg_write,
|
|
.reg_defaults = tas5707_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(tas5707_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.wr_table = &tas571x_write_regs,
|
|
.volatile_table = &tas5707_volatile_regs,
|
|
};
|
|
|
|
static const struct tas571x_chip tas5707_chip = {
|
|
.supply_names = tas5711_supply_names,
|
|
.num_supply_names = ARRAY_SIZE(tas5711_supply_names),
|
|
.controls = tas5707_controls,
|
|
.num_controls = ARRAY_SIZE(tas5707_controls),
|
|
.regmap_config = &tas5707_regmap_config,
|
|
.vol_reg_size = 1,
|
|
};
|
|
|
|
static const char *const tas5717_supply_names[] = {
|
|
"AVDD",
|
|
"DVDD",
|
|
"HPVDD",
|
|
"PVDD_AB",
|
|
"PVDD_CD",
|
|
};
|
|
|
|
static const DECLARE_TLV_DB_SCALE(tas5717_volume_tlv, -10375, 25, 0);
|
|
|
|
static const struct snd_kcontrol_new tas5717_controls[] = {
|
|
/* MVOL LSB is ignored - see comments in tas571x_i2c_probe() */
|
|
SOC_SINGLE_TLV("Master Volume",
|
|
TAS571X_MVOL_REG, 1, 0x1ff, 1,
|
|
tas5717_volume_tlv),
|
|
SOC_DOUBLE_R_TLV("Speaker Volume",
|
|
TAS571X_CH1_VOL_REG, TAS571X_CH2_VOL_REG,
|
|
1, 0x1ff, 1, tas5717_volume_tlv),
|
|
SOC_DOUBLE("Speaker Switch",
|
|
TAS571X_SOFT_MUTE_REG,
|
|
TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
|
|
1, 1),
|
|
|
|
SOC_DOUBLE_R_RANGE("CH1 Mixer Volume",
|
|
TAS5717_CH1_LEFT_CH_MIX_REG,
|
|
TAS5717_CH1_RIGHT_CH_MIX_REG,
|
|
16, 0, 0x80, 0),
|
|
|
|
SOC_DOUBLE_R_RANGE("CH2 Mixer Volume",
|
|
TAS5717_CH2_LEFT_CH_MIX_REG,
|
|
TAS5717_CH2_RIGHT_CH_MIX_REG,
|
|
16, 0, 0x80, 0),
|
|
|
|
/*
|
|
* The biquads are named according to the register names.
|
|
* Please note that TI's TAS57xx Graphical Development Environment
|
|
* tool names them different.
|
|
*/
|
|
BIQUAD_COEFS("CH1 - Biquad 0", TAS5717_CH1_BQ0_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 1", TAS5717_CH1_BQ1_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 2", TAS5717_CH1_BQ2_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 3", TAS5717_CH1_BQ3_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 4", TAS5717_CH1_BQ4_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 5", TAS5717_CH1_BQ5_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 6", TAS5717_CH1_BQ6_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 7", TAS5717_CH1_BQ7_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 8", TAS5717_CH1_BQ8_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 9", TAS5717_CH1_BQ9_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 10", TAS5717_CH1_BQ10_REG),
|
|
BIQUAD_COEFS("CH1 - Biquad 11", TAS5717_CH1_BQ11_REG),
|
|
|
|
BIQUAD_COEFS("CH2 - Biquad 0", TAS5717_CH2_BQ0_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 1", TAS5717_CH2_BQ1_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 2", TAS5717_CH2_BQ2_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 3", TAS5717_CH2_BQ3_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 4", TAS5717_CH2_BQ4_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 5", TAS5717_CH2_BQ5_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 6", TAS5717_CH2_BQ6_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 7", TAS5717_CH2_BQ7_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 8", TAS5717_CH2_BQ8_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 9", TAS5717_CH2_BQ9_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 10", TAS5717_CH2_BQ10_REG),
|
|
BIQUAD_COEFS("CH2 - Biquad 11", TAS5717_CH2_BQ11_REG),
|
|
|
|
BIQUAD_COEFS("CH3 - Biquad 0", TAS5717_CH3_BQ0_REG),
|
|
BIQUAD_COEFS("CH3 - Biquad 1", TAS5717_CH3_BQ1_REG),
|
|
|
|
BIQUAD_COEFS("CH4 - Biquad 0", TAS5717_CH4_BQ0_REG),
|
|
BIQUAD_COEFS("CH4 - Biquad 1", TAS5717_CH4_BQ1_REG),
|
|
};
|
|
|
|
static const struct reg_default tas5717_reg_defaults[] = {
|
|
{ 0x04, 0x05 },
|
|
{ 0x05, 0x40 },
|
|
{ 0x06, 0x00 },
|
|
{ 0x07, 0x03ff },
|
|
{ 0x08, 0x00c0 },
|
|
{ 0x09, 0x00c0 },
|
|
{ 0x1b, 0x82 },
|
|
{ TAS5717_CH1_RIGHT_CH_MIX_REG, 0x0 },
|
|
{ TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000},
|
|
{ TAS5717_CH2_LEFT_CH_MIX_REG, 0x0 },
|
|
{ TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000},
|
|
};
|
|
|
|
static const struct regmap_config tas5717_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.max_register = 0xff,
|
|
.reg_read = tas571x_reg_read,
|
|
.reg_write = tas571x_reg_write,
|
|
.reg_defaults = tas5717_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(tas5717_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.wr_table = &tas571x_write_regs,
|
|
.volatile_table = &tas571x_volatile_regs,
|
|
};
|
|
|
|
/* This entry is reused for tas5719 as the software interface is identical. */
|
|
static const struct tas571x_chip tas5717_chip = {
|
|
.supply_names = tas5717_supply_names,
|
|
.num_supply_names = ARRAY_SIZE(tas5717_supply_names),
|
|
.controls = tas5717_controls,
|
|
.num_controls = ARRAY_SIZE(tas5717_controls),
|
|
.regmap_config = &tas5717_regmap_config,
|
|
.vol_reg_size = 2,
|
|
};
|
|
|
|
static const char *const tas5721_supply_names[] = {
|
|
"AVDD",
|
|
"DVDD",
|
|
"DRVDD",
|
|
"PVDD",
|
|
};
|
|
|
|
static const struct snd_kcontrol_new tas5721_controls[] = {
|
|
SOC_SINGLE_TLV("Master Volume",
|
|
TAS571X_MVOL_REG,
|
|
0, 0xff, 1, tas5711_volume_tlv),
|
|
SOC_DOUBLE_R_TLV("Speaker Volume",
|
|
TAS571X_CH1_VOL_REG,
|
|
TAS571X_CH2_VOL_REG,
|
|
0, 0xff, 1, tas5711_volume_tlv),
|
|
SOC_DOUBLE("Speaker Switch",
|
|
TAS571X_SOFT_MUTE_REG,
|
|
TAS571X_SOFT_MUTE_CH1_SHIFT, TAS571X_SOFT_MUTE_CH2_SHIFT,
|
|
1, 1),
|
|
};
|
|
|
|
static const struct reg_default tas5721_reg_defaults[] = {
|
|
{TAS571X_CLK_CTRL_REG, 0x6c},
|
|
{TAS571X_DEV_ID_REG, 0x00},
|
|
{TAS571X_ERR_STATUS_REG, 0x00},
|
|
{TAS571X_SYS_CTRL_1_REG, 0xa0},
|
|
{TAS571X_SDI_REG, 0x05},
|
|
{TAS571X_SYS_CTRL_2_REG, 0x40},
|
|
{TAS571X_SOFT_MUTE_REG, 0x00},
|
|
{TAS571X_MVOL_REG, 0xff},
|
|
{TAS571X_CH1_VOL_REG, 0x30},
|
|
{TAS571X_CH2_VOL_REG, 0x30},
|
|
{TAS571X_CH3_VOL_REG, 0x30},
|
|
{TAS571X_VOL_CFG_REG, 0x91},
|
|
{TAS571X_MODULATION_LIMIT_REG, 0x02},
|
|
{TAS571X_IC_DELAY_CH1_REG, 0xac},
|
|
{TAS571X_IC_DELAY_CH2_REG, 0x54},
|
|
{TAS571X_IC_DELAY_CH3_REG, 0xac},
|
|
{TAS571X_IC_DELAY_CH4_REG, 0x54},
|
|
{TAS571X_PWM_CH_SDN_GROUP_REG, 0x30},
|
|
{TAS571X_START_STOP_PERIOD_REG, 0x0f},
|
|
{TAS571X_OSC_TRIM_REG, 0x82},
|
|
{TAS571X_BKND_ERR_REG, 0x02},
|
|
{TAS571X_INPUT_MUX_REG, 0x17772},
|
|
{TAS571X_CH4_SRC_SELECT_REG, 0x4303},
|
|
{TAS571X_PWM_MUX_REG, 0x1021345},
|
|
};
|
|
|
|
static const struct regmap_config tas5721_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.max_register = 0xff,
|
|
.reg_read = tas571x_reg_read,
|
|
.reg_write = tas571x_reg_write,
|
|
.reg_defaults = tas5721_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(tas5721_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.wr_table = &tas571x_write_regs,
|
|
.volatile_table = &tas571x_volatile_regs,
|
|
};
|
|
|
|
static const char *const tas5733_supply_names[] = {
|
|
"AVDD",
|
|
"DVDD",
|
|
"PVDD",
|
|
};
|
|
|
|
static const struct reg_default tas5733_reg_defaults[] = {
|
|
{TAS571X_CLK_CTRL_REG, 0x6c},
|
|
{TAS571X_DEV_ID_REG, 0x00},
|
|
{TAS571X_ERR_STATUS_REG, 0x00},
|
|
{TAS571X_SYS_CTRL_1_REG, 0xa0},
|
|
{TAS571X_SDI_REG, 0x05},
|
|
{TAS571X_SYS_CTRL_2_REG, 0x40},
|
|
{TAS571X_SOFT_MUTE_REG, 0x07},
|
|
{TAS571X_MVOL_REG, 0x03ff},
|
|
{TAS571X_CH1_VOL_REG, 0x00c0},
|
|
{TAS571X_CH2_VOL_REG, 0x00c0},
|
|
{TAS571X_CH3_VOL_REG, 0x00c0},
|
|
{TAS571X_VOL_CFG_REG, 0xf0},
|
|
{TAS571X_MODULATION_LIMIT_REG, 0x07},
|
|
{TAS571X_IC_DELAY_CH1_REG, 0xb8},
|
|
{TAS571X_IC_DELAY_CH2_REG, 0x60},
|
|
{TAS571X_IC_DELAY_CH3_REG, 0xa0},
|
|
{TAS571X_IC_DELAY_CH4_REG, 0x48},
|
|
{TAS571X_PWM_CH_SDN_GROUP_REG, 0x30},
|
|
{TAS571X_START_STOP_PERIOD_REG, 0x68},
|
|
{TAS571X_OSC_TRIM_REG, 0x82},
|
|
{TAS571X_BKND_ERR_REG, 0x02},
|
|
{TAS571X_INPUT_MUX_REG, 0x00897772},
|
|
{TAS571X_PWM_MUX_REG, 0x01021345},
|
|
{TAS5717_CH1_RIGHT_CH_MIX_REG, 0x00},
|
|
{TAS5717_CH1_LEFT_CH_MIX_REG, 0x800000},
|
|
{TAS5717_CH2_LEFT_CH_MIX_REG, 0x00},
|
|
{TAS5717_CH2_RIGHT_CH_MIX_REG, 0x800000},
|
|
};
|
|
|
|
static const struct regmap_config tas5733_regmap_config = {
|
|
.reg_bits = 8,
|
|
.val_bits = 32,
|
|
.max_register = 0xff,
|
|
.reg_read = tas571x_reg_read,
|
|
.reg_write = tas571x_reg_write,
|
|
.reg_defaults = tas5733_reg_defaults,
|
|
.num_reg_defaults = ARRAY_SIZE(tas5733_reg_defaults),
|
|
.cache_type = REGCACHE_RBTREE,
|
|
.wr_table = &tas571x_write_regs,
|
|
.volatile_table = &tas571x_volatile_regs,
|
|
};
|
|
|
|
static const struct tas571x_chip tas5733_chip = {
|
|
.supply_names = tas5733_supply_names,
|
|
.num_supply_names = ARRAY_SIZE(tas5733_supply_names),
|
|
.controls = tas5717_controls,
|
|
.num_controls = ARRAY_SIZE(tas5717_controls),
|
|
.regmap_config = &tas5733_regmap_config,
|
|
.vol_reg_size = 2,
|
|
};
|
|
|
|
static const struct tas571x_chip tas5721_chip = {
|
|
.supply_names = tas5721_supply_names,
|
|
.num_supply_names = ARRAY_SIZE(tas5721_supply_names),
|
|
.controls = tas5721_controls,
|
|
.num_controls = ARRAY_SIZE(tas5721_controls),
|
|
.regmap_config = &tas5721_regmap_config,
|
|
.vol_reg_size = 1,
|
|
};
|
|
|
|
static const struct snd_soc_dapm_widget tas571x_dapm_widgets[] = {
|
|
SND_SOC_DAPM_DAC("DACL", NULL, SND_SOC_NOPM, 0, 0),
|
|
SND_SOC_DAPM_DAC("DACR", NULL, SND_SOC_NOPM, 0, 0),
|
|
|
|
SND_SOC_DAPM_OUTPUT("OUT_A"),
|
|
SND_SOC_DAPM_OUTPUT("OUT_B"),
|
|
SND_SOC_DAPM_OUTPUT("OUT_C"),
|
|
SND_SOC_DAPM_OUTPUT("OUT_D"),
|
|
};
|
|
|
|
static const struct snd_soc_dapm_route tas571x_dapm_routes[] = {
|
|
{ "DACL", NULL, "Playback" },
|
|
{ "DACR", NULL, "Playback" },
|
|
|
|
{ "OUT_A", NULL, "DACL" },
|
|
{ "OUT_B", NULL, "DACL" },
|
|
{ "OUT_C", NULL, "DACR" },
|
|
{ "OUT_D", NULL, "DACR" },
|
|
};
|
|
|
|
static const struct snd_soc_component_driver tas571x_component = {
|
|
.set_bias_level = tas571x_set_bias_level,
|
|
.dapm_widgets = tas571x_dapm_widgets,
|
|
.num_dapm_widgets = ARRAY_SIZE(tas571x_dapm_widgets),
|
|
.dapm_routes = tas571x_dapm_routes,
|
|
.num_dapm_routes = ARRAY_SIZE(tas571x_dapm_routes),
|
|
.use_pmdown_time = 1,
|
|
.endianness = 1,
|
|
};
|
|
|
|
static struct snd_soc_dai_driver tas571x_dai = {
|
|
.name = "tas571x-hifi",
|
|
.playback = {
|
|
.stream_name = "Playback",
|
|
.channels_min = 2,
|
|
.channels_max = 2,
|
|
.rates = SNDRV_PCM_RATE_8000_48000,
|
|
.formats = SNDRV_PCM_FMTBIT_S32_LE |
|
|
SNDRV_PCM_FMTBIT_S24_LE |
|
|
SNDRV_PCM_FMTBIT_S16_LE,
|
|
},
|
|
.ops = &tas571x_dai_ops,
|
|
};
|
|
|
|
static int tas571x_i2c_probe(struct i2c_client *client)
|
|
{
|
|
struct tas571x_private *priv;
|
|
struct device *dev = &client->dev;
|
|
int i, ret;
|
|
|
|
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
i2c_set_clientdata(client, priv);
|
|
|
|
priv->chip = i2c_get_match_data(client);
|
|
|
|
priv->mclk = devm_clk_get(dev, "mclk");
|
|
if (IS_ERR(priv->mclk) && PTR_ERR(priv->mclk) != -ENOENT) {
|
|
dev_err(dev, "Failed to request mclk: %ld\n",
|
|
PTR_ERR(priv->mclk));
|
|
return PTR_ERR(priv->mclk);
|
|
}
|
|
|
|
if (WARN_ON(priv->chip->num_supply_names > TAS571X_MAX_SUPPLIES))
|
|
return -EINVAL;
|
|
for (i = 0; i < priv->chip->num_supply_names; i++)
|
|
priv->supplies[i].supply = priv->chip->supply_names[i];
|
|
|
|
ret = devm_regulator_bulk_get(dev, priv->chip->num_supply_names,
|
|
priv->supplies);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to get supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
ret = regulator_bulk_enable(priv->chip->num_supply_names,
|
|
priv->supplies);
|
|
if (ret) {
|
|
dev_err(dev, "Failed to enable supplies: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
priv->regmap = devm_regmap_init(dev, NULL, client,
|
|
priv->chip->regmap_config);
|
|
if (IS_ERR(priv->regmap)) {
|
|
ret = PTR_ERR(priv->regmap);
|
|
goto disable_regs;
|
|
}
|
|
|
|
priv->pdn_gpio = devm_gpiod_get_optional(dev, "pdn", GPIOD_OUT_LOW);
|
|
if (IS_ERR(priv->pdn_gpio)) {
|
|
dev_err(dev, "error requesting pdn_gpio: %ld\n",
|
|
PTR_ERR(priv->pdn_gpio));
|
|
ret = PTR_ERR(priv->pdn_gpio);
|
|
goto disable_regs;
|
|
}
|
|
|
|
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(priv->reset_gpio)) {
|
|
dev_err(dev, "error requesting reset_gpio: %ld\n",
|
|
PTR_ERR(priv->reset_gpio));
|
|
ret = PTR_ERR(priv->reset_gpio);
|
|
goto disable_regs;
|
|
} else if (priv->reset_gpio) {
|
|
/* pulse the active low reset line for ~100us */
|
|
usleep_range(100, 200);
|
|
gpiod_set_value(priv->reset_gpio, 0);
|
|
usleep_range(13500, 20000);
|
|
}
|
|
|
|
ret = regmap_write(priv->regmap, TAS571X_OSC_TRIM_REG, 0);
|
|
if (ret)
|
|
goto disable_regs;
|
|
|
|
usleep_range(50000, 60000);
|
|
|
|
memcpy(&priv->component_driver, &tas571x_component, sizeof(priv->component_driver));
|
|
priv->component_driver.controls = priv->chip->controls;
|
|
priv->component_driver.num_controls = priv->chip->num_controls;
|
|
|
|
if (priv->chip->vol_reg_size == 2) {
|
|
/*
|
|
* The master volume defaults to 0x3ff (mute), but we ignore
|
|
* (zero) the LSB because the hardware step size is 0.125 dB
|
|
* and TLV_DB_SCALE_ITEM has a resolution of 0.01 dB.
|
|
*/
|
|
ret = regmap_update_bits(priv->regmap, TAS571X_MVOL_REG, 1, 0);
|
|
if (ret)
|
|
goto disable_regs;
|
|
}
|
|
|
|
ret = devm_snd_soc_register_component(&client->dev,
|
|
&priv->component_driver,
|
|
&tas571x_dai, 1);
|
|
if (ret)
|
|
goto disable_regs;
|
|
|
|
return ret;
|
|
|
|
disable_regs:
|
|
regulator_bulk_disable(priv->chip->num_supply_names, priv->supplies);
|
|
return ret;
|
|
}
|
|
|
|
static void tas571x_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct tas571x_private *priv = i2c_get_clientdata(client);
|
|
|
|
regulator_bulk_disable(priv->chip->num_supply_names, priv->supplies);
|
|
}
|
|
|
|
static const struct of_device_id tas571x_of_match[] __maybe_unused = {
|
|
{ .compatible = "ti,tas5707", .data = &tas5707_chip, },
|
|
{ .compatible = "ti,tas5711", .data = &tas5711_chip, },
|
|
{ .compatible = "ti,tas5717", .data = &tas5717_chip, },
|
|
{ .compatible = "ti,tas5719", .data = &tas5717_chip, },
|
|
{ .compatible = "ti,tas5721", .data = &tas5721_chip, },
|
|
{ .compatible = "ti,tas5733", .data = &tas5733_chip, },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tas571x_of_match);
|
|
|
|
static const struct i2c_device_id tas571x_i2c_id[] = {
|
|
{ "tas5707", (kernel_ulong_t) &tas5707_chip },
|
|
{ "tas5711", (kernel_ulong_t) &tas5711_chip },
|
|
{ "tas5717", (kernel_ulong_t) &tas5717_chip },
|
|
{ "tas5719", (kernel_ulong_t) &tas5717_chip },
|
|
{ "tas5721", (kernel_ulong_t) &tas5721_chip },
|
|
{ "tas5733", (kernel_ulong_t) &tas5733_chip },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, tas571x_i2c_id);
|
|
|
|
static struct i2c_driver tas571x_i2c_driver = {
|
|
.driver = {
|
|
.name = "tas571x",
|
|
.of_match_table = of_match_ptr(tas571x_of_match),
|
|
},
|
|
.probe = tas571x_i2c_probe,
|
|
.remove = tas571x_i2c_remove,
|
|
.id_table = tas571x_i2c_id,
|
|
};
|
|
module_i2c_driver(tas571x_i2c_driver);
|
|
|
|
MODULE_DESCRIPTION("ASoC TAS571x driver");
|
|
MODULE_AUTHOR("Kevin Cernekee <cernekee@chromium.org>");
|
|
MODULE_LICENSE("GPL");
|