This patch adds support for Texas Intrument Amplifier TAS642X.
Signed-off-by: Aurelien Chanot chanot.a@gmail.com --- .../devicetree/bindings/sound/tas642x.txt | 27 ++ sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/tas642x.c | 374 +++++++++++++++++++++ sound/soc/codecs/tas642x.h | 32 ++ 5 files changed, 440 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/tas642x.txt create mode 100644 sound/soc/codecs/tas642x.c create mode 100644 sound/soc/codecs/tas642x.h
diff --git a/Documentation/devicetree/bindings/sound/tas642x.txt b/Documentation/devicetree/bindings/sound/tas642x.txt new file mode 100644 index 0000000..57abe6e --- /dev/null +++ b/Documentation/devicetree/bindings/sound/tas642x.txt @@ -0,0 +1,27 @@ +Texas Instruments TAS642X Audio amplifier + +The TAS642X serial control bus communicates through the I2C protocol only. The +serial bus is also used for periodic codec fault checking/reporting during +audio playback. For more product information please see the links below: + +http://www.ti.com/product/TAS6424-Q1 + + +Required properties: + +- compatible: "ti,tas6424" +- reg: I2C slave address + +Optional properties: + +- chan-swap : swap channels 1-2 with 3-4 +- chan-sel: uses channels 1 to 4 (0) or 5 to 8(1) from the TDM input. + +Example: + +tas6424: tas6424@64 { + status = "okay"; + compatible = "ti,tas6424"; + reg = <0x6c>; + chan-sel = <1>; +}; diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c67667b..dce371e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -135,6 +135,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_TAS5086 if I2C select SND_SOC_TAS571X if I2C select SND_SOC_TAS5720 if I2C + select SND_SOC_TAS642X if I2C select SND_SOC_TFA9879 if I2C select SND_SOC_TLV320AIC23_I2C if I2C select SND_SOC_TLV320AIC23_SPI if SPI_MASTER @@ -812,6 +813,10 @@ config SND_SOC_TAS5720 Enable support for Texas Instruments TAS5720L/M high-efficiency mono Class-D audio power amplifiers.
+config SND_SOC_TAS642X + tristate "Texas Instruments TAS6424 speaker amplifier" + depends on 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 958cd49..3f49516 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -142,6 +142,7 @@ snd-soc-sti-sas-objs := sti-sas.o snd-soc-tas5086-objs := tas5086.o snd-soc-tas571x-objs := tas571x.o snd-soc-tas5720-objs := tas5720.o +snd-soc-tas642x-objs := tas642x.o snd-soc-tfa9879-objs := tfa9879.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o @@ -363,6 +364,7 @@ obj-$(CONFIG_SND_SOC_TAS2552) += snd-soc-tas2552.o obj-$(CONFIG_SND_SOC_TAS5086) += snd-soc-tas5086.o obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o +obj-$(CONFIG_SND_SOC_TAS642X) += snd-soc-tas642x.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/tas642x.c b/sound/soc/codecs/tas642x.c new file mode 100644 index 0000000..16f2c94 --- /dev/null +++ b/sound/soc/codecs/tas642x.c @@ -0,0 +1,374 @@ +/* + * TAS642x amplifier audio driver + * + * Copyright (C) 2016 Witekio + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include <linux/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_device.h> +#include <linux/of_gpio.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 "tas642x.h" + +struct tas642x_chip { + struct snd_soc_codec_driver codec_driver; + const struct regmap_config *regmap_config; + int channel_number; +}; + +struct tas642x_private { + struct device *dev; + const struct tas642x_chip *chip; + struct regmap *regmap; + struct clk *mclk; + unsigned int format; + unsigned int chan_sel; /* 0=1-4; 1=5-8 */ + unsigned int chan_swap; /* 0=No; 1=Yes */ +}; + +static int tas642x_reg_write(void *context, unsigned int reg, + unsigned int value) +{ + struct i2c_client *client = context; + unsigned int size; + uint8_t buf[3]; + int ret; + + size = 2; + buf[0] = reg; + buf[1] = value; + + ret = i2c_master_send(client, buf, size); + if (ret == size) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static int tas642x_reg_read(void *context, unsigned int reg, + unsigned int *value) +{ + struct i2c_client *client = context; + uint8_t send_buf, recv_buf[4]; + struct i2c_msg msgs[2]; + unsigned int size; + unsigned int i; + int ret; + + size = 1; + 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; +} + +static int tas6424_set_dai_fmt(struct snd_soc_dai *dai, unsigned int format) +{ + struct tas642x_private *priv = snd_soc_codec_get_drvdata(dai->codec); + + /* set master/slave audio interface. Only support slave */ + switch (format & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + pr_err("Only support Slave mode!\n"); + return -EINVAL; + } + + priv->format = format; + + return 0; +} + +static int tas6424_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct tas642x_private *priv = snd_soc_codec_get_drvdata(dai->codec); + 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 = 0x04; + break; + case SND_SOC_DAIFMT_LEFT_J: + val = 0x05; + break; + case SND_SOC_DAIFMT_DSP_B: + val = 0x06; + break; + default: + pr_info("Invalid format (0x%x)!!\n", + priv->format & SND_SOC_DAIFMT_FORMAT_MASK); + return -EINVAL; + } + + if (params_width(params) < 24) + val += 0x10; + + if (priv->chan_sel) + val += 0x20; + + if (priv->chan_swap > 0) + val += 0x08; + + val = regmap_update_bits(priv->regmap, TAS642X_SAP_CTRL, + TAS642X_SAP_FMT_MSK | + TAS642X_SAP_CH_SWP_MSK | + TAS642X_SAP_TDM_SIZ_MSK | + TAS642X_SAP_TDM_SEL_MSK, + val); + pr_info("%s: regmap return 0x%x\n", __func__, val); + return val; +} + +static const struct snd_soc_dai_ops tas6424_dai_ops = { + .set_fmt = tas6424_set_dai_fmt, + .hw_params = tas6424_hw_params, +}; + +static const struct reg_default tas642x_reg_defaults[] = { + { TAS642X_MODE_CTRL, 0x00 }, + { TAS642X_MISC_CTRL1, 0x32 }, + { TAS642X_MISC_CTRL2, 0x62 }, + { TAS642X_SAP_CTRL, 0x04 }, + { TAS642X_CH_STATE, 0xAA }, + { TAS642X_CH1_VOL, 0xCF }, + { TAS642X_CH2_VOL, 0xCF }, + { TAS642X_CH3_VOL, 0xCF }, + { TAS642X_CH4_VOL, 0xCF }, +}; + + +static const struct regmap_config tas642x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .reg_read = tas642x_reg_read, + .reg_write = tas642x_reg_write, + .reg_defaults = tas642x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(tas642x_reg_defaults), + .cache_type = REGCACHE_RBTREE, +}; + +static const DECLARE_TLV_DB_SCALE(tas642x_volume_tlv, -10350, 50, 1); + +static const struct snd_kcontrol_new tas6424_controls[] = { + SOC_SINGLE("Spk 1 Playback Switch", TAS642X_CH_STATE, 7, 1, 1), + SOC_SINGLE_TLV("Spk 1 Playback Volume", + TAS642X_CH1_VOL, + 0, 0xff, 0, tas642x_volume_tlv), + SOC_SINGLE("Spk 2 Playback Switch", TAS642X_CH_STATE, 5, 1, 1), + SOC_SINGLE_TLV("Spk 2 Playback Volume", + TAS642X_CH2_VOL, + 0, 0xff, 0, tas642x_volume_tlv), + SOC_SINGLE("Spk 3 Playback Switch", TAS642X_CH_STATE, 3, 1, 1), + SOC_SINGLE_TLV("Spk 3 Playback Volume", + TAS642X_CH3_VOL, + 0, 0xff, 0, tas642x_volume_tlv), + SOC_SINGLE("Spk 4 Playback Switch", TAS642X_CH_STATE, 1, 1, 1), + SOC_SINGLE_TLV("Spk 4 Playback Volume", + TAS642X_CH4_VOL, + 0, 0xff, 0, tas642x_volume_tlv), +}; + +static const struct snd_soc_dapm_route tas642x_1_dapm_routes[] = { + { "Speaker 1", NULL, "Playback" }, + { "Speaker 2", NULL, "Playback" }, + { "Speaker 3", NULL, "Playback" }, + { "Speaker 4", NULL, "Playback" }, + { "OUT_1", NULL, "Speaker 1" }, + { "OUT_2", NULL, "Speaker 2" }, + { "OUT_3", NULL, "Speaker 3" }, + { "OUT_4", NULL, "Speaker 4" }, +}; + + +static const struct snd_soc_dapm_widget tas6424_1_dapm_widgets[] = { + SND_SOC_DAPM_DAC("Speaker 1", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Speaker 2", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Speaker 3", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("Speaker 4", NULL, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_OUTPUT("OUT_1"), + SND_SOC_DAPM_OUTPUT("OUT_2"), + SND_SOC_DAPM_OUTPUT("OUT_3"), + SND_SOC_DAPM_OUTPUT("OUT_4"), +}; + + +static int tas642x_codec_probe(struct snd_soc_codec *codec) +{ + pr_info("TAS642X: i2c codec Probe\n"); + /* Set All channels to mute */ + snd_soc_write(codec, TAS642X_CH_STATE, 0xAA); + return 0; +} + +static const struct tas642x_chip tas6424_chip = { + .codec_driver = { + .probe = tas642x_codec_probe, + .component_driver = { + .controls = tas6424_controls, + .num_controls = ARRAY_SIZE(tas6424_controls), + .dapm_widgets = tas6424_1_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tas6424_1_dapm_widgets), + .dapm_routes = tas642x_1_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(tas642x_1_dapm_routes), + }, + }, + .regmap_config = &tas642x_regmap_config, + .channel_number = 4, +}; + +static struct snd_soc_dai_driver tas642x_dai = { + .name = "tas642x-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 4, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .formats = SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tas6424_dai_ops, +}; + +static const struct of_device_id tas642x_of_match[]; + +#if defined(CONFIG_OF) +static void tas642x_pdata_from_of(struct tas642x_private *priv) +{ + struct device_node *np = priv->dev->of_node; + + priv->chan_swap = of_property_read_bool(np, "chan-swap"); + priv->chan_sel = of_property_read_bool(np, "chan-sel"); +} +#else /* CONFIG_OF */ +static void tas642x_pdata_from_of(struct tas642x_private *priv) +{ +} +#endif /* CONFIG_OF */ + +void tas642x_device_init(struct tas642x_private *priv) +{ + dev_set_drvdata(priv->dev, priv); + + if (priv->dev->of_node) + tas642x_pdata_from_of(priv); +} + +static int tas642x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct tas642x_private *priv; + struct device *dev = &client->dev; + const struct of_device_id *of_id; + + dev_info(dev, "i2c Probe\n"); + + priv = (struct tas642x_private *)devm_kzalloc(dev, + sizeof(struct tas642x_private), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + of_id = of_match_device(tas642x_of_match, dev); + if (!of_id) { + dev_err(dev, "Unknown device type\n"); + return -EINVAL; + } + priv->chip = of_id->data; + priv->dev = dev; + i2c_set_clientdata(client, priv); + priv->regmap = devm_regmap_init_i2c(client, priv->chip->regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + + tas642x_device_init(priv); + + dev_info(dev, "i2c Probe Ends!\n"); + return snd_soc_register_codec(&client->dev, &tas6424_chip.codec_driver, + &tas642x_dai, 1); +} + +static int tas642x_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + return 0; +} + +static const struct of_device_id tas642x_of_match[] = { + { .compatible = "ti,tas6424", .data = &tas6424_chip, }, + { } +}; +MODULE_DEVICE_TABLE(of, tas642x_of_match); + +static const struct i2c_device_id tas642x_i2c_id[] = { + { "tas6424", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, tas642x_i2c_id); + +static struct i2c_driver tas642x_i2c_driver = { + .driver = { + .name = "tas642x", + .of_match_table = of_match_ptr(tas642x_of_match), + }, + .probe = tas642x_i2c_probe, + .remove = tas642x_i2c_remove, + .id_table = tas642x_i2c_id, +}; +module_i2c_driver(tas642x_i2c_driver); + +MODULE_DESCRIPTION("ASoC tas642x driver"); +MODULE_AUTHOR("Aurelien Chanot chanot.a@gmail.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/tas642x.h b/sound/soc/codecs/tas642x.h new file mode 100644 index 0000000..e0dec07 --- /dev/null +++ b/sound/soc/codecs/tas642x.h @@ -0,0 +1,32 @@ +/* + * TAS642X amplifier audio driver + * + * Copyright (C) 2016 Witekio + * + * 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. + */ + +#ifndef _TAS642X_H +#define _TAS642X_H + +/* device registers */ +#define TAS642X_MODE_CTRL 0x00 +#define TAS642X_MISC_CTRL1 0x01 +#define TAS642X_MISC_CTRL2 0x02 + +#define TAS642X_SAP_CTRL 0x03 +# define TAS642X_SAP_SAMP_MSK 0xC0 +# define TAS642X_SAP_TDM_SEL_MSK 0x20 +# define TAS642X_SAP_TDM_SIZ_MSK 0x10 +# define TAS642X_SAP_CH_SWP_MSK 0x08 +# define TAS642X_SAP_FMT_MSK 0x07 +#define TAS642X_CH_STATE 0x04 +#define TAS642X_CH1_VOL 0x05 +#define TAS642X_CH2_VOL 0x06 +#define TAS642X_CH3_VOL 0x07 +#define TAS642X_CH4_VOL 0x08 + +#endif /* _TAS642X_H */