clear-pkgs-linux-iot-lts2018/0333-ASoC-tdf8532-NXP-TDF85...

562 lines
13 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: "Wagner, Steffen" <steffen.wagner@intel.com>
Date: Mon, 8 May 2017 21:19:09 +0530
Subject: [PATCH] ASoC: tdf8532: NXP TDF8532 audio class-D amplifier driver
This is a basic driver to register the codec, expose the
codec DAI and control the power mode of the amplifier.
Change-Id: Ie6ab037cd4d6c87e8e139b6d8af6cd4295445bf2
Signed-off-by: Mohit Sinha <mohit.sinha@intel.com>
Signed-off-by: Steffen Wagner <steffen.wagner@intel.com>
Reviewed-on:
Reviewed-by: B, Jayachandran <jayachandran.b@intel.com>
Reviewed-by: Shaik, Kareem M <kareem.m.shaik@intel.com>
Reviewed-by: Koul, Vinod <vinod.koul@intel.com>
Tested-by: Sm, Bhadur A <bhadur.a.sm@intel.com>
---
sound/soc/codecs/Kconfig | 5 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/tdf8532.c | 377 +++++++++++++++++++++++++++++++++++++
sound/soc/codecs/tdf8532.h | 101 ++++++++++
4 files changed, 485 insertions(+)
create mode 100644 sound/soc/codecs/tdf8532.c
create mode 100644 sound/soc/codecs/tdf8532.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b002d79d910e..006be9a90286 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -163,6 +163,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_TAS5720 if I2C
select SND_SOC_TAS6424 if I2C
select SND_SOC_TDA7419 if I2C
+ select SND_SOC_TDF8532 if I2C
select SND_SOC_TFA9879 if I2C
select SND_SOC_TLV320AIC23_I2C if I2C
select SND_SOC_TLV320AIC23_SPI if SPI_MASTER
@@ -1015,6 +1016,10 @@ config SND_SOC_TDA7419
depends on I2C
select REGMAP_I2C
+config SND_SOC_TDF8532
+ tristate
+ 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 5aed74a73acf..b9ec39dfc960 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -179,6 +179,7 @@ snd-soc-tas571x-objs := tas571x.o
snd-soc-tas5720-objs := tas5720.o
snd-soc-tas6424-objs := tas6424.o
snd-soc-tda7419-objs := tda7419.o
+snd-soc-tdf8532-objs := tdf8532.o
snd-soc-tfa9879-objs := tfa9879.o
snd-soc-tlv320aic23-objs := tlv320aic23.o
snd-soc-tlv320aic23-i2c-objs := tlv320aic23-i2c.o
@@ -443,6 +444,7 @@ obj-$(CONFIG_SND_SOC_TAS571X) += snd-soc-tas571x.o
obj-$(CONFIG_SND_SOC_TAS5720) += snd-soc-tas5720.o
obj-$(CONFIG_SND_SOC_TAS6424) += snd-soc-tas6424.o
obj-$(CONFIG_SND_SOC_TDA7419) += snd-soc-tda7419.o
+obj-$(CONFIG_SND_SOC_TDF8532) += snd-soc-tdf8532.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/tdf8532.c b/sound/soc/codecs/tdf8532.c
new file mode 100644
index 000000000000..7a3cca073845
--- /dev/null
+++ b/sound/soc/codecs/tdf8532.c
@@ -0,0 +1,377 @@
+/*
+ * Codec driver for NXP Semiconductors - TDF8532
+ * Copyright (c) 2017, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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/init.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+#include <linux/time.h>
+#include <linux/acpi.h>
+#include <sound/soc.h>
+#include <sound/tlv.h>
+#include <sound/pcm_params.h>
+#include "tdf8532.h"
+
+static int __tdf8532_build_pkt(struct tdf8532_priv *dev_data,
+ va_list valist, u8 *payload)
+{
+ int param;
+ u8 len;
+ u8 *cmd_payload;
+ const u8 cmd_offset = 3;
+
+ payload[HEADER_TYPE] = MSG_TYPE_STX;
+ payload[HEADER_PKTID] = dev_data->pkt_id;
+
+ cmd_payload = &(payload[cmd_offset]);
+
+ param = va_arg(valist, int);
+ len = 0;
+
+ while (param != END) {
+ cmd_payload[len] = param;
+
+ len++;
+
+ param = va_arg(valist, int);
+ }
+
+ payload[HEADER_LEN] = len;
+
+ return len + cmd_offset;
+}
+
+static int __tdf8532_single_write(struct tdf8532_priv *dev_data,
+ int dummy, ...)
+{
+ va_list valist;
+ int ret;
+ u8 len;
+ u8 payload[255];
+
+ va_start(valist, dummy);
+
+ len = __tdf8532_build_pkt(dev_data, valist, payload);
+
+ va_end(valist);
+
+ print_hex_dump_debug("tdf8532-codec: Tx:", DUMP_PREFIX_NONE, 32, 1,
+ payload, len, false);
+ ret = i2c_master_send(dev_data->i2c, payload, len);
+
+ dev_data->pkt_id++;
+
+ if (ret < 0) {
+ dev_err(&(dev_data->i2c->dev),
+ "i2c send packet returned: %d\n", ret);
+
+ return ret;
+ }
+
+ return 0;
+}
+
+
+static uint8_t tdf8532_read_wait_ack(struct tdf8532_priv *dev_data,
+ unsigned long timeout)
+{
+ uint8_t ack_repl[HEADER_SIZE] = {0, 0, 0};
+ unsigned long timeout_point = jiffies + timeout;
+ int ret;
+
+ do {
+ ret = i2c_master_recv(dev_data->i2c, ack_repl, HEADER_SIZE);
+ if (ret < 0)
+ goto out;
+ } while (time_before(jiffies, timeout_point) &&
+ ack_repl[0] != MSG_TYPE_ACK);
+
+ if (ack_repl[0] != MSG_TYPE_ACK)
+ return -ETIME;
+ else
+ return ack_repl[2];
+
+out:
+ return ret;
+}
+
+static uint8_t tdf8532_single_read(struct tdf8532_priv *dev_data,
+ char **repl_buff)
+{
+ int ret;
+ uint8_t len;
+
+ struct device *dev = &(dev_data->i2c->dev);
+
+ ret = tdf8532_read_wait_ack(dev_data, msecs_to_jiffies(ACK_TIMEOUT));
+
+ if (ret < 0) {
+ dev_err(dev,
+ "Error waiting for ACK reply: %d\n", ret);
+ goto out;
+ }
+
+ len = ret + HEADER_SIZE;
+
+ *repl_buff = kzalloc(len, GFP_KERNEL);
+
+ ret = i2c_master_recv(dev_data->i2c, *repl_buff, len);
+
+ print_hex_dump_debug("tdf8532-codec: Rx:", DUMP_PREFIX_NONE, 32, 1,
+ *repl_buff, len, false);
+
+ if (ret < 0 || ret != len) {
+ dev_err(dev,
+ "i2c recv packet returned: %d (expected: %d)\n",
+ ret, len);
+ goto out_free;
+ }
+
+ return len;
+
+out_free:
+ kfree(*repl_buff);
+ repl_buff = NULL;
+out:
+ return ret;
+}
+
+static int tdf8532_get_state(struct tdf8532_priv *dev_data,
+ struct get_dev_status_repl **status_repl)
+{
+ int ret = 0;
+ char *repl_buff = NULL;
+
+ ret = tdf8532_amp_write(dev_data, GET_DEV_STATUS);
+ if (ret < 0)
+ goto out;
+
+ ret = tdf8532_single_read(dev_data, &repl_buff);
+ if (ret < 0)
+ goto out;
+
+ *status_repl = (struct get_dev_status_repl *) repl_buff;
+
+out:
+ return ret;
+}
+
+static int tdf8532_wait_state(struct tdf8532_priv *dev_data, u8 req_state,
+ unsigned long timeout)
+{
+ unsigned long timeout_point = jiffies + msecs_to_jiffies(timeout);
+ int ret;
+ struct get_dev_status_repl *status_repl;
+ struct device *dev = &(dev_data->i2c->dev);
+
+ do {
+ ret = tdf8532_get_state(dev_data, &status_repl);
+ if (ret < 0)
+ goto out;
+
+ print_hex_dump_debug("tdf8532-codec: wait_state: ",
+ DUMP_PREFIX_NONE, 32, 1, status_repl,
+ 6, false);
+ } while (time_before(jiffies, timeout_point)
+ && status_repl->state != req_state);
+
+ if (status_repl->state == req_state)
+ return 0;
+
+ ret = -ETIME;
+
+ dev_err(dev, "tdf8532-codec: state: %u, req_state: %u, ret: %d\n",
+ status_repl->state, req_state, ret);
+
+out:
+ kfree(status_repl);
+ return ret;
+}
+
+static int tdf8532_start_play(struct tdf8532_priv *tdf8532)
+{
+ int ret;
+
+ ret = tdf8532_amp_write(tdf8532, SET_CLK_STATE, CLK_CONNECT);
+ if (ret < 0)
+ return ret;
+
+ ret = tdf8532_amp_write(tdf8532, SET_CHNL_ENABLE,
+ CHNL_MASK(tdf8532->channels));
+
+ if (ret >= 0)
+ ret = tdf8532_wait_state(tdf8532, STATE_PLAY, ACK_TIMEOUT);
+
+ return ret;
+}
+
+
+static int tdf8532_stop_play(struct tdf8532_priv *tdf8532)
+{
+ int ret;
+
+ ret = tdf8532_amp_write(tdf8532, SET_CHNL_DISABLE,
+ CHNL_MASK(tdf8532->channels));
+ if (ret < 0)
+ goto out;
+
+ ret = tdf8532_wait_state(tdf8532, STATE_STBY, ACK_TIMEOUT);
+ if (ret < 0)
+ goto out;
+
+ ret = tdf8532_amp_write(tdf8532, SET_CLK_STATE, CLK_DISCONNECT);
+ if (ret < 0)
+ goto out;
+
+ ret = tdf8532_wait_state(tdf8532, STATE_IDLE, ACK_TIMEOUT);
+
+out:
+ return ret;
+}
+
+
+static int tdf8532_dai_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ int ret = 0;
+ struct snd_soc_component *component = dai->component;
+ struct tdf8532_priv *tdf8532 = snd_soc_component_get_drvdata(component);
+
+ dev_dbg(component->dev, "%s: cmd = %d\n", __func__, cmd);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ ret = tdf8532_start_play(tdf8532);
+ break;
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_STOP:
+ ret = tdf8532_stop_play(tdf8532);
+ break;
+ }
+
+ return ret;
+}
+
+static int tdf8532_mute(struct snd_soc_dai *dai, int mute)
+{
+ struct snd_soc_component *component = dai->component;
+ struct tdf8532_priv *tdf8532 = snd_soc_component_get_drvdata(component);
+
+ dev_dbg(component->dev, "%s\n", __func__);
+
+ if (mute)
+ return tdf8532_amp_write(tdf8532, SET_CHNL_MUTE,
+ CHNL_MASK(CHNL_MAX));
+ else
+ return tdf8532_amp_write(tdf8532, SET_CHNL_UNMUTE,
+ CHNL_MASK(CHNL_MAX));
+}
+
+static const struct snd_soc_dai_ops tdf8532_dai_ops = {
+ .trigger = tdf8532_dai_trigger,
+ .digital_mute = tdf8532_mute,
+};
+
+static struct snd_soc_component_driver soc_component_tdf8532;
+
+static struct snd_soc_dai_driver tdf8532_dai[] = {
+ {
+ .name = "tdf8532-hifi",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 4,
+ .channels_max = 4,
+ .rates = SNDRV_PCM_RATE_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ },
+ .ops = &tdf8532_dai_ops,
+ }
+};
+
+static int tdf8532_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct tdf8532_priv *dev_data;
+ struct device *dev = &(i2c->dev);
+
+ dev_dbg(&i2c->dev, "%s\n", __func__);
+
+ dev_data = devm_kzalloc(dev, sizeof(struct tdf8532_priv), GFP_KERNEL);
+
+ if (!dev_data) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ if (ret < 0)
+ dev_err(&i2c->dev, "Failed to set fast mute option: %d\n", ret);
+
+ dev_data->i2c = i2c;
+ dev_data->pkt_id = 0;
+ dev_data->channels = 4;
+
+ i2c_set_clientdata(i2c, dev_data);
+
+ ret = devm_snd_soc_register_component(&i2c->dev, &soc_component_tdf8532,
+ tdf8532_dai, ARRAY_SIZE(tdf8532_dai));
+ if (ret != 0) {
+ dev_err(&i2c->dev, "Failed to register codec: %d\n", ret);
+ goto out;
+ }
+
+out:
+ return ret;
+}
+
+static int tdf8532_i2c_remove(struct i2c_client *i2c)
+{
+ return 0;
+}
+
+static const struct i2c_device_id tdf8532_i2c_id[] = {
+ { "tdf8532", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, tdf8532_i2c_id);
+
+#if CONFIG_ACPI
+static const struct acpi_device_id tdf8532_acpi_match[] = {
+ {"INT34C3", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(acpi, tdf8532_acpi_match);
+#endif
+
+static struct i2c_driver tdf8532_i2c_driver = {
+ .driver = {
+ .name = "tdf8532-codec",
+ .owner = THIS_MODULE,
+ .acpi_match_table = ACPI_PTR(tdf8532_acpi_match),
+ },
+ .probe = tdf8532_i2c_probe,
+ .remove = tdf8532_i2c_remove,
+ .id_table = tdf8532_i2c_id,
+};
+
+module_i2c_driver(tdf8532_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC NXP Semiconductors TDF8532 driver");
+MODULE_AUTHOR("Steffen Wagner <steffen.wagner@intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/sound/soc/codecs/tdf8532.h b/sound/soc/codecs/tdf8532.h
new file mode 100644
index 000000000000..6e3f2c147eac
--- /dev/null
+++ b/sound/soc/codecs/tdf8532.h
@@ -0,0 +1,101 @@
+/*
+ * tdf8532.h - Codec driver for NXP Semiconductors
+ * Copyright (c) 2017, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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.
+ */
+
+
+#ifndef __TDF8532_H_
+#define __TDF8532_H_
+
+#define ACK_TIMEOUT 300
+
+#define CHNL_MAX 5
+
+#define AMP_MOD 0x80
+#define END -1
+
+#define MSG_TYPE_STX 0x02
+#define MSG_TYPE_NAK 0x15
+#define MSG_TYPE_ACK 0x6
+
+#define HEADER_SIZE 3
+#define HEADER_TYPE 0
+#define HEADER_PKTID 1
+#define HEADER_LEN 2
+
+/* Set commands */
+#define SET_CLK_STATE 0x1A
+#define CLK_DISCONNECT 0x00
+#define CLK_CONNECT 0x01
+
+#define SET_CHNL_ENABLE 0x26
+#define SET_CHNL_DISABLE 0x27
+
+#define SET_CHNL_MUTE 0x42
+#define SET_CHNL_UNMUTE 0x43
+
+struct header_repl {
+ u8 msg_type;
+ u8 pkt_id;
+ u8 len;
+} __packed;
+
+#define GET_IDENT 0xE0
+
+struct get_ident_repl {
+ struct header_repl header;
+ u8 module_id;
+ u8 cmd_id;
+ u8 type_name;
+ u8 hw_major;
+ u8 hw_minor;
+ u8 sw_major;
+ u8 sw_minor;
+ u8 sw_sub;
+} __packed;
+
+#define GET_ERROR 0xE2
+
+struct get_error_repl {
+ struct header_repl header;
+ u8 module_id;
+ u8 cmd_id;
+ u8 last_cmd_id;
+ u8 error;
+ u8 status;
+} __packed;
+
+#define GET_DEV_STATUS 0x80
+
+enum dev_state {STATE_BOOT, STATE_IDLE, STATE_STBY, STATE_LDAG, STATE_PLAY,
+ STATE_PROT, STATE_SDWN, STATE_CLFA, STATE_NONE };
+
+struct get_dev_status_repl {
+ struct header_repl header;
+ u8 module_id;
+ u8 cmd_id;
+ u8 state;
+} __packed;
+
+/* Helpers */
+#define CHNL_MASK(channels) (u8)((0x00FF << channels) >> 8)
+
+#define tdf8532_amp_write(dev_data, ...)\
+ __tdf8532_single_write(dev_data, 0, AMP_MOD, __VA_ARGS__, END)
+
+struct tdf8532_priv {
+ struct i2c_client *i2c;
+ u8 channels;
+ u8 pkt_id;
+};
+
+#endif
--
https://clearlinux.org