ASoC: tas2781: Add a debugfs node for acoustic tuning
authorShenghao Ding <shenghao-ding@ti.com>
Wed, 7 May 2025 09:46:15 +0000 (17:46 +0800)
committerMark Brown <broonie@kernel.org>
Wed, 7 May 2025 11:35:30 +0000 (20:35 +0900)
"Acoustic Tuning" debugfs node is a bridge to the acoustic tuning tool
which can tune the chips' acoustic effect.

Signed-off-by: Shenghao Ding <shenghao-ding@ti.com>
Link: https://patch.msgid.link/20250507094616.210-1-shenghao-ding@ti.com
Signed-off-by: Mark Brown <broonie@kernel.org>
include/sound/tas2781.h
sound/soc/codecs/tas2781-i2c.c

index eff011444cc82ddd2b9b6a93bf4ad4e2fbe014c5..07ea38fba03b0384b89e5a38e0324fc0ff8afc90 100644 (file)
@@ -159,10 +159,33 @@ struct calidata {
        unsigned int cali_dat_sz_per_dev;
 };
 
+/*
+ * To enable CONFIG_SND_SOC_TAS2781_ACOUST_I2C will create a bridge to the
+ * acoustic tuning tool which can tune the chips' acoustic effect. Due to the
+ * whole directly exposing the registers, there exist some potential risks. So
+ * this define is invisible in Kconfig, anyone who wants to use acoustic tool
+ * have to edit the source manually.
+ */
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+#define TASDEV_DATA_PAYLOAD_SIZE       128
+struct acoustic_data {
+       unsigned char len;
+       unsigned char id;
+       unsigned char addr;
+       unsigned char book;
+       unsigned char page;
+       unsigned char reg;
+       unsigned char data[TASDEV_DATA_PAYLOAD_SIZE];
+};
+#endif
+
 struct tasdevice_priv {
        struct tasdevice tasdevice[TASDEVICE_MAX_CHANNELS];
        struct tasdevice_rca rcabin;
        struct calidata cali_data;
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+       struct acoustic_data acou_data;
+#endif
        struct tasdevice_fw *fmw;
        struct gpio_desc *speaker_id;
        struct gpio_desc *reset;
index 8d8a84a53766f34689ba471f5a16af576cb5e6e2..68d9fe83212aa575ca818cf092db35a0978def4b 100644 (file)
@@ -14,6 +14,9 @@
 //
 
 #include <linux/crc8.h>
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+#include <linux/debugfs.h>
+#endif
 #include <linux/firmware.h>
 #include <linux/gpio/consumer.h>
 #include <linux/i2c.h>
@@ -1422,10 +1425,150 @@ static int tasdevice_create_cali_ctrls(struct tasdevice_priv *priv)
                nctrls < i ? nctrls : i);
 }
 
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+/*
+ * This debugfs node is a bridge to the acoustic tuning application
+ * tool which can tune the chips' acoustic effect.
+ *
+ * package structure for PPC3 communications:
+ *     Pkg len (1 byte)
+ *     Pkg id (1 byte, 'r' or 'w')
+ *     Dev id (1 byte, i2c address)
+ *     Book id (1 byte)
+ *     Page id (1 byte)
+ *     Reg id (1 byte)
+ *     switch (pkg id) {
+ *     case 'w':
+ *             1 byte, length of data to read
+ *     case 'r':
+ *             data payload (1~128 bytes)
+ *     }
+ */
+static ssize_t acoustic_ctl_read(struct file *file, char __user *to,
+       size_t count, loff_t *ppos)
+{
+       struct snd_soc_component *comp = file->private_data;
+       struct tasdevice_priv *tas_priv = snd_soc_component_get_drvdata(comp);
+       struct acoustic_data *p = &tas_priv->acou_data;
+       int ret = -1;
+
+       if (p->id == 'r' && p->len == count && count <= sizeof(*p))
+               ret = simple_read_from_buffer(to, count, ppos, p, p->len);
+       else
+               dev_err(tas_priv->dev, "Not ready for get.\n");
+       return ret;
+}
+
+static ssize_t acoustic_ctl_write(struct file *file,
+       const char __user *from, size_t count, loff_t *ppos)
+{
+       struct snd_soc_component *comp = file->private_data;
+       struct tasdevice_priv *priv = snd_soc_component_get_drvdata(comp);
+       struct acoustic_data *p = &priv->acou_data;
+       unsigned int max_pkg_len = sizeof(*p);
+       unsigned char *src;
+       int j, len, reg, val;
+       unsigned short chn;
+       int ret = -1;
+
+       if (count > sizeof(*p)) {
+               dev_err(priv->dev, "count(%u) is larger than max(%u).\n",
+                       (unsigned int)count, max_pkg_len);
+               return ret;
+       }
+
+       src = memdup_user(from, count);
+       if (IS_ERR(src))
+               return PTR_ERR(src);
+
+       if (src[0] > max_pkg_len && src[0] != count) {
+               dev_err(priv->dev, "pkg(%u), max(%u), count(%u) dismatch.\n",
+                       src[0], max_pkg_len, (unsigned int)count);
+               ret = 0;
+               goto exit;
+       }
+
+       switch (src[1]) {
+       case 'r':
+               /* length of data to read */
+               len = src[6];
+               break;
+       case 'w':
+               /* Skip 6 bytes for package type and register address */
+               len = src[0] - 6;
+               break;
+       default:
+               dev_err(priv->dev, "%s Wrong code %02x.\n", __func__, src[1]);
+               ret = 0;
+               goto exit;
+       }
+
+       if (len < 1) {
+               dev_err(priv->dev, "pkg fmt invalid %02x.\n", len);
+               ret = 0;
+               goto exit;
+       }
+
+       for (j = 0; j < priv->ndev; j++)
+               if (src[2] == priv->tasdevice[j].dev_addr) {
+                       chn = j;
+                       break;
+               }
+       if (j >= priv->ndev) {
+               dev_err(priv->dev, "no such device 0x%02x.\n", src[2]);
+               ret = 0;
+               goto exit;
+       }
+
+       reg = TASDEVICE_REG(src[3], src[4], src[5]);
+
+       guard(mutex)(&priv->codec_lock);
+
+       if (src[1] == 'w') {
+               if (len > 1)
+                       ret = tasdevice_dev_bulk_write(priv, chn, reg,
+                                &src[6], len);
+               else
+                       ret = tasdevice_dev_write(priv, chn, reg, src[6]);
+       } else {
+               struct acoustic_data *p = &priv->acou_data;
+
+               memcpy(p, src, 6);
+               if (len > 1) {
+                       ret = tasdevice_dev_bulk_read(priv, chn, reg,
+                               p->data, len);
+               } else {
+                       ret = tasdevice_dev_read(priv, chn, reg, &val);
+                       p->data[0] = val;
+               }
+               p->len = len + 6;
+       }
+
+       if (ret)
+               dev_err(priv->dev, "i2c communication error.\n");
+       else
+               ret = count;
+exit:
+       kfree(src);
+       return ret;
+}
+
+static const struct file_operations acoustic_ctl_fops = {
+       .open = simple_open,
+       .read = acoustic_ctl_read,
+       .write = acoustic_ctl_write,
+};
+#endif
+
 static void tasdevice_fw_ready(const struct firmware *fmw,
        void *context)
 {
        struct tasdevice_priv *tas_priv = context;
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+       struct snd_soc_component *comp = tas_priv->codec;
+       struct dentry *debugfs_root = comp->debugfs_root;
+       char *acoustic_debugfs_node;
+#endif
        int ret = 0;
        int i;
 
@@ -1499,6 +1642,17 @@ static void tasdevice_fw_ready(const struct firmware *fmw,
 
        tasdevice_prmg_load(tas_priv, 0);
        tas_priv->cur_prog = 0;
+
+#ifdef CONFIG_SND_SOC_TAS2781_ACOUST_I2C
+       if (tas_priv->name_prefix)
+               acoustic_debugfs_node = devm_kasprintf(tas_priv->dev,
+                       GFP_KERNEL, "%s_acoustic_ctl", tas_priv->name_prefix);
+       else
+               acoustic_debugfs_node = devm_kstrdup(tas_priv->dev,
+                       "acoustic_ctl", GFP_KERNEL);
+       debugfs_create_file(acoustic_debugfs_node, 0644, debugfs_root,
+               comp, &acoustic_ctl_fops);
+#endif
 out:
        if (tas_priv->fw_state == TASDEVICE_RCA_FW_OK) {
                /* If DSP FW fail, DSP kcontrol won't be created. */