diff options
author | Shuming Fan <shumingf@realtek.com> | 2019-12-27 13:44:45 +0800 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2020-01-09 21:22:36 +0000 |
commit | 320b8b0d13b81f3697acff5b6ddb47f88a09c118 (patch) | |
tree | f9c65dea63cbd84e5935ef60a577460c46cc1b09 | |
parent | c23ff4b3ba6547e857427524a5ba577d56249b0d (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/Kconfig | 10 | ||||
-rw-r--r-- | sound/soc/codecs/Makefile | 2 | ||||
-rw-r--r-- | sound/soc/codecs/rt711-sdw.c | 552 | ||||
-rw-r--r-- | sound/soc/codecs/rt711-sdw.h | 281 | ||||
-rw-r--r-- | sound/soc/codecs/rt711.c | 1293 | ||||
-rw-r--r-- | sound/soc/codecs/rt711.h | 227 |
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_ |