summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShuming Fan <shumingf@realtek.com>2019-12-27 13:44:45 +0800
committerMark Brown <broonie@kernel.org>2020-01-09 21:22:36 +0000
commit320b8b0d13b81f3697acff5b6ddb47f88a09c118 (patch)
treef9c65dea63cbd84e5935ef60a577460c46cc1b09
parentc23ff4b3ba6547e857427524a5ba577d56249b0d (diff)
ASoC: rt711: add rt711 codec driver
This is the initial codec driver for rt711. Signed-off-by: Shuming Fan <shumingf@realtek.com> Tested-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> Link: https://lore.kernel.org/r/20191227054445.27223-1-shumingf@realtek.com Signed-off-by: Mark Brown <broonie@kernel.org>
-rw-r--r--sound/soc/codecs/Kconfig10
-rw-r--r--sound/soc/codecs/Makefile2
-rw-r--r--sound/soc/codecs/rt711-sdw.c552
-rw-r--r--sound/soc/codecs/rt711-sdw.h281
-rw-r--r--sound/soc/codecs/rt711.c1293
-rw-r--r--sound/soc/codecs/rt711.h227
6 files changed, 2365 insertions, 0 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 986a31c68992..65b81888ca3d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -165,6 +165,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_RT5670 if I2C
select SND_SOC_RT5677 if I2C && SPI_MASTER
select SND_SOC_RT5682 if I2C
+ select SND_SOC_RT711_SDW if SOUNDWIRE
select SND_SOC_SGTL5000 if I2C
select SND_SOC_SI476X if MFD_SI476X_CORE
select SND_SOC_SIMPLE_AMPLIFIER
@@ -1059,6 +1060,15 @@ config SND_SOC_RT5677_SPI
config SND_SOC_RT5682
tristate
+config SND_SOC_RT711
+ tristate
+
+config SND_SOC_RT711_SDW
+ tristate "Realtek RT711 Codec - SDW"
+ depends on SOUNDWIRE
+ select SND_SOC_RT711
+ select REGMAP_SOUNDWIRE
+
#Freescale sgtl5000 codec
config SND_SOC_SGTL5000
tristate "Freescale SGTL5000 CODEC"
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 495f7e2f63eb..f4dfe033d120 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -173,6 +173,7 @@ snd-soc-rt5670-objs := rt5670.o
snd-soc-rt5677-objs := rt5677.o
snd-soc-rt5677-spi-objs := rt5677-spi.o
snd-soc-rt5682-objs := rt5682.o
+snd-soc-rt711-objs := rt711.o rt711-sdw.o
snd-soc-sgtl5000-objs := sgtl5000.o
snd-soc-alc5623-objs := alc5623.o
snd-soc-alc5632-objs := alc5632.o
@@ -465,6 +466,7 @@ obj-$(CONFIG_SND_SOC_RT5670) += snd-soc-rt5670.o
obj-$(CONFIG_SND_SOC_RT5677) += snd-soc-rt5677.o
obj-$(CONFIG_SND_SOC_RT5677_SPI) += snd-soc-rt5677-spi.o
obj-$(CONFIG_SND_SOC_RT5682) += snd-soc-rt5682.o
+obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o
obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o
obj-$(CONFIG_SND_SOC_SIGMADSP) += snd-soc-sigmadsp.o
obj-$(CONFIG_SND_SOC_SIGMADSP_I2C) += snd-soc-sigmadsp-i2c.o
diff --git a/sound/soc/codecs/rt711-sdw.c b/sound/soc/codecs/rt711-sdw.c
new file mode 100644
index 000000000000..e28dc84ede5b
--- /dev/null
+++ b/sound/soc/codecs/rt711-sdw.c
@@ -0,0 +1,552 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rt711-sdw.c -- rt711 ALSA SoC audio driver
+//
+// Copyright(c) 2019 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_type.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <sound/soc.h>
+#include "rt711.h"
+#include "rt711-sdw.h"
+
+static bool rt711_readable_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x00e0:
+ case 0x00f0:
+ case 0x2012 ... 0x2016:
+ case 0x201a ... 0x2027:
+ case 0x2029 ... 0x202a:
+ case 0x202d ... 0x2034:
+ case 0x2201 ... 0x2204:
+ case 0x2206 ... 0x2212:
+ case 0x2220 ... 0x2223:
+ case 0x2230 ... 0x2239:
+ case 0x2f01 ... 0x2f0f:
+ case 0x3000 ... 0x3fff:
+ case 0x7000 ... 0x7fff:
+ case 0x8300 ... 0x83ff:
+ case 0x9c00 ... 0x9cff:
+ case 0xb900 ... 0xb9ff:
+ case 0x752009:
+ case 0x752011:
+ case 0x75201a:
+ case 0x752045:
+ case 0x752046:
+ case 0x752048:
+ case 0x75204a:
+ case 0x75206b:
+ case 0x75206f:
+ case 0x752080:
+ case 0x752081:
+ case 0x752091:
+ case 0x755800:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool rt711_volatile_register(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0x2016:
+ case 0x201b:
+ case 0x201c:
+ case 0x201d:
+ case 0x201f:
+ case 0x2021:
+ case 0x2023:
+ case 0x2230:
+ case 0x2012 ... 0x2015: /* HD-A read */
+ case 0x202d ... 0x202f: /* BRA */
+ case 0x2201 ... 0x2212: /* i2c debug */
+ case 0x2220 ... 0x2223: /* decoded HD-A */
+ case 0x9c00 ... 0x9cff:
+ case 0xb900 ... 0xb9ff:
+ case 0xff01:
+ case 0x75201a:
+ case 0x752046:
+ case 0x752080:
+ case 0x752081:
+ case 0x755800:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int rt711_sdw_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct device *dev = context;
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned int sdw_data_3, sdw_data_2, sdw_data_1, sdw_data_0;
+ unsigned int reg2 = 0, reg3 = 0, reg4 = 0, mask, nid, val2;
+ unsigned int is_hda_reg = 1, is_index_reg = 0;
+ int ret;
+
+ if (reg > 0xffff)
+ is_index_reg = 1;
+
+ mask = reg & 0xf000;
+
+ if (is_index_reg) { /* index registers */
+ val2 = reg & 0xff;
+ reg = reg >> 8;
+ nid = reg & 0xff;
+ ret = regmap_write(rt711->sdw_regmap, reg, 0);
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, val2);
+ if (ret < 0)
+ return ret;
+
+ reg3 = RT711_PRIV_DATA_R_H | nid;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg3, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg4 = reg3 + 0x1000;
+ reg4 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg4, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x3000) {
+ reg += 0x8000;
+ ret = regmap_write(rt711->sdw_regmap, reg, *val);
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x7000) {
+ reg += 0x2000;
+ reg |= 0x800;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if ((reg & 0xff00) == 0x8300) { /* for R channel */
+ reg2 = reg - 0x1000;
+ reg2 &= ~0x80;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg2, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rt711->sdw_regmap, reg, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x9000) {
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((*val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (*val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0xb000) {
+ ret = regmap_write(rt711->sdw_regmap, reg, *val);
+ if (ret < 0)
+ return ret;
+ } else {
+ ret = regmap_read(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ is_hda_reg = 0;
+ }
+
+ if (is_hda_reg || is_index_reg) {
+ sdw_data_3 = 0;
+ sdw_data_2 = 0;
+ sdw_data_1 = 0;
+ sdw_data_0 = 0;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_3, &sdw_data_3);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_2, &sdw_data_2);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_1, &sdw_data_1);
+ if (ret < 0)
+ return ret;
+ ret = regmap_read(rt711->sdw_regmap,
+ RT711_READ_HDA_0, &sdw_data_0);
+ if (ret < 0)
+ return ret;
+ *val = ((sdw_data_3 & 0xff) << 24) |
+ ((sdw_data_2 & 0xff) << 16) |
+ ((sdw_data_1 & 0xff) << 8) | (sdw_data_0 & 0xff);
+ }
+
+ if (is_hda_reg == 0)
+ dev_dbg(dev, "[%s] %04x => %08x\n", __func__, reg, *val);
+ else if (is_index_reg)
+ dev_dbg(dev, "[%s] %04x %04x %04x %04x => %08x\n",
+ __func__, reg, reg2, reg3, reg4, *val);
+ else
+ dev_dbg(dev, "[%s] %04x %04x => %08x\n",
+ __func__, reg, reg2, *val);
+
+ return 0;
+}
+
+static int rt711_sdw_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct device *dev = context;
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned int reg2 = 0, reg3, reg4, nid, mask, val2;
+ unsigned int is_index_reg = 0;
+ int ret;
+
+ if (reg > 0xffff)
+ is_index_reg = 1;
+
+ mask = reg & 0xf000;
+
+ if (is_index_reg) { /* index registers */
+ val2 = reg & 0xff;
+ reg = reg >> 8;
+ nid = reg & 0xff;
+ ret = regmap_write(rt711->sdw_regmap, reg, 0);
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, val2);
+ if (ret < 0)
+ return ret;
+
+ reg3 = RT711_PRIV_DATA_W_H | nid;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg3, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg4 = reg3 + 0x1000;
+ reg4 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg4, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ is_index_reg = 1;
+ } else if (reg < 0x4fff) {
+ ret = regmap_write(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (reg == RT711_FUNC_RESET) {
+ ret = regmap_write(rt711->sdw_regmap, reg, val);
+ if (ret < 0)
+ return ret;
+ } else if (mask == 0x7000) {
+ ret = regmap_write(rt711->sdw_regmap,
+ reg, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ reg2 = reg + 0x1000;
+ reg2 |= 0x80;
+ ret = regmap_write(rt711->sdw_regmap, reg2, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ } else if ((reg & 0xff00) == 0x8300) { /* for R channel */
+ reg2 = reg - 0x1000;
+ reg2 &= ~0x80;
+ ret = regmap_write(rt711->sdw_regmap,
+ reg2, ((val >> 8) & 0xff));
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rt711->sdw_regmap, reg, (val & 0xff));
+ if (ret < 0)
+ return ret;
+ }
+
+ if (reg2 == 0)
+ dev_dbg(dev, "[%s] %04x <= %04x\n", __func__, reg, val);
+ else if (is_index_reg)
+ dev_dbg(dev, "[%s] %04x %04x %04x %04x <= %04x %04x\n",
+ __func__, reg, reg2, reg3, reg4, val2, val);
+ else
+ dev_dbg(dev, "[%s] %04x %04x <= %04x\n",
+ __func__, reg, reg2, val);
+
+ return 0;
+}
+
+static const struct regmap_config rt711_regmap = {
+ .reg_bits = 24,
+ .val_bits = 32,
+ .readable_reg = rt711_readable_register,
+ .volatile_reg = rt711_volatile_register,
+ .max_register = 0x755800,
+ .reg_defaults = rt711_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(rt711_reg_defaults),
+ .cache_type = REGCACHE_RBTREE,
+ .use_single_read = true,
+ .use_single_write = true,
+ .reg_read = rt711_sdw_read,
+ .reg_write = rt711_sdw_write,
+};
+
+static const struct regmap_config rt711_sdw_regmap = {
+ .name = "sdw",
+ .reg_bits = 32,
+ .val_bits = 8,
+ .readable_reg = rt711_readable_register,
+ .max_register = 0xff01,
+ .cache_type = REGCACHE_NONE,
+ .use_single_read = true,
+ .use_single_write = true,
+};
+
+static int rt711_update_status(struct sdw_slave *slave,
+ enum sdw_slave_status status)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ /* Update the status */
+ rt711->status = status;
+
+ if (status == SDW_SLAVE_UNATTACHED)
+ rt711->hw_init = false;
+
+ /*
+ * Perform initialization only if slave status is present and
+ * hw_init flag is false
+ */
+ if (rt711->hw_init || rt711->status != SDW_SLAVE_ATTACHED)
+ return 0;
+
+ /* perform I/O transfers required for Slave initialization */
+ return rt711_io_init(&slave->dev, slave);
+}
+
+static int rt711_read_prop(struct sdw_slave *slave)
+{
+ struct sdw_slave_prop *prop = &slave->prop;
+ int nval, i, num_of_ports = 1;
+ u32 bit;
+ unsigned long addr;
+ struct sdw_dpn_prop *dpn;
+
+ prop->paging_support = false;
+
+ /* first we need to allocate memory for set bits in port lists */
+ prop->source_ports = 0x14; /* BITMAP: 00010100 */
+ prop->sink_ports = 0x8; /* BITMAP: 00001000 */
+
+ nval = hweight32(prop->source_ports);
+ num_of_ports += nval;
+ prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->src_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->src_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->src_dpn_prop;
+ addr = prop->source_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* do this again for sink now */
+ nval = hweight32(prop->sink_ports);
+ num_of_ports += nval;
+ prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval,
+ sizeof(*prop->sink_dpn_prop),
+ GFP_KERNEL);
+ if (!prop->sink_dpn_prop)
+ return -ENOMEM;
+
+ i = 0;
+ dpn = prop->sink_dpn_prop;
+ addr = prop->sink_ports;
+ for_each_set_bit(bit, &addr, 32) {
+ dpn[i].num = bit;
+ dpn[i].type = SDW_DPN_FULL;
+ dpn[i].simple_ch_prep_sm = true;
+ dpn[i].ch_prep_timeout = 10;
+ i++;
+ }
+
+ /* Allocate port_ready based on num_of_ports */
+ slave->port_ready = devm_kcalloc(&slave->dev, num_of_ports,
+ sizeof(*slave->port_ready),
+ GFP_KERNEL);
+ if (!slave->port_ready)
+ return -ENOMEM;
+
+ /* Initialize completion */
+ for (i = 0; i < num_of_ports; i++)
+ init_completion(&slave->port_ready[i]);
+
+ /* set the timeout values */
+ prop->clk_stop_timeout = 20;
+
+ /* wake-up event */
+ prop->wake_capable = 1;
+
+ return 0;
+}
+
+static int rt711_bus_config(struct sdw_slave *slave,
+ struct sdw_bus_params *params)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+ int ret;
+
+ memcpy(&rt711->params, params, sizeof(*params));
+
+ ret = rt711_clock_config(&slave->dev);
+ if (ret < 0)
+ dev_err(&slave->dev, "Invalid clk config");
+
+ return ret;
+}
+
+static int rt711_interrupt_callback(struct sdw_slave *slave,
+ struct sdw_slave_intr_status *status)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ dev_dbg(&slave->dev,
+ "%s control_port_stat=%x", __func__, status->control_port);
+
+ if (status->control_port & 0x4) {
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_detect_work, msecs_to_jiffies(250));
+ }
+
+ return 0;
+}
+
+static struct sdw_slave_ops rt711_slave_ops = {
+ .read_prop = rt711_read_prop,
+ .interrupt_callback = rt711_interrupt_callback,
+ .update_status = rt711_update_status,
+ .bus_config = rt711_bus_config,
+};
+
+static int rt711_sdw_probe(struct sdw_slave *slave,
+ const struct sdw_device_id *id)
+{
+ struct regmap *sdw_regmap, *regmap;
+
+ /* Assign ops */
+ slave->ops = &rt711_slave_ops;
+
+ /* Regmap Initialization */
+ sdw_regmap = devm_regmap_init_sdw(slave, &rt711_sdw_regmap);
+ if (!sdw_regmap)
+ return -EINVAL;
+
+ regmap = devm_regmap_init(&slave->dev, NULL,
+ &slave->dev, &rt711_regmap);
+ if (!regmap)
+ return -EINVAL;
+
+ rt711_init(&slave->dev, sdw_regmap, regmap, slave);
+
+ return 0;
+}
+
+static int rt711_sdw_remove(struct sdw_slave *slave)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(&slave->dev);
+
+ if (rt711 && rt711->hw_init) {
+ cancel_delayed_work(&rt711->jack_detect_work);
+ cancel_delayed_work(&rt711->jack_btn_check_work);
+ cancel_work_sync(&rt711->calibration_work);
+ }
+
+ return 0;
+}
+
+static const struct sdw_device_id rt711_id[] = {
+ SDW_SLAVE_ENTRY(0x025d, 0x711, 0),
+ {},
+};
+MODULE_DEVICE_TABLE(sdw, rt711_id);
+
+static int rt711_dev_suspend(struct device *dev)
+{
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+
+ if (!rt711->hw_init)
+ return 0;
+
+ regcache_cache_only(rt711->regmap, true);
+
+ return 0;
+}
+
+#define RT711_PROBE_TIMEOUT 2000
+
+static int rt711_dev_resume(struct device *dev)
+{
+ struct sdw_slave *slave = dev_to_sdw_dev(dev);
+ struct rt711_priv *rt711 = dev_get_drvdata(dev);
+ unsigned long time;
+
+ if (!rt711->hw_init)
+ return 0;
+
+ if (!slave->unattach_request)
+ goto regmap_sync;
+
+ time = wait_for_completion_timeout(&slave->initialization_complete,
+ msecs_to_jiffies(RT711_PROBE_TIMEOUT));
+ if (!time) {
+ dev_err(&slave->dev, "Initialization not complete, timed out\n");
+ return -ETIMEDOUT;
+ }
+
+regmap_sync:
+ slave->unattach_request = 0;
+ regcache_cache_only(rt711->regmap, false);
+ regcache_sync_region(rt711->regmap, 0x3000, 0x8fff);
+ regcache_sync_region(rt711->regmap, 0x752009, 0x752091);
+
+ return 0;
+}
+
+static const struct dev_pm_ops rt711_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(rt711_dev_suspend, rt711_dev_resume)
+ SET_RUNTIME_PM_OPS(rt711_dev_suspend, rt711_dev_resume, NULL)
+};
+
+static struct sdw_driver rt711_sdw_driver = {
+ .driver = {
+ .name = "rt711",
+ .owner = THIS_MODULE,
+ .pm = &rt711_pm,
+ },
+ .probe = rt711_sdw_probe,
+ .remove = rt711_sdw_remove,
+ .ops = &rt711_slave_ops,
+ .id_table = rt711_id,
+};
+module_sdw_driver(rt711_sdw_driver);
+
+MODULE_DESCRIPTION("ASoC RT711 SDW driver");
+MODULE_AUTHOR("Shuming Fan <shumingf@realtek.com>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/rt711-sdw.h b/sound/soc/codecs/rt711-sdw.h
new file mode 100644
index 000000000000..43b2b984b29c
--- /dev/null
+++ b/sound/soc/codecs/rt711-sdw.h
@@ -0,0 +1,281 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * rt711-sdw.h -- RT711 ALSA SoC audio driver header
+ *
+ * Copyright(c) 2019 Realtek Semiconductor Corp.
+ */
+
+#ifndef __RT711_SDW_H__
+#define __RT711_SDW_H__
+
+static const struct reg_default rt711_reg_defaults[] = {
+ { 0x0000, 0x00 },
+ { 0x0001, 0x00 },
+ { 0x0002, 0x00 },
+ { 0x0003, 0x00 },
+ { 0x0004, 0x00 },
+ { 0x0005, 0x01 },
+ { 0x0020, 0x00 },
+ { 0x0022, 0x00 },
+ { 0x0023, 0x00 },
+ { 0x0024, 0x00 },
+ { 0x0025, 0x00 },
+ { 0x0026, 0x00 },
+ { 0x0030, 0x00 },
+ { 0x0032, 0x00 },
+ { 0x0033, 0x00 },
+ { 0x0034, 0x00 },
+ { 0x0035, 0x00 },
+ { 0x0036, 0x00 },
+ { 0x0040, 0x00 },
+ { 0x0041, 0x00 },
+ { 0x0042, 0x00 },
+ { 0x0043, 0x00 },
+ { 0x0044, 0x20 },
+ { 0x0045, 0x01 },
+ { 0x0046, 0x01 },
+ { 0x0050, 0x20 },
+ { 0x0051, 0x02 },
+ { 0x0052, 0x5d },
+ { 0x0053, 0x07 },
+ { 0x0054, 0x11 },
+ { 0x0055, 0x00 },
+ { 0x0060, 0x00 },
+ { 0x0070, 0x00 },
+ { 0x0080, 0xc0 },
+ { 0x0088, 0x00 },
+ { 0x00e0, 0x00 },
+ { 0x00e1, 0x00 },
+ { 0x00e2, 0x00 },
+ { 0x00e3, 0x00 },
+ { 0x00e5, 0x00 },
+ { 0x00ee, 0x00 },
+ { 0x00ef, 0x00 },
+ { 0x00f0, 0x00 },
+ { 0x00f1, 0x00 },
+ { 0x00f2, 0x00 },
+ { 0x00f3, 0x00 },
+ { 0x00f4, 0x00 },
+ { 0x00f5, 0x00 },
+ { 0x00fe, 0x00 },
+ { 0x00ff, 0x00 },
+ { 0x0100, 0x00 },
+ { 0x0101, 0x00 },
+ { 0x0102, 0x00 },
+ { 0x0103, 0x00 },
+ { 0x0104, 0x00 },
+ { 0x0105, 0x00 },
+ { 0x0120, 0x00 },
+ { 0x0122, 0x00 },
+ { 0x0123, 0x00 },
+ { 0x0124, 0x00 },
+ { 0x0125, 0x00 },
+ { 0x0126, 0x00 },
+ { 0x0127, 0x00 },
+ { 0x0130, 0x00 },
+ { 0x0132, 0x00 },
+ { 0x0133, 0x00 },
+ { 0x0134, 0x00 },
+ { 0x0135, 0x00 },
+ { 0x0136, 0x00 },
+ { 0x0137, 0x00 },
+ { 0x0200, 0x00 },
+ { 0x0201, 0x00 },
+ { 0x0202, 0x00 },
+ { 0x0203, 0x00 },
+ { 0x0204, 0x00 },
+ { 0x0205, 0x03 },
+ { 0x0220, 0x00 },
+ { 0x0222, 0x00 },
+ { 0x0223, 0x00 },
+ { 0x0224, 0x00 },
+ { 0x0225, 0x00 },
+ { 0x0226, 0x00 },
+ { 0x0227, 0x00 },
+ { 0x0230, 0x00 },
+ { 0x0232, 0x00 },
+ { 0x0233, 0x00 },
+ { 0x0234, 0x00 },
+ { 0x0235, 0x00 },
+ { 0x0236, 0x00 },
+ { 0x0237, 0x00 },
+ { 0x0300, 0x00 },
+ { 0x0301, 0x00 },
+ { 0x0302, 0x20 },
+ { 0x0303, 0x00 },
+ { 0x0304, 0x00 },
+ { 0x0305, 0x03 },
+ { 0x0320, 0x00 },
+ { 0x0322, 0x00 },
+ { 0x0323, 0x00 },
+ { 0x0324, 0x00 },
+ { 0x0325, 0x00 },
+ { 0x0326, 0x00 },
+ { 0x0327, 0x00 },
+ { 0x0330, 0x00 },
+ { 0x0332, 0x00 },
+ { 0x0333, 0x00 },
+ { 0x0334, 0x00 },
+ { 0x0335, 0x00 },
+ { 0x0336, 0x00 },
+ { 0x0337, 0x00 },
+ { 0x0400, 0x00 },
+ { 0x0401, 0x00 },
+ { 0x0402, 0x00 },
+ { 0x0403, 0x00 },
+ { 0x0404, 0x00 },
+ { 0x0405, 0x03 },
+ { 0x0420, 0x00 },
+ { 0x0422, 0x00 },
+ { 0x0423, 0x00 },
+ { 0x0424, 0x00 },
+ { 0x0425, 0x00 },
+ { 0x0426, 0x00 },
+ { 0x0427, 0x00 },
+ { 0x0430, 0x00 },
+ { 0x0432, 0x00 },
+ { 0x0433, 0x00 },
+ { 0x0434, 0x00 },
+ { 0x0435, 0x00 },
+ { 0x0436, 0x00 },
+ { 0x0437, 0x00 },
+ { 0x0f00, 0x00 },
+ { 0x0f01, 0x00 },
+ { 0x0f02, 0x20 },
+ { 0x0f03, 0x00 },
+ { 0x0f04, 0x00 },
+ { 0x0f05, 0x03 },
+ { 0x0f06, 0x00 },
+ { 0x0f07, 0x00 },
+ { 0x0f08, 0x00 },
+ { 0x0f09, 0x00 },
+ { 0x0f10, 0x00 },
+ { 0x0f11, 0x00 },
+ { 0x0f12, 0x00 },
+ { 0x0f13, 0x00 },
+ { 0x0f14, 0x00 },
+ { 0x0f15, 0x00 },
+ { 0x0f16, 0x00 },
+ { 0x0f17, 0x00 },
+ { 0x0f18, 0x00 },
+ { 0x0f19, 0x00 },
+ { 0x0f1a, 0x00 },
+ { 0x0f1b, 0x00 },
+ { 0x0f1c, 0x00 },
+ { 0x0f1d, 0x00 },
+ { 0x0f1e, 0x00 },
+ { 0x0f1f, 0x00 },
+ { 0x0f20, 0x00 },
+ { 0x0f22, 0x00 },
+ { 0x0f23, 0x00 },
+ { 0x0f24, 0x00 },
+ { 0x0f25, 0x00 },
+ { 0x0f26, 0x00 },
+ { 0x0f27, 0x00 },
+ { 0x0f30, 0x00 },
+ { 0x0f32, 0x00 },
+ { 0x0f33, 0x00 },
+ { 0x0f34, 0x00 },
+ { 0x0f35, 0x00 },
+ { 0x0f36, 0x00 },
+ { 0x0f37, 0x00 },
+ { 0x2012, 0x00 },
+ { 0x2013, 0x00 },
+ { 0x2014, 0x00 },
+ { 0x2015, 0x00 },
+ { 0x2016, 0x00 },
+ { 0x201a, 0x00 },
+ { 0x201b, 0x00 },
+ { 0x201c, 0x0c },
+ { 0x201d, 0x00 },
+ { 0x201e, 0x00 },
+ { 0x201f, 0x00 },
+ { 0x2020, 0x00 },
+ { 0x2021, 0x00 },
+ { 0x2022, 0x00 },
+ { 0x2023, 0x00 },
+ { 0x2024, 0x00 },
+ { 0x2025, 0x01 },
+ { 0x2026, 0x00 },
+ { 0x2027, 0x00 },
+ { 0x2029, 0x00 },
+ { 0x202a, 0x00 },
+ { 0x202d, 0x00 },
+ { 0x202e, 0x00 },
+ { 0x202f, 0x00 },
+ { 0x2030, 0x00 },
+ { 0x2031, 0x00 },
+ { 0x2032, 0x00 },
+ { 0x2033, 0x00 },
+ { 0x2034, 0x00 },
+ { 0x2201, 0xc7 },
+ { 0x2202, 0x0c },
+ { 0x2203, 0x22 },
+ { 0x2204, 0x04 },
+ { 0x2206, 0x00 },
+ { 0x2207, 0x00 },
+ { 0x2208, 0x00 },
+ { 0x2209, 0x00 },
+ { 0x220a, 0x00 },
+ { 0x220b, 0x00 },
+ { 0x220c, 0x00 },
+ { 0x220d, 0x04 },
+ { 0x220e, 0x00 },
+ { 0x220f, 0x00 },
+ { 0x2211, 0x01 },
+ { 0x2212, 0x00 },
+ { 0x2220, 0x00 },
+ { 0x2221, 0x00 },
+ { 0x2222, 0x00 },
+ { 0x2223, 0x00 },
+ { 0x2230, 0x00 },
+ { 0x2231, 0x2f },
+ { 0x2232, 0x80 },
+ { 0x2233, 0x00 },
+ { 0x2234, 0x00 },
+ { 0x2235, 0x00 },
+ { 0x2236, 0x00 },
+ { 0x2237, 0x00 },
+ { 0x2238, 0x00 },
+ { 0x2239, 0x00 },
+ { 0x2f01, 0x00 },
+ { 0x2f02, 0x09 },
+ { 0x2f03, 0x00 },
+ { 0x2f04, 0x00 },
+ { 0x2f05, 0x0b },
+ { 0x2f06, 0x01 },
+ { 0x2f07, 0xcf },
+ { 0x2f08, 0x00 },
+ { 0x2f09, 0x00 },
+ { 0x2f0a, 0x00 },
+ { 0x2f0b, 0x00 },
+ { 0x2f0c, 0x00 },
+ { 0x2f0d, 0x00 },
+ { 0x2f0e, 0x00 },
+ { 0x2f0f, 0x00 },
+ { 0x3122, 0x00 },
+ { 0x3123, 0x00 },
+ { 0x7303, 0x57 },
+ { 0x8383, 0x57 },
+ { 0x7308, 0x97 },
+ { 0x8388, 0x97 },
+ { 0x7309, 0x97 },
+ { 0x8389, 0x97 },
+ { 0x7312, 0x00 },
+ { 0x8392, 0x00 },
+ { 0x7313, 0x00 },
+ { 0x8393, 0x00 },
+ { 0x7319, 0x00 },
+ { 0x8399, 0x00 },
+ { 0x752009, 0x1029 },
+ { 0x752011, 0x007a },
+ { 0x75201a, 0x8003 },
+ { 0x752045, 0x5289 },
+ { 0x752048, 0xd049 },
+ { 0x75204a, 0xa83b },
+ { 0x75206b, 0x5064 },
+ { 0x75206f, 0x058b },
+ { 0x752091, 0x0000 },
+};
+
+#endif /* __RT711_SDW_H__ */
diff --git a/sound/soc/codecs/rt711.c b/sound/soc/codecs/rt711.c
new file mode 100644
index 000000000000..3bebba7a63be
--- /dev/null
+++ b/sound/soc/codecs/rt711.c
@@ -0,0 +1,1293 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// rt711.c -- rt711 ALSA SoC audio driver
+//
+// Copyright(c) 2019 Realtek Semiconductor Corp.
+//
+//
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+#include <sound/hda_verbs.h>
+#include <sound/jack.h>
+
+#include "rt711.h"
+
+static int rt711_index_write(struct regmap *regmap,
+ unsigned int nid, unsigned int reg, unsigned int value)
+{
+ int ret;
+ unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg;
+
+ ret = regmap_write(regmap, addr, value);
+ if (ret < 0)
+ pr_err("Failed to set private value: %06x <= %04x ret=%d\n",
+ addr, value, ret);
+
+ return ret;
+}
+
+static int rt711_index_read(struct regmap *regmap,
+ unsigned int nid, unsigned int reg, unsigned int *value)
+{
+ int ret;
+ unsigned int addr = ((RT711_PRIV_INDEX_W_H | nid) << 8) | reg;
+
+ *value = 0;
+ ret = regmap_read(regmap, addr, value);
+ if (ret < 0)
+ pr_err("Failed to get private value: %06x => %04x ret=%d\n",
+ addr, *value, ret);
+
+ return ret;
+}
+
+static int rt711_index_update_bits(struct regmap *regmap, unsigned int nid,
+ unsigned int reg, unsigned int mask, unsigned int val)
+{
+ unsigned int tmp, orig;
+ int ret;
+
+ ret = rt711_index_read(regmap, nid, reg, &orig);
+ if (ret < 0)
+ return ret;
+
+ tmp = orig & ~mask;
+ tmp |= val & mask;
+
+ return rt711_index_write(regmap, nid, reg, tmp);
+}
+
+static void rt711_reset(struct regmap *regmap)
+{
+ regmap_write(regmap, RT711_FUNC_RESET, 0);
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG,
+ RT711_PARA_VERB_CTL, RT711_HIDDEN_REG_SW_RESET,
+ RT711_HIDDEN_REG_SW_RESET);
+}
+
+static int rt711_calibration(struct rt711_priv *rt711)
+{
+ unsigned int val, loop = 0;
+ struct device *dev;
+ struct regmap *regmap = rt711->regmap;
+ int ret = 0;
+
+ mutex_lock(&rt711->calibrate_mutex);
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D0);
+
+ dev = regmap_get_device(regmap);
+
+ /* Calibration manual mode */
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG, RT711_FSM_CTL,
+ 0xf, 0x0);
+
+ /* trigger */
+ rt711_index_update_bits(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER,
+ RT711_DAC_DC_CALI_TRIGGER);
+
+ /* wait for calibration process */
+ rt711_index_read(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, &val);
+
+ while (val & RT711_DAC_DC_CALI_TRIGGER) {
+ if (loop >= 500) {
+ pr_err("%s, calibration time-out!\n",
+ __func__);
+ ret = -ETIMEDOUT;
+ break;
+ }
+ loop++;
+
+ usleep_range(10000, 11000);
+ rt711_index_read(regmap, RT711_VENDOR_CALI,
+ RT711_DAC_DC_CALI_CTL1, &val);
+ }
+
+ /* depop mode */
+ rt711_index_update_bits(regmap, RT711_VENDOR_REG,
+ RT711_FSM_CTL, 0xf, RT711_DEPOP_CTL);
+
+ regmap_write(rt711->regmap,
+ RT711_SET_AUDIO_POWER_STATE, AC_PWRST_D3);
+ mutex_unlock(&rt711->calibrate_mutex);
+
+ dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static unsigned int rt711_button_detect(struct rt711_priv *rt711)
+{
+ unsigned int btn_type = 0, val80, val81;
+ int ret;
+
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_IRQ_FLAG_TABLE1, &val80);
+ if (ret < 0)
+ goto read_error;
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_IRQ_FLAG_TABLE2, &val81);
+ if (ret < 0)
+ goto read_error;
+
+ val80 &= 0x0381;
+ val81 &= 0xff00;
+
+ switch (val80) {
+ case 0x0200:
+ case 0x0100:
+ case 0x0080:
+ btn_type |= SND_JACK_BTN_0;
+ break;
+ case 0x0001:
+ btn_type |= SND_JACK_BTN_3;
+ break;
+ }
+ switch (val81) {
+ case 0x8000:
+ case 0x4000:
+ case 0x2000:
+ btn_type |= SND_JACK_BTN_1;
+ break;
+ case 0x1000:
+ case 0x0800:
+ case 0x0400:
+ btn_type |= SND_JACK_BTN_2;
+ break;
+ case 0x0200:
+ case 0x0100:
+ btn_type |= SND_JACK_BTN_3;
+ break;
+ }
+read_error:
+ return btn_type;
+}
+
+static int rt711_headset_detect(struct rt711_priv *rt711)
+{
+ unsigned int buf, loop = 0;
+ int ret;
+ unsigned int jack_status = 0, reg;
+
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL2, &buf);
+ if (ret < 0)
+ goto io_error;
+
+ while (loop < 500 &&
+ (buf & RT711_COMBOJACK_AUTO_DET_STATUS) == 0) {
+ loop++;
+
+ usleep_range(9000, 10000);
+ ret = rt711_index_read(rt711->regmap, RT711_VENDOR_REG,
+ RT711_COMBO_JACK_AUTO_CTL2, &buf);
+ if (ret < 0)
+ goto io_error;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_error;
+ if ((jack_status & (1 << 31)) == 0)
+ goto remove_error;
+ }
+
+ if (loop >= 500)
+ goto to_error;
+
+ if (buf & RT711_COMBOJACK_AUTO_DET_TRS)
+ rt711->jack_type = SND_JACK_HEADPHONE;
+ else if ((buf & RT711_COMBOJACK_AUTO_DET_CTIA) ||
+ (buf & RT711_COMBOJACK_AUTO_DET_OMTP))
+ rt711->jack_type = SND_JACK_HEADSET;
+
+ return 0;
+
+to_error:
+ ret = -ETIMEDOUT;
+ pr_err_ratelimited("Time-out error in %s\n", __func__);
+ return ret;
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+ return ret;
+remove_error:
+ pr_err_ratelimited("Jack removal in %s\n", __func__);
+ return -ENODEV;
+}
+
+static void rt711_jack_detect_handler(struct work_struct *work)
+{
+ struct rt711_priv *rt711 =
+ container_of(work, struct rt711_priv, jack_detect_work.work);
+ int btn_type = 0, ret;
+ unsigned int jack_status = 0, reg;
+
+ if (!rt711->hs_jack)
+ return;
+
+ if (!rt711->component->card->instantiated)
+ return;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_error;
+
+ /* pin attached */
+ if (jack_status & (1 << 31)) {
+ /* jack in */
+ if (rt711->jack_type == 0) {
+ ret = rt711_headset_detect(rt711);
+ if (ret < 0)
+ return;
+ if (rt711->jack_type == SND_JACK_HEADSET)
+ btn_type = rt711_button_detect(rt711);
+ } else if (rt711->jack_type == SND_JACK_HEADSET) {
+ /* jack is already in, report button event */
+ btn_type = rt711_button_detect(rt711);
+ }
+ } else {
+ /* jack out */
+ rt711->jack_type = 0;
+ }
+
+ dev_dbg(&rt711->slave->dev,
+ "in %s, jack_type=0x%x\n", __func__, rt711->jack_type);
+ dev_dbg(&rt711->slave->dev,
+ "in %s, btn_type=0x%x\n", __func__, btn_type);
+
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type | btn_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ if (btn_type) {
+ /* button released */
+ snd_soc_jack_report(rt711->hs_jack, rt711->jack_type,
+ SND_JACK_HEADSET |
+ SND_JACK_BTN_0 | SND_JACK_BTN_1 |
+ SND_JACK_BTN_2 | SND_JACK_BTN_3);
+
+ mod_delayed_work(system_power_efficient_wq,
+ &rt711->jack_btn_check_work, msecs_to_jiffies(200));
+ }
+
+ return;
+
+io_error:
+ pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret);
+}
+
+static void rt711_btn_check_handler(struct work_struct *work)
+{
+ struct rt711_priv *rt711 = container_of(work, struct rt711_priv,
+ jack_btn_check_work.work);
+ int btn_type = 0, ret;
+ unsigned int jack_status = 0, reg;
+
+ reg = RT711_VERB_GET_PIN_SENSE | RT711_HP_OUT;
+ ret = regmap_read(rt711->regmap, reg, &jack_status);
+ if (ret < 0)
+ goto io_