[PATCH v2 0/5] regmap/SoundWire/ASoC: Add SoundWire SDCA support
The MIPI SoundWire Device Class standard will define audio functionality beyond the scope of the existing SoundWire 1.2 standard, which is limited to the bus and interface.
The description is inspired by the USB Audio Class, with "functions", "entities", "control selectors", "audio clusters". The main difference with the USB Audio class is that the devices are typically on a motherboard and descriptors stored in platform firmware instead of being retrieved from the device.
The current set of devices managed in this patchset are conformant with the SDCA 0.6 specification and require dedicated drivers since the descriptors and platform firmware specification is not complete at this time. They do however rely on the hierarchical addressing required by the SDCA standard. Future devices conformant with SDCA 1.0 should rely on a class driver.
This series adds support for the hierarchical SDCA addressing and extends regmap. It then provides 3 codecs for RT711-sdca headset codec, RT1316 amplifier and RT715-scda microphone codec.
Note that the release of this code before the formal adoption of the SDCA 1.0 specification was formally endorsed by the MIPI Board to make sure there is no delay for Linux-based support of this specification.
v2: - rt715-sdca: Use rt715_sdca prefix to avoid compiling issue. - rt715-sdca: Merge multiple mute/volume operation into single mute/volume operation - rt711-sdca: Initial ret = 0 as it could be used uninitialized.
Jack Yu (1): ASoC/SoundWire: rt715-sdca: First version of rt715 sdw sdca codec driver
Pierre-Louis Bossart (2): soundwire: SDCA: add helper macro to access controls regmap/SoundWire: sdw: add support for SoundWire 1.2 MBQ
Shuming Fan (2): ASoC/SoundWire: rt1316: Add RT1316 SDCA vendor-specific driver ASoC/SoundWire: rt711-sdca: Add RT711 SDCA vendor-specific driver
drivers/base/regmap/Kconfig | 6 +- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw-mbq.c | 101 ++ include/linux/regmap.h | 35 + include/linux/soundwire/sdw_registers.h | 32 + sound/soc/codecs/Kconfig | 20 + sound/soc/codecs/Makefile | 6 + sound/soc/codecs/rt1316-sdw.c | 756 ++++++++++++ sound/soc/codecs/rt1316-sdw.h | 115 ++ sound/soc/codecs/rt711-sdca-sdw.c | 424 +++++++ sound/soc/codecs/rt711-sdca-sdw.h | 101 ++ sound/soc/codecs/rt711-sdca.c | 1482 +++++++++++++++++++++++ sound/soc/codecs/rt711-sdca.h | 246 ++++ sound/soc/codecs/rt715-sdca-sdw.c | 278 +++++ sound/soc/codecs/rt715-sdca-sdw.h | 170 +++ sound/soc/codecs/rt715-sdca.c | 947 +++++++++++++++ sound/soc/codecs/rt715-sdca.h | 124 ++ 17 files changed, 4843 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regmap-sdw-mbq.c create mode 100644 sound/soc/codecs/rt1316-sdw.c create mode 100644 sound/soc/codecs/rt1316-sdw.h create mode 100644 sound/soc/codecs/rt711-sdca-sdw.c create mode 100644 sound/soc/codecs/rt711-sdca-sdw.h create mode 100644 sound/soc/codecs/rt711-sdca.c create mode 100644 sound/soc/codecs/rt711-sdca.h create mode 100644 sound/soc/codecs/rt715-sdca-sdw.c create mode 100644 sound/soc/codecs/rt715-sdca-sdw.h create mode 100644 sound/soc/codecs/rt715-sdca.c create mode 100644 sound/soc/codecs/rt715-sdca.h
From: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
The upcoming SDCA (SoundWire Device Class Audio) specification defines a hierarchical encoding to interface with Class-defined capabilities.
The specification is not yet accessible to the general public but this information is released with explicit permission from the MIPI Board to avoid delays with SDCA support on Linux platforms.
A block of 64 MBytes of register addresses are allocated to SDCA controls, starting at address 0x40000000. The 26 LSBs which identify individual controls are set based on the following variables:
- Function Number. An SCDA device can be split in up to 8 independent Functions. Each of these Functions is described in the SDCA specification, e.g. Smart Amplifier, Smart Microphone, Simple Microphone, Jack codec, HID, etc.
- Entity Number. Within each Function, an Entity is an identifiable block. Up to 127 Entities are connected in a pre-defined graph (similar to USB), with Entity0 reserved for Function-level configurations. In contrast to USB, the SDCA spec pre-defines Function Types, topologies, and allowed options, i.e. the degree of freedom is not unlimited to limit the possibility of errors in descriptors leading to software quirks.
- Control Selector. Within each Entity, the SDCA specification defines 48 controls such as Mute, Gain, AGC, etc, and 16 implementation defined ones. Some Control Selectors might be used for low-level platform setup, and other exposed to applications and users. Note that the same Control Selector capability, e.g. Latency control, might be located at different offsets in different entities, the Control Selector mapping is Entity-specific.
- Control Number. Some Control Selectors allow channel-specific values to be set, with up to 64 channels allowed. This is mostly used for volume control.
- Current/Next values. Some Control Selectors are 'Dual-Ranked'. Software may either update the Current value directly for immediate effect. Alternatively, software may write into the 'Next' values and update the SoundWire 1.2 'Commit Groups' register to copy 'Next' values into 'Current' ones in a synchronized manner. This is different from bank switching which is typically used to change the bus configuration only.
- MBQ. the Multi-Byte Quantity bit is used to provide atomic updates when accessing more that one byte, for example a 16-bit volume control would be updated consistently, the intermediate values mixing old MSB with new LSB are not applied.
These 6 parameters are used to build a 32-bit address to access the desired Controls. Because of address range, paging is required, but the most often used parameter values are placed in the lower 16 bits of the address. This helps to keep the paging registers constant while updating Controls for a specific Device/Function.
Reviewed-by: Rander Wang rander.wang@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com --- include/linux/soundwire/sdw_registers.h | 32 +++++++++++++++++++++++++ 1 file changed, 32 insertions(+)
diff --git a/include/linux/soundwire/sdw_registers.h b/include/linux/soundwire/sdw_registers.h index 0cb1a22685b8..138bec908c40 100644 --- a/include/linux/soundwire/sdw_registers.h +++ b/include/linux/soundwire/sdw_registers.h @@ -309,4 +309,36 @@ #define SDW_CASC_PORT_MASK_INTSTAT3 1 #define SDW_CASC_PORT_REG_OFFSET_INTSTAT3 2
+/* + * v1.2 device - SDCA address mapping + * + * Spec definition + * Bits Contents + * 31 0 (required by addressing range) + * 30:26 0b10000 (Control Prefix) + * 25 0 (Reserved) + * 24:22 Function Number [2:0] + * 21 Entity[6] + * 20:19 Control Selector[5:4] + * 18 0 (Reserved) + * 17:15 Control Number[5:3] + * 14 Next + * 13 MBQ + * 12:7 Entity[5:0] + * 6:3 Control Selector[3:0] + * 2:0 Control Number[2:0] + */ + +#define SDW_SDCA_CTL(fun, ent, ctl, ch) (BIT(30) | \ + (((fun) & 0x7) << 22) | \ + (((ent) & 0x40) << 15) | \ + (((ent) & 0x3f) << 7) | \ + (((ctl) & 0x30) << 15) | \ + (((ctl) & 0x0f) << 3) | \ + (((ch) & 0x38) << 12) | \ + ((ch) & 0x07)) + +#define SDW_SDCA_MBQ_CTL(reg) ((reg) | BIT(13)) +#define SDW_SDCA_NEXT_CTL(reg) ((reg) | BIT(14)) + #endif /* __SDW_REGISTERS_H */
From: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
The SoundWire 1.1 specification only allowed for reads and writes of bytes. The SoundWire 1.2 specification adds a new capability to transfer "Multi-Byte Quantities" (MBQ) across the bus. The transfers still happens one-byte-at-a-time, but the update is atomic.
For example when writing a 16-bit volume, the first byte transferred is only taken into account when the second byte is successfully transferred.
The mechanism is symmetrical for read and writes: - On a read, the address of the last byte to be read is modified by setting the MBQ bit - On a write, the address of all but the last byte to be written are modified by setting the MBQ bit. The address for the last byte relies on the MBQ bit being cleared.
The current definitions for MBQ-based controls in the SDCA draft standard are limited to 16 bits for volumes, so for now this is the only supported format. An update will be provided if and when support for 24-bit and 32-bit values is specified by the SDCA standard.
One possible objection is that this code could have been handled with regmap-sdw.c. However this is a new spec addition not handled by every SoundWire 1.1 and non-SDCA device, so there's no reason to load code that will never be used.
Also in practice it's extremely unlikely that CONFIG_REGMAP would not be selected with CONFIG_REGMAP_MBQ selected. However there's no functional dependency between the two modules so they can be selected separately.
Reviewed-by: Rander Wang rander.wang@linux.intel.com Reviewed-by: Guennadi Liakhovetski guennadi.liakhovetski@linux.intel.com Reviewed-by: Kai Vehmanen kai.vehmanen@linux.intel.com Signed-off-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com --- drivers/base/regmap/Kconfig | 6 +- drivers/base/regmap/Makefile | 1 + drivers/base/regmap/regmap-sdw-mbq.c | 101 +++++++++++++++++++++++++++ include/linux/regmap.h | 35 ++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 drivers/base/regmap/regmap-sdw-mbq.c
diff --git a/drivers/base/regmap/Kconfig b/drivers/base/regmap/Kconfig index bcb90d8c3960..50b1e2d06a25 100644 --- a/drivers/base/regmap/Kconfig +++ b/drivers/base/regmap/Kconfig @@ -4,7 +4,7 @@ # subsystems should select the appropriate symbols.
config REGMAP - default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM) + default y if (REGMAP_I2C || REGMAP_SPI || REGMAP_SPMI || REGMAP_W1 || REGMAP_AC97 || REGMAP_MMIO || REGMAP_IRQ || REGMAP_SOUNDWIRE || REGMAP_SOUNDWIRE_MBQ || REGMAP_SCCB || REGMAP_I3C || REGMAP_SPI_AVMM) select IRQ_DOMAIN if REGMAP_IRQ bool
@@ -46,6 +46,10 @@ config REGMAP_SOUNDWIRE tristate depends on SOUNDWIRE
+config REGMAP_SOUNDWIRE_MBQ + tristate + depends on SOUNDWIRE + config REGMAP_SCCB tristate depends on I2C diff --git a/drivers/base/regmap/Makefile b/drivers/base/regmap/Makefile index ac1b69ee4051..33f63adb5b3d 100644 --- a/drivers/base/regmap/Makefile +++ b/drivers/base/regmap/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_REGMAP_MMIO) += regmap-mmio.o obj-$(CONFIG_REGMAP_IRQ) += regmap-irq.o obj-$(CONFIG_REGMAP_W1) += regmap-w1.o obj-$(CONFIG_REGMAP_SOUNDWIRE) += regmap-sdw.o +obj-$(CONFIG_REGMAP_SOUNDWIRE_MBQ) += regmap-sdw-mbq.o obj-$(CONFIG_REGMAP_SCCB) += regmap-sccb.o obj-$(CONFIG_REGMAP_I3C) += regmap-i3c.o obj-$(CONFIG_REGMAP_SPI_AVMM) += regmap-spi-avmm.o diff --git a/drivers/base/regmap/regmap-sdw-mbq.c b/drivers/base/regmap/regmap-sdw-mbq.c new file mode 100644 index 000000000000..8ce30650b97c --- /dev/null +++ b/drivers/base/regmap/regmap-sdw-mbq.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2020 Intel Corporation. + +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_registers.h> +#include "internal.h" + +static int regmap_sdw_mbq_write(void *context, unsigned int reg, unsigned int val) +{ + struct device *dev = context; + struct sdw_slave *slave = dev_to_sdw_dev(dev); + int ret; + + ret = sdw_write(slave, SDW_SDCA_MBQ_CTL(reg), (val >> 8) & 0xff); + if (ret < 0) + return ret; + + return sdw_write(slave, reg, val & 0xff); +} + +static int regmap_sdw_mbq_read(void *context, unsigned int reg, unsigned int *val) +{ + struct device *dev = context; + struct sdw_slave *slave = dev_to_sdw_dev(dev); + int read0; + int read1; + + read0 = sdw_read(slave, reg); + if (read0 < 0) + return read0; + + read1 = sdw_read(slave, SDW_SDCA_MBQ_CTL(reg)); + if (read1 < 0) + return read1; + + *val = (read1 << 8) | read0; + + return 0; +} + +static struct regmap_bus regmap_sdw_mbq = { + .reg_read = regmap_sdw_mbq_read, + .reg_write = regmap_sdw_mbq_write, + .reg_format_endian_default = REGMAP_ENDIAN_LITTLE, + .val_format_endian_default = REGMAP_ENDIAN_LITTLE, +}; + +static int regmap_sdw_mbq_config_check(const struct regmap_config *config) +{ + /* MBQ-based controls are only 16-bits for now */ + if (config->val_bits != 16) + return -ENOTSUPP; + + /* Registers are 32 bits wide */ + if (config->reg_bits != 32) + return -ENOTSUPP; + + if (config->pad_bits != 0) + return -ENOTSUPP; + + return 0; +} + +struct regmap *__regmap_init_sdw_mbq(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + int ret; + + ret = regmap_sdw_mbq_config_check(config); + if (ret) + return ERR_PTR(ret); + + return __regmap_init(&sdw->dev, ®map_sdw_mbq, + &sdw->dev, config, lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_sdw_mbq); + +struct regmap *__devm_regmap_init_sdw_mbq(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + int ret; + + ret = regmap_sdw_mbq_config_check(config); + if (ret) + return ERR_PTR(ret); + + return __devm_regmap_init(&sdw->dev, ®map_sdw_mbq, + &sdw->dev, config, lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__devm_regmap_init_sdw_mbq); + +MODULE_DESCRIPTION("Regmap SoundWire MBQ Module"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/regmap.h b/include/linux/regmap.h index e7834d98207f..a652d1474d6a 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -570,6 +570,10 @@ struct regmap *__regmap_init_sdw(struct sdw_slave *sdw, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_sdw_mbq(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_spi_avmm(struct spi_device *spi, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -619,6 +623,10 @@ struct regmap *__devm_regmap_init_sdw(struct sdw_slave *sdw, const struct regmap_config *config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__devm_regmap_init_sdw_mbq(struct sdw_slave *sdw, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__devm_regmap_init_slimbus(struct slim_device *slimbus, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -817,6 +825,19 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__regmap_init_sdw, #config, \ sdw, config)
+/** + * regmap_init_sdw_mbq() - Initialise register map + * + * @sdw: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_sdw_mbq(sdw, config) \ + __regmap_lockdep_wrapper(__regmap_init_sdw_mbq, #config, \ + sdw, config) + /** * regmap_init_spi_avmm() - Initialize register map for Intel SPI Slave * to AVMM Bus Bridge @@ -989,6 +1010,20 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__devm_regmap_init_sdw, #config, \ sdw, config)
+/** + * devm_regmap_init_sdw_mbq() - Initialise managed register map + * + * @sdw: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer + * to a struct regmap. The regmap will be automatically freed by the + * device management code. + */ +#define devm_regmap_init_sdw_mbq(sdw, config) \ + __regmap_lockdep_wrapper(__devm_regmap_init_sdw_mbq, #config, \ + sdw, config) + /** * devm_regmap_init_slimbus() - Initialise managed register map *
On Mon, Nov 30, 2020 at 10:40:17PM +0800, Bard Liao wrote:
From: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com
The SoundWire 1.1 specification only allowed for reads and writes of bytes. The SoundWire 1.2 specification adds a new capability to transfer "Multi-Byte Quantities" (MBQ) across the bus. The transfers still happens one-byte-at-a-time, but the update is atomic.
Please do not submit new versions of already applied patches, please submit incremental updates to the existing code. Modifying existing commits creates problems for other users building on top of those commits so it's best practice to only change pubished git commits if absolutely essential.
From: Jack Yu jack.yu@realtek.com
First version of rt715 sdw sdca codec driver.
Signed-off-by: Jack Yu jack.yu@realtek.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com --- sound/soc/codecs/Kconfig | 7 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt715-sdca-sdw.c | 278 +++++++++ sound/soc/codecs/rt715-sdca-sdw.h | 170 ++++++ sound/soc/codecs/rt715-sdca.c | 947 ++++++++++++++++++++++++++++++ sound/soc/codecs/rt715-sdca.h | 124 ++++ 6 files changed, 1528 insertions(+) create mode 100644 sound/soc/codecs/rt715-sdca-sdw.c create mode 100644 sound/soc/codecs/rt715-sdca-sdw.h create mode 100644 sound/soc/codecs/rt715-sdca.c create mode 100644 sound/soc/codecs/rt715-sdca.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 34c6dd04b85a..e7797f08e057 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -177,6 +177,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_RT700_SDW imply SND_SOC_RT711_SDW imply SND_SOC_RT715_SDW + imply SND_SOC_RT715_SDCA_SDW imply SND_SOC_RT1308_SDW imply SND_SOC_SGTL5000 imply SND_SOC_SI476X @@ -1216,6 +1217,12 @@ config SND_SOC_RT715_SDW select SND_SOC_RT715 select REGMAP_SOUNDWIRE
+config SND_SOC_RT715_SDCA_SDW + tristate "Realtek RT715 SDCA Codec - SDW" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + #Freescale sgtl5000 codec config SND_SOC_SGTL5000 tristate "Freescale SGTL5000 CODEC" diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 11ce98c25d6c..b1683403afb3 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -188,6 +188,7 @@ snd-soc-rt5682-i2c-objs := rt5682-i2c.o snd-soc-rt700-objs := rt700.o rt700-sdw.o snd-soc-rt711-objs := rt711.o rt711-sdw.o snd-soc-rt715-objs := rt715.o rt715-sdw.o +snd-soc-rt715-sdca-objs := rt715-sdca.o rt715-sdca-sdw.o snd-soc-sgtl5000-objs := sgtl5000.o snd-soc-alc5623-objs := alc5623.o snd-soc-alc5632-objs := alc5632.o @@ -498,6 +499,7 @@ obj-$(CONFIG_SND_SOC_RT5682_SDW) += snd-soc-rt5682-sdw.o obj-$(CONFIG_SND_SOC_RT700) += snd-soc-rt700.o obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o +obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.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/rt715-sdca-sdw.c b/sound/soc/codecs/rt715-sdca-sdw.c new file mode 100644 index 000000000000..e73a826ee8e3 --- /dev/null +++ b/sound/soc/codecs/rt715-sdca-sdw.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt715-sdca-sdw.c -- rt715 ALSA SoC audio driver +// +// Copyright(c) 2020 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/soundwire/sdw_registers.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "rt715-sdca.h" +#include "rt715-sdca-sdw.h" + +static bool rt715_sdca_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x201a ... 0x2027: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2200 ... 0x2204: + case 0x2206 ... 0x2212: + case 0x2230 ... 0x2239: + case 0x2f5b: + case SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_ST_CTRL, CH_00): + return true; + default: + return false; + } +} + +static bool rt715_sdca_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x201b: + case 0x201c: + case 0x201d: + case 0x201f: + case 0x2021: + case 0x2023: + case 0x2230: + case 0x202d ... 0x202f: /* BRA */ + case 0x2200 ... 0x2212: /* i2c debug */ + case 0x2f07: + case 0x2f1b ... 0x2f1e: + case 0x2f30 ... 0x2f34: + case 0x2f50 ... 0x2f51: + case 0x2f53 ... 0x2f59: + case 0x2f5c ... 0x2f5f: + case SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_ST_CTRL, CH_00): /* VAD Searching status */ + return true; + default: + return false; + } +} + +static bool rt715_sdca_mbq_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000: + case 0x200002b: + case 0x2000036: + case 0x2000037: + case 0x2000039: + case 0x6100000: + return true; + default: + return false; + } +} + +static bool rt715_sdca_mbq_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000: + return true; + default: + return false; + } +} + +static const struct regmap_config rt715_sdca_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = rt715_sdca_readable_register, + .volatile_reg = rt715_sdca_volatile_register, + .max_register = 0x43ffffff, + .reg_defaults = rt715_reg_defaults_sdca, + .num_reg_defaults = ARRAY_SIZE(rt715_reg_defaults_sdca), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static const struct regmap_config rt715_sdca_mbq_regmap = { + .name = "sdw-mbq", + .reg_bits = 32, + .val_bits = 16, + .readable_reg = rt715_sdca_mbq_readable_register, + .volatile_reg = rt715_sdca_mbq_volatile_register, + .max_register = 0x43ffffff, + .reg_defaults = rt715_mbq_reg_defaults_sdca, + .num_reg_defaults = ARRAY_SIZE(rt715_mbq_reg_defaults_sdca), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt715_sdca_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt715_sdca_priv *rt715 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt715->status = status; + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt715->hw_init || rt715->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt715_sdca_io_init(&slave->dev, slave); +} + +static int rt715_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval, i; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->paging_support = true; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x50;/* BITMAP: 01010000 */ + prop->sink_ports = 0x0; /* BITMAP: 00000000 */ + + nval = hweight32(prop->source_ports); + prop->src_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->src_dpn_prop), + GFP_KERNEL); + if (!prop->src_dpn_prop) + return -ENOMEM; + + dpn = prop->src_dpn_prop; + i = 0; + addr = prop->source_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[i].num = bit; + dpn[i].simple_ch_prep_sm = true; + dpn[i].ch_prep_timeout = 10; + i++; + } + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + return 0; +} + +static struct sdw_slave_ops rt715_sdca_slave_ops = { + .read_prop = rt715_sdca_read_prop, + .update_status = rt715_sdca_update_status, +}; + +static int rt715_sdca_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *mbq_regmap, *regmap; + + slave->ops = &rt715_sdca_slave_ops; + + /* Regmap Initialization */ + mbq_regmap = devm_regmap_init_sdw_mbq(slave, &rt715_sdca_mbq_regmap); + if (!mbq_regmap) + return -EINVAL; + + regmap = devm_regmap_init_sdw(slave, &rt715_sdca_regmap); + if (!regmap) + return -EINVAL; + + return rt715_sdca_init(&slave->dev, mbq_regmap, regmap, slave); +} + +static const struct sdw_device_id rt715_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x715, 0x3, 0x1, 0), + SDW_SLAVE_ENTRY_EXT(0x025d, 0x714, 0x3, 0x1, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt715_sdca_id); + +static int __maybe_unused rt715_dev_suspend(struct device *dev) +{ + struct rt715_sdca_priv *rt715 = dev_get_drvdata(dev); + + if (!rt715->hw_init) + return 0; + + regcache_cache_only(rt715->regmap, true); + regcache_mark_dirty(rt715->regmap); + regcache_cache_only(rt715->mbq_regmap, true); + regcache_mark_dirty(rt715->mbq_regmap); + + return 0; +} + +#define RT715_PROBE_TIMEOUT 2000 + +static int __maybe_unused rt715_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct rt715_sdca_priv *rt715 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt715->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->enumeration_complete, + msecs_to_jiffies(RT715_PROBE_TIMEOUT)); + if (!time) { + dev_err(&slave->dev, "Enumeration not complete, timed out\n"); + return -ETIMEDOUT; + } + +regmap_sync: + slave->unattach_request = 0; + regcache_cache_only(rt715->regmap, false); + regcache_sync_region(rt715->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT715_SDCA_ST_EN, RT715_SDCA_ST_CTRL, + CH_00), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_ST_CTRL, CH_00)); + regcache_cache_only(rt715->mbq_regmap, false); + regcache_sync_region(rt715->mbq_regmap, 0x2000000, 0x61020ff); + regcache_sync_region(rt715->mbq_regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT715_SDCA_ST_EN, RT715_SDCA_ST_CTRL, + CH_00), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_ST_CTRL, CH_00)); + + return 0; +} + +static const struct dev_pm_ops rt715_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt715_dev_suspend, rt715_dev_resume) + SET_RUNTIME_PM_OPS(rt715_dev_suspend, rt715_dev_resume, NULL) +}; + +static struct sdw_driver rt715_sdw_driver = { + .driver = { + .name = "rt715-sdca", + .owner = THIS_MODULE, + .pm = &rt715_pm, + }, + .probe = rt715_sdca_sdw_probe, + .ops = &rt715_sdca_slave_ops, + .id_table = rt715_sdca_id, +}; +module_sdw_driver(rt715_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT715 driver SDW SDCA"); +MODULE_AUTHOR("Jack Yu jack.yu@realtek.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt715-sdca-sdw.h b/sound/soc/codecs/rt715-sdca-sdw.h new file mode 100644 index 000000000000..cd365bb60747 --- /dev/null +++ b/sound/soc/codecs/rt715-sdca-sdw.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt715-sdca-sdw.h -- RT715 ALSA SoC audio driver header + * + * Copyright(c) 2020 Realtek Semiconductor Corp. + */ + +#ifndef __RT715_SDW_SDCA_H__ +#define __RT715_SDW_SDCA_H__ + +#include <linux/soundwire/sdw_registers.h> + +static const struct reg_default rt715_reg_defaults_sdca[] = { + { 0x201a, 0x00 }, + { 0x201e, 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 }, + { 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, 0x0b }, + { 0x2f04, 0x00 }, + { 0x2f05, 0x0e }, + { 0x2f06, 0x01 }, + { 0x2f08, 0x00 }, + { 0x2f09, 0x00 }, + { 0x2f0a, 0x00 }, + { 0x2f0b, 0x00 }, + { 0x2f0c, 0x00 }, + { 0x2f0d, 0x00 }, + { 0x2f0e, 0x12 }, + { 0x2f0f, 0x00 }, + { 0x2f10, 0x00 }, + { 0x2f11, 0x00 }, + { 0x2f12, 0x00 }, + { 0x2f13, 0x00 }, + { 0x2f14, 0x00 }, + { 0x2f15, 0x00 }, + { 0x2f16, 0x00 }, + { 0x2f17, 0x00 }, + { 0x2f18, 0x00 }, + { 0x2f19, 0x03 }, + { 0x2f1a, 0x00 }, + { 0x2f1f, 0x10 }, + { 0x2f20, 0x00 }, + { 0x2f21, 0x00 }, + { 0x2f22, 0x00 }, + { 0x2f23, 0x00 }, + { 0x2f24, 0x00 }, + { 0x2f25, 0x00 }, + { 0x2f52, 0x01 }, + { 0x2f5a, 0x02 }, + { 0x2f5b, 0x05 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_CX_CLK_SEL_EN, + RT715_SDCA_CX_CLK_SEL_CTRL, CH_00), 0x1 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_03), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_04), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_03), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_04), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_EN_CTRL, CH_00), 0x02 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_ST_CTRL, CH_00), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), 0x01 }, +}; + +static const struct reg_default rt715_mbq_reg_defaults_sdca[] = { + { 0x200002b, 0x0420 }, + { 0x2000036, 0x0000 }, + { 0x2000037, 0x0000 }, + { 0x2000039, 0xaa81 }, + { 0x6100000, 0x0100 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_01), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_02), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_03), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_04), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_01), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_02), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_03), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_04), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_01), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_02), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_01), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_02), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_03), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_04), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_05), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_06), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_07), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_08), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_01), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_02), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_03), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_04), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_05), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_06), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_07), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, CH_08), 0x00 }, +}; +#endif /* __RT715_SDW_SDCA_H__ */ diff --git a/sound/soc/codecs/rt715-sdca.c b/sound/soc/codecs/rt715-sdca.c new file mode 100644 index 000000000000..b43ac8559e45 --- /dev/null +++ b/sound/soc/codecs/rt715-sdca.c @@ -0,0 +1,947 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt715-sdca.c -- rt715 ALSA SoC audio driver +// +// Copyright(c) 2020 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/pm_runtime.h> +#include <linux/pm.h> +#include <linux/soundwire/sdw.h> +#include <linux/regmap.h> +#include <linux/slab.h> +#include <linux/platform_device.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 <linux/soundwire/sdw_registers.h> + +#include "rt715-sdca.h" + +static int rt715_sdca_index_write(struct rt715_sdca_priv *rt715, + unsigned int nid, unsigned int reg, unsigned int value) +{ + struct regmap *regmap = rt715->mbq_regmap; + unsigned int addr; + int ret; + + addr = (nid << 20) | reg; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) + dev_err(&rt715->slave->dev, + "Failed to set private value: %08x <= %04x %d\n", ret, addr, + value); + + return ret; +} + +static int rt715_sdca_index_read(struct rt715_sdca_priv *rt715, + unsigned int nid, unsigned int reg, unsigned int *value) +{ + struct regmap *regmap = rt715->mbq_regmap; + unsigned int addr; + int ret; + + addr = (nid << 20) | reg; + + ret = regmap_read(regmap, addr, value); + if (ret < 0) + dev_err(&rt715->slave->dev, + "Failed to get private value: %06x => %04x ret=%d\n", + addr, *value, ret); + + return ret; +} + +static int rt715_sdca_index_update_bits(struct rt715_sdca_priv *rt715, + unsigned int nid, unsigned int reg, unsigned int mask, unsigned int val) +{ + unsigned int tmp; + int ret; + + ret = rt715_sdca_index_read(rt715, nid, reg, &tmp); + if (ret < 0) + return ret; + + set_mask_bits(&tmp, mask, val); + + return rt715_sdca_index_write(rt715, nid, reg, tmp); +} + +/* SDCA Volume/Boost control */ +static int rt715_sdca_set_amp_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + unsigned int val_l, val_r, gain_l_val, gain_r_val, loop_cnt, i, reg; + int ret; + + if (strstr(ucontrol->id.name, "FU02 Capture Volume") || + strstr(ucontrol->id.name, "FU06 Capture Volume")) + loop_cnt = 2; + else if (strstr(ucontrol->id.name, "FU0E Boost") || + strstr(ucontrol->id.name, "FU0C Boost")) + loop_cnt = 4; + else + loop_cnt = 1; + + /* control value to 2s complement */ + /* L channel */ + gain_l_val = ucontrol->value.integer.value[0]; + if (gain_l_val > mc->max) + gain_l_val = mc->max; + val_l = gain_l_val; + + if (mc->shift == 8) { + gain_l_val = (gain_l_val * 10) << mc->shift; + } else { + gain_l_val = + ((abs(gain_l_val - mc->shift) * RT715_SDCA_DB_STEP) << 8) / 1000; + if (val_l <= mc->shift) { + gain_l_val = ~gain_l_val; + gain_l_val += 1; + } + gain_l_val &= 0xffff; + } + + /* R channel */ + gain_r_val = ucontrol->value.integer.value[1]; + if (gain_r_val > mc->max) + gain_r_val = mc->max; + val_r = gain_r_val; + + if (mc->shift == 8) { + gain_r_val = (gain_r_val * 10) << mc->shift; + } else { + gain_r_val = + ((abs(gain_r_val - mc->shift) * RT715_SDCA_DB_STEP) << 8) / 1000; + if (val_r <= mc->shift) { + gain_r_val = ~gain_r_val; + gain_r_val += 1; + } + gain_r_val &= 0xffff; + } + + /* Lch*/ + for (i = 0; i < loop_cnt; i++) { + ret = regmap_write(rt715->mbq_regmap, mc->reg + i * 2, gain_l_val); + if (ret != 0) { + dev_err(component->dev, "Failed to write 0x%x=0x%x\n", + mc->reg + i * 2, gain_l_val); + return ret; + } + } + + /* Rch */ + for (i = 0; i < loop_cnt; i++) { + reg = (i == 3) ? (mc->rreg - 2) | BIT(15) : mc->rreg + i * 2; + ret = regmap_write(rt715->mbq_regmap, reg, gain_r_val); + if (ret != 0) { + dev_err(component->dev, "Failed to write 0x%x=0x%x\n", + reg, gain_r_val); + return ret; + } + } + + return 0; +} + +static int rt715_sdca_set_amp_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + unsigned int val_l, val_r, ctl_l, ctl_r, neg_flag = 0; + int ret; + + ret = regmap_read(rt715->mbq_regmap, mc->reg, &val_l); + if (ret < 0) + dev_err(component->dev, "Failed to read 0x%x, ret=%d\n", mc->reg, ret); + ret = regmap_read(rt715->mbq_regmap, mc->rreg, &val_r); + if (ret < 0) + dev_err(component->dev, "Failed to read 0x%x, ret=%d\n", mc->rreg, + ret); + + /* L channel */ + if (mc->shift == 8) { + ctl_l = (val_l >> mc->shift) / 10; + } else { + ctl_l = val_l; + if (ctl_l & BIT(15)) { + ctl_l = ~(val_l - 1) & 0xffff; + neg_flag = 1; + } + ctl_l *= 1000; + ctl_l >>= 8; + if (neg_flag) + ctl_l = mc->shift - ctl_l / RT715_SDCA_DB_STEP; + else + ctl_l = mc->shift + ctl_l / RT715_SDCA_DB_STEP; + } + + neg_flag = 0; + /* R channel */ + if (mc->shift == 8) { + ctl_r = (val_r >> mc->shift) / 10; + } else { + ctl_r = val_r; + if (ctl_r & BIT(15)) { + ctl_r = ~(val_r - 1) & 0xffff; + neg_flag = 1; + } + ctl_r *= 1000; + ctl_r >>= 8; + if (neg_flag) + ctl_r = mc->shift - ctl_r / RT715_SDCA_DB_STEP; + else + ctl_r = mc->shift + ctl_r / RT715_SDCA_DB_STEP; + } + + ucontrol->value.integer.value[0] = ctl_l; + ucontrol->value.integer.value[1] = ctl_r; + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -17625, 375, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static int rt715_sdca_fu_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 2; + uinfo->value.integer.min = 0; + uinfo->value.integer.max = 1; + + return 0; +} + +static int rt715_sdca_get_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int invert = mc->invert; + unsigned int max = mc->max; + int val; + + val = snd_soc_component_read(component, mc->reg); + if (val < 0) + return -EINVAL; + ucontrol->value.integer.value[0] = invert ? max - val : val; + + val = snd_soc_component_read(component, mc->rreg); + if (val < 0) + return -EINVAL; + ucontrol->value.integer.value[1] = invert ? max - val : val; + + return 0; +} + +static int rt715_sdca_put_volsw(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int val, val2, loop_cnt = 2, i; + unsigned int invert = mc->invert; + unsigned int reg2 = mc->rreg; + unsigned int reg = mc->reg; + unsigned int max = mc->max; + int err; + + val = ucontrol->value.integer.value[0]; + if (invert) + val = max - val; + + val2 = ucontrol->value.integer.value[1]; + if (invert) + val2 = max - val2; + + for (i = 0; i < loop_cnt; i++) { + err = snd_soc_component_write(component, reg + i * 2, val); + if (err < 0) + return err; + err = snd_soc_component_write(component, reg2 + i * 2, val2); + if (err < 0) + return err; + } + + return 0; +} + +#define RT715_SDCA_FU_VALUE(xlreg, xrreg, xshift, xmax, xinvert) \ + ((unsigned long)&(struct soc_mixer_control) \ + {.reg = xlreg, .rreg = xrreg, .shift = xshift, \ + .max = xmax, .invert = xinvert}) + +#define RT715_SDCA_FU_CTRL(xname, reg_left, reg_right, xshift, xmax, xinvert) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = rt715_sdca_fu_info, \ + .get = rt715_sdca_get_volsw, \ + .put = rt715_sdca_put_volsw, \ + .private_value = RT715_SDCA_FU_VALUE(reg_left, reg_right, xshift, \ + xmax, xinvert)} + +#define SOC_DOUBLE_R_EXT(xname, reg_left, reg_right, xshift, xmax, xinvert,\ + xhandler_get, xhandler_put) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .info = snd_soc_info_volsw, \ + .get = xhandler_get, .put = xhandler_put, \ + .private_value = SOC_DOUBLE_R_VALUE(reg_left, reg_right, xshift, \ + xmax, xinvert) } + +static const struct snd_kcontrol_new rt715_sdca_snd_controls[] = { + /* Capture switch */ + SOC_DOUBLE_R("FU0A Capture Switch", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), + 0, 1, 1), + RT715_SDCA_FU_CTRL("FU02 Capture Switch", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), + 0, 1, 1), + RT715_SDCA_FU_CTRL("FU06 Capture Switch", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_MUTE_CTRL, CH_02), + 0, 1, 1), + /* Volume Control */ + SOC_DOUBLE_R_EXT_TLV("FU0A Capture Volume", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC7_27_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_02), + 0x2f, 0x7f, 0, + rt715_sdca_set_amp_gain_get, rt715_sdca_set_amp_gain_put, + in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU02 Capture Volume", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC8_9_VOL, + RT715_SDCA_FU_VOL_CTRL, CH_02), + 0x2f, 0x7f, 0, + rt715_sdca_set_amp_gain_get, rt715_sdca_set_amp_gain_put, + in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU06 Capture Volume", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, + CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_ADC10_11_VOL, + RT715_SDCA_FU_VOL_CTRL, + CH_02), 0x2f, 0x7f, 0, + rt715_sdca_set_amp_gain_get, rt715_sdca_set_amp_gain_put, + in_vol_tlv), + /* MIC Boost Control */ + SOC_DOUBLE_R_EXT_TLV("FU0E Boost", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, + CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_DMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, + CH_02), 8, 3, 0, + rt715_sdca_set_amp_gain_get, rt715_sdca_set_amp_gain_put, + mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU0C Boost", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, + CH_01), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_FU_AMIC_GAIN_EN, + RT715_SDCA_FU_DMIC_GAIN_CTRL, + CH_02), 8, 3, 0, + rt715_sdca_set_amp_gain_get, rt715_sdca_set_amp_gain_put, + mic_vol_tlv), +}; + +static int rt715_sdca_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + unsigned int val, mask_sft; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 12; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + mask_sft = 8; + else if (strstr(ucontrol->id.name, "ADC 24 Mux")) + mask_sft = 4; + else if (strstr(ucontrol->id.name, "ADC 25 Mux")) + mask_sft = 0; + else + return -EINVAL; + + rt715_sdca_index_read(rt715, RT715_VENDOR_HDA_CTL, + RT715_HDA_LEGACY_MUX_CTL1, &val); + val = (val >> mask_sft) & 0xf; + + /* + * The first two indices of ADC Mux 24/25 are routed to the same + * hardware source. ie, ADC Mux 24 0/1 will both connect to MIC2. + * To have a unique set of inputs, we skip the index1 of the muxes. + */ + if ((strstr(ucontrol->id.name, "ADC 24 Mux") || + strstr(ucontrol->id.name, "ADC 25 Mux")) && val > 0) + val -= 1; + ucontrol->value.enumerated.item[0] = val; + + return 0; +} + +static int rt715_sdca_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, mask_sft; + + if (item[0] >= e->items) + return -EINVAL; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 12; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + mask_sft = 8; + else if (strstr(ucontrol->id.name, "ADC 24 Mux")) + mask_sft = 4; + else if (strstr(ucontrol->id.name, "ADC 25 Mux")) + mask_sft = 0; + else + return -EINVAL; + + /* Verb ID = 0x701h, nid = e->reg */ + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + rt715_sdca_index_read(rt715, RT715_VENDOR_HDA_CTL, + RT715_HDA_LEGACY_MUX_CTL1, &val2); + val2 = (val2 >> mask_sft) & 0xf; + + change = val != val2; + + if (change) + rt715_sdca_index_update_bits(rt715, RT715_VENDOR_HDA_CTL, + RT715_HDA_LEGACY_MUX_CTL1, 0xf << mask_sft, val << mask_sft); + + snd_soc_dapm_mux_update_power(dapm, kcontrol, item[0], e, NULL); + + return change; +} + +static const char * const adc_22_23_mux_text[] = { + "MIC1", + "MIC2", + "LINE1", + "LINE2", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +/* + * Due to mux design for nid 24 (MUX_IN3)/25 (MUX_IN4), connection index 0 and + * 1 will be connected to the same dmic source, therefore we skip index 1 to + * avoid misunderstanding on usage of dapm routing. + */ +static int rt715_adc_24_25_values[] = { + 0, + 2, + 3, + 4, + 5, +}; + +static const char * const adc_24_mux_text[] = { + "MIC2", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +static const char * const adc_25_mux_text[] = { + "MIC1", + "DMIC1", + "DMIC2", + "DMIC3", + "DMIC4", +}; + +static SOC_ENUM_SINGLE_DECL(rt715_adc22_enum, SND_SOC_NOPM, 0, + adc_22_23_mux_text); + +static SOC_ENUM_SINGLE_DECL(rt715_adc23_enum, SND_SOC_NOPM, 0, + adc_22_23_mux_text); + +static SOC_VALUE_ENUM_SINGLE_DECL(rt715_adc24_enum, + SND_SOC_NOPM, 0, 0xf, + adc_24_mux_text, rt715_adc_24_25_values); +static SOC_VALUE_ENUM_SINGLE_DECL(rt715_adc25_enum, + SND_SOC_NOPM, 0, 0xf, + adc_25_mux_text, rt715_adc_24_25_values); + +static const struct snd_kcontrol_new rt715_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt715_adc22_enum, + rt715_sdca_mux_get, rt715_sdca_mux_put); + +static const struct snd_kcontrol_new rt715_adc23_mux = + SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt715_adc23_enum, + rt715_sdca_mux_get, rt715_sdca_mux_put); + +static const struct snd_kcontrol_new rt715_adc24_mux = + SOC_DAPM_ENUM_EXT("ADC 24 Mux", rt715_adc24_enum, + rt715_sdca_mux_get, rt715_sdca_mux_put); + +static const struct snd_kcontrol_new rt715_adc25_mux = + SOC_DAPM_ENUM_EXT("ADC 25 Mux", rt715_adc25_enum, + rt715_sdca_mux_get, rt715_sdca_mux_put); + +static int rt715_sdca_pde23_24_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt715->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_CREQ_POW_EN, + RT715_SDCA_REQ_POW_CTRL, + CH_00), 0x00); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt715->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_CREQ_POW_EN, + RT715_SDCA_REQ_POW_CTRL, + CH_00), 0x03); + break; + } + return 0; +} + +static const struct snd_soc_dapm_widget rt715_sdca_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("DMIC3"), + SND_SOC_DAPM_INPUT("DMIC4"), + SND_SOC_DAPM_INPUT("MIC1"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + + SND_SOC_DAPM_SUPPLY("PDE23_24", SND_SOC_NOPM, 0, 0, + rt715_sdca_pde23_24_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_ADC("ADC 07", NULL, SND_SOC_NOPM, 4, 0), + SND_SOC_DAPM_ADC("ADC 08", NULL, SND_SOC_NOPM, 4, 0), + SND_SOC_DAPM_ADC("ADC 09", NULL, SND_SOC_NOPM, 4, 0), + SND_SOC_DAPM_ADC("ADC 27", NULL, SND_SOC_NOPM, 4, 0), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc22_mux), + SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc23_mux), + SND_SOC_DAPM_MUX("ADC 24 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc24_mux), + SND_SOC_DAPM_MUX("ADC 25 Mux", SND_SOC_NOPM, 0, 0, + &rt715_adc25_mux), + SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP6TX", "DP6 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt715_sdca_audio_map[] = { + {"DP6TX", NULL, "ADC 09"}, + {"DP6TX", NULL, "ADC 08"}, + {"DP4TX", NULL, "ADC 07"}, + {"DP4TX", NULL, "ADC 27"}, + {"DP4TX", NULL, "ADC 09"}, + {"DP4TX", NULL, "ADC 08"}, + + {"LINE1", NULL, "PDE23_24"}, + {"LINE2", NULL, "PDE23_24"}, + {"MIC1", NULL, "PDE23_24"}, + {"MIC2", NULL, "PDE23_24"}, + {"DMIC1", NULL, "PDE23_24"}, + {"DMIC2", NULL, "PDE23_24"}, + {"DMIC3", NULL, "PDE23_24"}, + {"DMIC4", NULL, "PDE23_24"}, + + {"ADC 09", NULL, "ADC 22 Mux"}, + {"ADC 08", NULL, "ADC 23 Mux"}, + {"ADC 07", NULL, "ADC 24 Mux"}, + {"ADC 27", NULL, "ADC 25 Mux"}, + {"ADC 22 Mux", "MIC1", "MIC1"}, + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 22 Mux", "LINE1", "LINE1"}, + {"ADC 22 Mux", "LINE2", "LINE2"}, + {"ADC 22 Mux", "DMIC1", "DMIC1"}, + {"ADC 22 Mux", "DMIC2", "DMIC2"}, + {"ADC 22 Mux", "DMIC3", "DMIC3"}, + {"ADC 22 Mux", "DMIC4", "DMIC4"}, + {"ADC 23 Mux", "MIC1", "MIC1"}, + {"ADC 23 Mux", "MIC2", "MIC2"}, + {"ADC 23 Mux", "LINE1", "LINE1"}, + {"ADC 23 Mux", "LINE2", "LINE2"}, + {"ADC 23 Mux", "DMIC1", "DMIC1"}, + {"ADC 23 Mux", "DMIC2", "DMIC2"}, + {"ADC 23 Mux", "DMIC3", "DMIC3"}, + {"ADC 23 Mux", "DMIC4", "DMIC4"}, + {"ADC 24 Mux", "MIC2", "MIC2"}, + {"ADC 24 Mux", "DMIC1", "DMIC1"}, + {"ADC 24 Mux", "DMIC2", "DMIC2"}, + {"ADC 24 Mux", "DMIC3", "DMIC3"}, + {"ADC 24 Mux", "DMIC4", "DMIC4"}, + {"ADC 25 Mux", "MIC1", "MIC1"}, + {"ADC 25 Mux", "DMIC1", "DMIC1"}, + {"ADC 25 Mux", "DMIC2", "DMIC2"}, + {"ADC 25 Mux", "DMIC3", "DMIC3"}, + {"ADC 25 Mux", "DMIC4", "DMIC4"}, +}; + +static const struct snd_soc_component_driver soc_codec_dev_rt715_sdca = { + .controls = rt715_sdca_snd_controls, + .num_controls = ARRAY_SIZE(rt715_sdca_snd_controls), + .dapm_widgets = rt715_sdca_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt715_sdca_dapm_widgets), + .dapm_routes = rt715_sdca_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt715_sdca_audio_map), +}; + +static int rt715_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct rt715_sdw_stream_data *stream; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt715_sdca_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) + +{ + struct rt715_sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + if (!stream) + return; + + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt715_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct rt715_sdw_stream_data *stream; + int retval, port, num_channels; + unsigned int val; + + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt715->slave) + return -EINVAL; + + switch (dai->id) { + case RT715_AIF1: + direction = SDW_DATA_DIR_TX; + port = 6; + rt715_sdca_index_write(rt715, RT715_VENDOR_REG, RT715_SDW_INPUT_SEL, + 0xa500); + break; + case RT715_AIF2: + direction = SDW_DATA_DIR_TX; + port = 4; + rt715_sdca_index_write(rt715, RT715_VENDOR_REG, RT715_SDW_INPUT_SEL, + 0xaf00); + break; + default: + dev_err(component->dev, "Invalid DAI id %d\n", dai->id); + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = GENMASK(num_channels - 1, 0); + port_config.num = port; + + retval = sdw_stream_add_slave(rt715->slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(component->dev, "Unable to configure port, retval:%d\n", + retval); + return retval; + } + + switch (params_rate(params)) { + case 8000: + val = 0x1; + break; + case 11025: + val = 0x2; + break; + case 12000: + val = 0x3; + break; + case 16000: + val = 0x4; + break; + case 22050: + val = 0x5; + break; + case 24000: + val = 0x6; + break; + case 32000: + val = 0x7; + break; + case 44100: + val = 0x8; + break; + case 48000: + val = 0x9; + break; + case 88200: + val = 0xa; + break; + case 96000: + val = 0xb; + break; + case 176400: + val = 0xc; + break; + case 192000: + val = 0xd; + break; + case 384000: + val = 0xe; + break; + case 768000: + val = 0xf; + break; + default: + dev_err(component->dev, "Unsupported sample rate %d\n", + params_rate(params)); + return -EINVAL; + } + + regmap_write(rt715->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_CS_FREQ_IND_EN, + RT715_SDCA_FREQ_IND_CTRL, CH_00), val); + + return 0; +} + +static int rt715_sdca_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt715_sdca_priv *rt715 = snd_soc_component_get_drvdata(component); + struct rt715_sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt715->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt715->slave, stream->sdw_stream); + return 0; +} + +#define RT715_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000) +#define RT715_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S8) + +static struct snd_soc_dai_ops rt715_sdca_ops = { + .hw_params = rt715_sdca_pcm_hw_params, + .hw_free = rt715_sdca_pcm_hw_free, + .set_sdw_stream = rt715_sdca_set_sdw_stream, + .shutdown = rt715_sdca_shutdown, +}; + +static struct snd_soc_dai_driver rt715_sdca_dai[] = { + { + .name = "rt715-aif1", + .id = RT715_AIF1, + .capture = { + .stream_name = "DP6 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT715_STEREO_RATES, + .formats = RT715_FORMATS, + }, + .ops = &rt715_sdca_ops, + }, + { + .name = "rt715-aif2", + .id = RT715_AIF2, + .capture = { + .stream_name = "DP4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT715_STEREO_RATES, + .formats = RT715_FORMATS, + }, + .ops = &rt715_sdca_ops, + }, +}; + +/* Bus clock frequency */ +#define RT715_CLK_FREQ_9600000HZ 9600000 +#define RT715_CLK_FREQ_12000000HZ 12000000 +#define RT715_CLK_FREQ_6000000HZ 6000000 +#define RT715_CLK_FREQ_4800000HZ 4800000 +#define RT715_CLK_FREQ_2400000HZ 2400000 +#define RT715_CLK_FREQ_12288000HZ 12288000 + +int rt715_sdca_init(struct device *dev, struct regmap *mbq_regmap, + struct regmap *regmap, struct sdw_slave *slave) +{ + struct rt715_sdca_priv *rt715; + int ret; + + rt715 = devm_kzalloc(dev, sizeof(*rt715), GFP_KERNEL); + if (!rt715) + return -ENOMEM; + + dev_set_drvdata(dev, rt715); + rt715->slave = slave; + rt715->regmap = regmap; + rt715->mbq_regmap = mbq_regmap; + rt715->hw_sdw_ver = slave->id.sdw_version; + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt715->hw_init = false; + rt715->first_init = false; + + ret = devm_snd_soc_register_component(dev, + &soc_codec_dev_rt715_sdca, + rt715_sdca_dai, + ARRAY_SIZE(rt715_sdca_dai)); + + return ret; +} + +int rt715_sdca_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt715_sdca_priv *rt715 = dev_get_drvdata(dev); + unsigned int hw_ver; + + if (rt715->hw_init) + return 0; + + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + if (!rt715->first_init) { + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + + rt715->first_init = true; + } + + pm_runtime_get_noresume(&slave->dev); + + rt715_sdca_index_read(rt715, RT715_VENDOR_REG, + RT715_PRODUCT_NUM, &hw_ver); + hw_ver = hw_ver & 0x000f; + + /* set clock selector = external */ + regmap_write(rt715->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_CX_CLK_SEL_EN, + RT715_SDCA_CX_CLK_SEL_CTRL, CH_00), 0x1); + /* set GPIO_4/5/6 to be 3rd/4th DMIC usage */ + if (hw_ver == 0x0) + rt715_sdca_index_update_bits(rt715, RT715_VENDOR_REG, + RT715_AD_FUNC_EN, 0x54, 0x54); + else if (hw_ver == 0x1) { + rt715_sdca_index_update_bits(rt715, RT715_VENDOR_REG, + RT715_AD_FUNC_EN, 0x55, 0x55); + rt715_sdca_index_update_bits(rt715, RT715_VENDOR_REG, + RT715_REV_1, 0x40, 0x40); + } + /* trigger mode = VAD enable */ + regmap_write(rt715->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT715_SDCA_SMPU_TRIG_ST_EN, + RT715_SDCA_SMPU_TRIG_EN_CTRL, CH_00), 0x2); + /* SMPU-1 interrupt enable mask */ + regmap_update_bits(rt715->regmap, RT715_INT_MASK, 0x1, 0x1); + + /* Mark Slave initialization complete */ + rt715->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + return 0; +} + +MODULE_DESCRIPTION("ASoC rt715 driver SDW SDCA"); +MODULE_AUTHOR("Jack Yu jack.yu@realtek.com"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/codecs/rt715-sdca.h b/sound/soc/codecs/rt715-sdca.h new file mode 100644 index 000000000000..840c237895dd --- /dev/null +++ b/sound/soc/codecs/rt715-sdca.h @@ -0,0 +1,124 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt715-sdca.h -- RT715 ALSA SoC audio driver header + * + * Copyright(c) 2020 Realtek Semiconductor Corp. + */ + +#ifndef __RT715_SDCA_H__ +#define __RT715_SDCA_H__ + +#include <linux/regmap.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/soc.h> +#include <linux/workqueue.h> +#include <linux/device.h> + +struct rt715_sdca_priv { + struct regmap *regmap; + struct regmap *mbq_regmap; + struct snd_soc_codec *codec; + struct sdw_slave *slave; + struct delayed_work adc_mute_work; + int dbg_nid; + int dbg_vid; + int dbg_payload; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_init; + int l_is_unmute; + int r_is_unmute; + int hw_sdw_ver; +}; + +struct rt715_sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +/* MIPI Register */ +#define RT715_INT_CTRL 0x005a +#define RT715_INT_MASK 0x005e + +/* NID */ +#define RT715_AUDIO_FUNCTION_GROUP 0x01 +#define RT715_MIC_ADC 0x07 +#define RT715_LINE_ADC 0x08 +#define RT715_MIX_ADC 0x09 +#define RT715_DMIC1 0x12 +#define RT715_DMIC2 0x13 +#define RT715_MIC1 0x18 +#define RT715_MIC2 0x19 +#define RT715_LINE1 0x1a +#define RT715_LINE2 0x1b +#define RT715_DMIC3 0x1d +#define RT715_DMIC4 0x29 +#define RT715_VENDOR_REG 0x20 +#define RT715_MUX_IN1 0x22 +#define RT715_MUX_IN2 0x23 +#define RT715_MUX_IN3 0x24 +#define RT715_MUX_IN4 0x25 +#define RT715_MIX_ADC2 0x27 +#define RT715_INLINE_CMD 0x55 +#define RT715_VENDOR_HDA_CTL 0x61 + +/* Index (NID:20h) */ +#define RT715_PRODUCT_NUM 0x0 +#define RT715_IRQ_CTRL 0x2b +#define RT715_AD_FUNC_EN 0x36 +#define RT715_REV_1 0x37 +#define RT715_SDW_INPUT_SEL 0x39 +#define RT715_EXT_DMIC_CLK_CTRL2 0x54 + +/* Index (NID:61h) */ +#define RT715_HDA_LEGACY_MUX_CTL1 0x00 + +/* SDCA (Function) */ +#define FUN_JACK_CODEC 0x01 +#define FUN_MIC_ARRAY 0x02 +#define FUN_HID 0x03 +/* SDCA (Entity) */ +#define RT715_SDCA_ST_EN 0x00 +#define RT715_SDCA_CS_FREQ_IND_EN 0x01 +#define RT715_SDCA_FU_ADC8_9_VOL 0x02 +#define RT715_SDCA_SMPU_TRIG_ST_EN 0x05 +#define RT715_SDCA_FU_ADC10_11_VOL 0x06 +#define RT715_SDCA_FU_ADC7_27_VOL 0x0a +#define RT715_SDCA_FU_AMIC_GAIN_EN 0x0c +#define RT715_SDCA_FU_DMIC_GAIN_EN 0x0e +#define RT715_SDCA_CX_CLK_SEL_EN 0x10 +#define RT715_SDCA_CREQ_POW_EN 0x18 +/* SDCA (Control) */ +#define RT715_SDCA_ST_CTRL 0x00 +#define RT715_SDCA_CX_CLK_SEL_CTRL 0x01 +#define RT715_SDCA_REQ_POW_CTRL 0x01 +#define RT715_SDCA_FU_MUTE_CTRL 0x01 +#define RT715_SDCA_FU_VOL_CTRL 0x02 +#define RT715_SDCA_FU_DMIC_GAIN_CTRL 0x0b +#define RT715_SDCA_FREQ_IND_CTRL 0x10 +#define RT715_SDCA_SMPU_TRIG_EN_CTRL 0x10 +#define RT715_SDCA_SMPU_TRIG_ST_CTRL 0x11 +/* SDCA (Channel) */ +#define CH_00 0x00 +#define CH_01 0x01 +#define CH_02 0x02 +#define CH_03 0x03 +#define CH_04 0x04 +#define CH_05 0x05 +#define CH_06 0x06 +#define CH_07 0x07 +#define CH_08 0x08 + +#define RT715_SDCA_DB_STEP 375 + +enum { + RT715_AIF1, + RT715_AIF2, +}; + +int rt715_sdca_io_init(struct device *dev, struct sdw_slave *slave); +int rt715_sdca_init(struct device *dev, struct regmap *mbq_regmap, + struct regmap *regmap, struct sdw_slave *slave); + +#endif /* __RT715_SDCA_H__ */
On Mon, Nov 30, 2020 at 10:40:18PM +0800, Bard Liao wrote:
From: Jack Yu jack.yu@realtek.com
First version of rt715 sdw sdca codec driver.
This doesn't apply against the ASoC tree, please check and resend.
-----Original Message----- From: Mark Brown broonie@kernel.org Sent: Thursday, December 3, 2020 12:08 AM To: Bard Liao yung-chuan.liao@linux.intel.com Cc: alsa-devel@alsa-project.org; vkoul@kernel.org; vinod.koul@linaro.org; linux-kernel@vger.kernel.org; tiwai@suse.de; gregkh@linuxfoundation.org; jank@cadence.com; srinivas.kandagatla@linaro.org; rander.wang@linux.intel.com; ranjani.sridharan@linux.intel.com; hui.wang@canonical.com; pierre-louis.bossart@linux.intel.com; Kale, Sanyog R sanyog.r.kale@intel.com; Liao, Bard bard.liao@intel.com Subject: Re: [PATCH v2 3/5] ASoC/SoundWire: rt715-sdca: First version of rt715 sdw sdca codec driver
On Mon, Nov 30, 2020 at 10:40:18PM +0800, Bard Liao wrote:
From: Jack Yu jack.yu@realtek.com
First version of rt715 sdw sdca codec driver.
This doesn't apply against the ASoC tree, please check and resend.
Looks like the previous version is already in ASoC tree. I will resend the remaining patches on top of it.
From: Shuming Fan shumingf@realtek.com
This is the initial amplifier driver for rt1316 SDCA version.
Signed-off-by: Shuming Fan shumingf@realtek.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com --- sound/soc/codecs/Kconfig | 6 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt1316-sdw.c | 756 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/rt1316-sdw.h | 115 ++++++ 4 files changed, 879 insertions(+) create mode 100644 sound/soc/codecs/rt1316-sdw.c create mode 100644 sound/soc/codecs/rt1316-sdw.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index e7797f08e057..ebc124142f90 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -179,6 +179,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_RT715_SDW imply SND_SOC_RT715_SDCA_SDW imply SND_SOC_RT1308_SDW + imply SND_SOC_RT1316_SDW imply SND_SOC_SGTL5000 imply SND_SOC_SI476X imply SND_SOC_SIMPLE_AMPLIFIER @@ -1110,6 +1111,11 @@ config SND_SOC_RT1308_SDW depends on I2C && SOUNDWIRE select REGMAP_SOUNDWIRE
+config SND_SOC_RT1316_SDW + tristate "Realtek RT1316 Codec - SDW" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + config SND_SOC_RT5514 tristate depends on I2C diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index b1683403afb3..601bbb8b46e7 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -164,6 +164,7 @@ snd-soc-rt1015p-objs := rt1015p.o snd-soc-rt1305-objs := rt1305.o snd-soc-rt1308-objs := rt1308.o snd-soc-rt1308-sdw-objs := rt1308-sdw.o +snd-soc-rt1316-sdw-objs := rt1316-sdw.o snd-soc-rt274-objs := rt274.o snd-soc-rt286-objs := rt286.o snd-soc-rt298-objs := rt298.o @@ -474,6 +475,7 @@ obj-$(CONFIG_SND_SOC_RT1015P) += snd-soc-rt1015p.o obj-$(CONFIG_SND_SOC_RT1305) += snd-soc-rt1305.o obj-$(CONFIG_SND_SOC_RT1308) += snd-soc-rt1308.o obj-$(CONFIG_SND_SOC_RT1308_SDW) += snd-soc-rt1308-sdw.o +obj-$(CONFIG_SND_SOC_RT1316_SDW) += snd-soc-rt1316-sdw.o obj-$(CONFIG_SND_SOC_RT274) += snd-soc-rt274.o obj-$(CONFIG_SND_SOC_RT286) += snd-soc-rt286.o obj-$(CONFIG_SND_SOC_RT298) += snd-soc-rt298.o diff --git a/sound/soc/codecs/rt1316-sdw.c b/sound/soc/codecs/rt1316-sdw.c new file mode 100644 index 000000000000..145ffb8cd1ca --- /dev/null +++ b/sound/soc/codecs/rt1316-sdw.c @@ -0,0 +1,756 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt1316-sdw.c -- rt1316 SDCA ALSA SoC amplifier audio driver +// +// Copyright(c) 2020 Realtek Semiconductor Corp. +// +// +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/pm_runtime.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include "rt1316-sdw.h" + +static bool rt1316_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2f0a: + case 0x2f36: + case 0x3203 ... 0x320e: + case 0xc000 ... 0xc7b4: + case 0xcf00 ... 0xcf03: + case 0xd101 ... 0xd103: + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_UDMPU21, RT1316_SDCA_CTL_UDMPU_CLUSTER, 0): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_L): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_R): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE23, RT1316_SDCA_CTL_REQ_POWER_STATE, 0): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE27, RT1316_SDCA_CTL_REQ_POWER_STATE, 0): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE22, RT1316_SDCA_CTL_REQ_POWER_STATE, 0): + case SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE24, RT1316_SDCA_CTL_REQ_POWER_STATE, 0): + return true; + default: + return false; + } +} + +static bool rt1316_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0xc000: + case 0xc093: + case 0xc09d: + case 0xc0a3: + case 0xc201: + case 0xc427 ... 0xc428: + case 0xd102: + return true; + default: + return false; + } +} + +static const struct regmap_config rt1316_sdw_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = rt1316_readable_register, + .volatile_reg = rt1316_volatile_register, + .max_register = 0x4108ffff, + .reg_defaults = rt1316_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rt1316_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt1316_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval; + int i, j; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->is_sdca = true; + + prop->paging_support = true; + + /* first we need to allocate memory for set bits in port lists */ + prop->source_ports = 0x04; /* BITMAP: 00000100 */ + prop->sink_ports = 0x2; /* BITMAP: 00000010 */ + + nval = hweight32(prop->source_ports); + 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); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + j = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[j].num = bit; + dpn[j].type = SDW_DPN_FULL; + dpn[j].simple_ch_prep_sm = true; + dpn[j].ch_prep_timeout = 10; + j++; + } + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + dev_dbg(&slave->dev, "%s\n", __func__); + + return 0; +} + +static int rt1316_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt1316_sdw_priv *rt1316 = dev_get_drvdata(dev); + + if (rt1316->hw_init) + return 0; + + if (rt1316->first_hw_init) { + regcache_cache_only(rt1316->regmap, false); + regcache_cache_bypass(rt1316->regmap, true); + } else { + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + /* sw reset */ + regmap_write(rt1316->regmap, 0xc000, 0x02); + + /* initial settings - blind write */ + regmap_write(rt1316->regmap, 0xc710, 0x17); + regmap_write(rt1316->regmap, 0xc711, 0x80); + regmap_write(rt1316->regmap, 0xc712, 0x26); + regmap_write(rt1316->regmap, 0xc713, 0x06); + regmap_write(rt1316->regmap, 0xc714, 0x80); + regmap_write(rt1316->regmap, 0xc715, 0x06); + regmap_write(rt1316->regmap, 0xc702, 0x0a); + regmap_write(rt1316->regmap, 0xc703, 0x0a); + regmap_write(rt1316->regmap, 0xc001, 0x45); + regmap_write(rt1316->regmap, 0xc003, 0x00); + regmap_write(rt1316->regmap, 0xc004, 0x11); + regmap_write(rt1316->regmap, 0xc005, 0x00); + regmap_write(rt1316->regmap, 0xc006, 0x00); + regmap_write(rt1316->regmap, 0xc106, 0x00); + regmap_write(rt1316->regmap, 0xc007, 0x11); + regmap_write(rt1316->regmap, 0xc008, 0x11); + regmap_write(rt1316->regmap, 0xc009, 0x00); + + regmap_write(rt1316->regmap, 0x2f0a, 0x00); + regmap_write(rt1316->regmap, 0xd101, 0xf0); + regmap_write(rt1316->regmap, 0xd103, 0x9b); + regmap_write(rt1316->regmap, 0x2f36, 0x8e); + regmap_write(rt1316->regmap, 0x3206, 0x80); + regmap_write(rt1316->regmap, 0x3211, 0x0b); + regmap_write(rt1316->regmap, 0x3216, 0x06); + regmap_write(rt1316->regmap, 0xc614, 0x20); + regmap_write(rt1316->regmap, 0xc615, 0x0a); + regmap_write(rt1316->regmap, 0xc616, 0x02); + regmap_write(rt1316->regmap, 0xc617, 0x00); + regmap_write(rt1316->regmap, 0xc60b, 0x10); + regmap_write(rt1316->regmap, 0xc60e, 0x05); + regmap_write(rt1316->regmap, 0xc102, 0x00); + regmap_write(rt1316->regmap, 0xc090, 0xb0); + regmap_write(rt1316->regmap, 0xc00f, 0x01); + regmap_write(rt1316->regmap, 0xc09c, 0x7b); + + regmap_write(rt1316->regmap, 0xc602, 0x07); + regmap_write(rt1316->regmap, 0xc603, 0x07); + regmap_write(rt1316->regmap, 0xc0a3, 0x71); + regmap_write(rt1316->regmap, 0xc00b, 0x30); + regmap_write(rt1316->regmap, 0xc093, 0x80); + regmap_write(rt1316->regmap, 0xc09d, 0x80); + regmap_write(rt1316->regmap, 0xc0b0, 0x77); + regmap_write(rt1316->regmap, 0xc010, 0xa5); + regmap_write(rt1316->regmap, 0xc050, 0x83); + regmap_write(rt1316->regmap, 0x2f55, 0x03); + regmap_write(rt1316->regmap, 0x3217, 0xb5); + regmap_write(rt1316->regmap, 0x3202, 0x02); + + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_XU24, RT1316_SDCA_CTL_BYPASS, 0), 0x00); + + /* for IV sense */ + regmap_write(rt1316->regmap, 0x2232, 0x80); + regmap_write(rt1316->regmap, 0xc0b0, 0x77); + regmap_write(rt1316->regmap, 0xc011, 0x00); + regmap_write(rt1316->regmap, 0xc020, 0x00); + regmap_write(rt1316->regmap, 0xc023, 0x00); + regmap_write(rt1316->regmap, 0x3101, 0x00); + regmap_write(rt1316->regmap, 0x3004, 0xa0); + regmap_write(rt1316->regmap, 0x3005, 0xb1); + regmap_write(rt1316->regmap, 0xc007, 0x11); + regmap_write(rt1316->regmap, 0xc008, 0x11); + regmap_write(rt1316->regmap, 0xc009, 0x00); + regmap_write(rt1316->regmap, 0xc022, 0xd6); + regmap_write(rt1316->regmap, 0xc025, 0xd6); + + regmap_write(rt1316->regmap, 0xd001, 0x03); + regmap_write(rt1316->regmap, 0xd002, 0xbf); + regmap_write(rt1316->regmap, 0xd003, 0x03); + regmap_write(rt1316->regmap, 0xd004, 0xbf); + + if (rt1316->first_hw_init) { + regcache_cache_bypass(rt1316->regmap, false); + regcache_mark_dirty(rt1316->regmap); + } else + rt1316->first_hw_init = true; + + /* Mark Slave initialization complete */ + rt1316->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + return 0; +} + +static int rt1316_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt1316_sdw_priv *rt1316 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt1316->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt1316->hw_init = false; + + if (status == SDW_SLAVE_ATTACHED) + regcache_mark_dirty(rt1316->regmap); + + /* + * Perform initialization only if slave status is present and + * hw_init flag is false + */ + if (rt1316->hw_init || rt1316->status != SDW_SLAVE_ATTACHED) + return 0; + + /* perform I/O transfers required for Slave initialization */ + return rt1316_io_init(&slave->dev, slave); +} + +static int rt1316_classd_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt1316_sdw_priv *rt1316 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE23, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE27, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE22, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE23, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE27, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE22, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + + default: + break; + } + + return 0; +} + +static int rt1316_pde24_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt1316_sdw_priv *rt1316 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE24, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt1316->regmap, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE24, + RT1316_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + } + return 0; +} + +static const char * const rt1316_rx_data_ch_select[] = { + "L,R", + "L,L", + "L,R", + "L,L+R", + "R,L", + "R,R", + "R,L+R", + "L+R,L", + "L+R,R", + "L+R,L+R", +}; + +static SOC_ENUM_SINGLE_DECL(rt1316_rx_data_ch_enum, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_UDMPU21, RT1316_SDCA_CTL_UDMPU_CLUSTER, 0), 0, + rt1316_rx_data_ch_select); + +static const char * const rt1316_xu24_bypass_ctl[] = { + "Not Bypass", + "Bypass", +}; + +static SOC_ENUM_SINGLE_DECL(rt1316_xu24_bypass_enum, + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_XU24, RT1316_SDCA_CTL_BYPASS, 0), 0, + rt1316_xu24_bypass_ctl); + +static const char * const rt1316_lr_iv_sel[] = { + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", +}; + +static SOC_ENUM_SINGLE_DECL(rt1316_l_v_tag_enum, + 0x3004, 0, rt1316_lr_iv_sel); +static SOC_ENUM_SINGLE_DECL(rt1316_l_i_tag_enum, + 0x3004, 4, rt1316_lr_iv_sel); +static SOC_ENUM_SINGLE_DECL(rt1316_r_v_tag_enum, + 0x3005, 0, rt1316_lr_iv_sel); +static SOC_ENUM_SINGLE_DECL(rt1316_r_i_tag_enum, + 0x3005, 4, rt1316_lr_iv_sel); + +static const struct snd_kcontrol_new rt1316_snd_controls[] = { + + /* I2S Data Channel Selection */ + SOC_ENUM("RX Channel Select", rt1316_rx_data_ch_enum), + + /* XU24 Bypass Control */ + SOC_ENUM("XU24 Bypass Control", rt1316_xu24_bypass_enum), + + /* Left/Right IV tag */ + SOC_ENUM("Left V Tag Select", rt1316_l_v_tag_enum), + SOC_ENUM("Left I Tag Select", rt1316_l_i_tag_enum), + SOC_ENUM("Right V Tag Select", rt1316_r_v_tag_enum), + SOC_ENUM("Right I Tag Select", rt1316_r_i_tag_enum), + + /* IV mixer Control */ + SOC_DOUBLE("Isense Mixer Switch", 0xc605, 2, 0, 1, 1), + SOC_DOUBLE("Vsense Mixer Switch", 0xc605, 3, 1, 1, 1), +}; + +static const struct snd_kcontrol_new rt1316_sto_dac_l = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_L), + 0, 1, 1); + +static const struct snd_kcontrol_new rt1316_sto_dac_r = + SOC_DAPM_SINGLE_AUTODISABLE("Switch", + SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_R), + 0, 1, 1); + +static const struct snd_soc_dapm_widget rt1316_dapm_widgets[] = { + /* Audio Interface */ + SND_SOC_DAPM_AIF_IN("DP1RX", "DP1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0), + + /* Digital Interface */ + SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SWITCH("DAC L", SND_SOC_NOPM, 0, 0, &rt1316_sto_dac_l), + SND_SOC_DAPM_SWITCH("DAC R", SND_SOC_NOPM, 0, 0, &rt1316_sto_dac_r), + + /* Output Lines */ + SND_SOC_DAPM_PGA_E("CLASS D", SND_SOC_NOPM, 0, 0, NULL, 0, + rt1316_classd_event, + SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMU), + SND_SOC_DAPM_OUTPUT("SPOL"), + SND_SOC_DAPM_OUTPUT("SPOR"), + + SND_SOC_DAPM_SUPPLY("PDE 24", SND_SOC_NOPM, 0, 0, + rt1316_pde24_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA("I Sense", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("V Sense", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SIGGEN("I Gen"), + SND_SOC_DAPM_SIGGEN("V Gen"), +}; + +static const struct snd_soc_dapm_route rt1316_dapm_routes[] = { + { "DAC", NULL, "DP1RX" }, + { "DAC L", "Switch", "DAC" }, + { "DAC R", "Switch", "DAC" }, + { "CLASS D", NULL, "DAC L" }, + { "CLASS D", NULL, "DAC R" }, + { "SPOL", NULL, "CLASS D" }, + { "SPOR", NULL, "CLASS D" }, + + { "I Sense", NULL, "I Gen" }, + { "V Sense", NULL, "V Gen" }, + { "I Sense", NULL, "PDE 24" }, + { "V Sense", NULL, "PDE 24" }, + { "DP2TX", NULL, "I Sense" }, + { "DP2TX", NULL, "V Sense" }, +}; + +static int rt1316_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct sdw_stream_data *stream; + + if (!sdw_stream) + return 0; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt1316_sdw_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt1316_sdw_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + struct snd_soc_component *component = dai->component; + struct rt1316_sdw_priv *rt1316 = + snd_soc_component_get_drvdata(component); + + if (tx_mask) + return -EINVAL; + + if (slots > 2) + return -EINVAL; + + rt1316->rx_mask = rx_mask; + rt1316->slots = slots; + /* slot_width is not used since it's irrelevant for SoundWire */ + + return 0; +} + +static int rt1316_sdw_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt1316_sdw_priv *rt1316 = + snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels, ch_mask; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt1316->sdw_slave) + return -EINVAL; + + /* SoundWire specific configuration */ + /* port 1 for playback */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + port = 1; + } else { + direction = SDW_DATA_DIR_TX; + port = 2; + } + + if (rt1316->slots) { + num_channels = rt1316->slots; + ch_mask = rt1316->rx_mask; + } else { + num_channels = params_channels(params); + ch_mask = (1 << num_channels) - 1; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = num_channels; + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + port_config.ch_mask = ch_mask; + port_config.num = port; + + retval = sdw_stream_add_slave(rt1316->sdw_slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + return 0; +} + +static int rt1316_sdw_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt1316_sdw_priv *rt1316 = + snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt1316->sdw_slave) + return -EINVAL; + + sdw_stream_remove_slave(rt1316->sdw_slave, stream->sdw_stream); + return 0; +} + +/* + * slave_ops: callbacks for get_clock_stop_mode, clock_stop and + * port_prep are not defined for now + */ +static struct sdw_slave_ops rt1316_slave_ops = { + .read_prop = rt1316_read_prop, + .update_status = rt1316_update_status, +}; + +static const struct snd_soc_component_driver soc_component_sdw_rt1316 = { + .controls = rt1316_snd_controls, + .num_controls = ARRAY_SIZE(rt1316_snd_controls), + .dapm_widgets = rt1316_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt1316_dapm_widgets), + .dapm_routes = rt1316_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(rt1316_dapm_routes), +}; + +static const struct snd_soc_dai_ops rt1316_aif_dai_ops = { + .hw_params = rt1316_sdw_hw_params, + .hw_free = rt1316_sdw_pcm_hw_free, + .set_sdw_stream = rt1316_set_sdw_stream, + .shutdown = rt1316_sdw_shutdown, + .set_tdm_slot = rt1316_sdw_set_tdm_slot, +}; + +#define RT1316_STEREO_RATES SNDRV_PCM_RATE_48000 +#define RT1316_FORMATS (SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver rt1316_sdw_dai[] = { + { + .name = "rt1316-aif", + .playback = { + .stream_name = "DP1 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT1316_STEREO_RATES, + .formats = RT1316_FORMATS, + }, + .capture = { + .stream_name = "DP2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT1316_STEREO_RATES, + .formats = RT1316_FORMATS, + }, + .ops = &rt1316_aif_dai_ops, + }, +}; + +static int rt1316_sdw_init(struct device *dev, struct regmap *regmap, + struct sdw_slave *slave) +{ + struct rt1316_sdw_priv *rt1316; + int ret; + + rt1316 = devm_kzalloc(dev, sizeof(*rt1316), GFP_KERNEL); + if (!rt1316) + return -ENOMEM; + + dev_set_drvdata(dev, rt1316); + rt1316->sdw_slave = slave; + rt1316->regmap = regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt1316->hw_init = false; + rt1316->first_hw_init = false; + + ret = devm_snd_soc_register_component(dev, + &soc_component_sdw_rt1316, + rt1316_sdw_dai, + ARRAY_SIZE(rt1316_sdw_dai)); + + dev_dbg(&slave->dev, "%s\n", __func__); + + return ret; +} + +static int rt1316_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap; + + /* Regmap Initialization */ + regmap = devm_regmap_init_sdw(slave, &rt1316_sdw_regmap); + if (!regmap) + return -EINVAL; + + return rt1316_sdw_init(&slave->dev, regmap, slave); +} + +static const struct sdw_device_id rt1316_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x1316, 0x3, 0x1, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt1316_id); + +static int __maybe_unused rt1316_dev_suspend(struct device *dev) +{ + struct rt1316_sdw_priv *rt1316 = dev_get_drvdata(dev); + + if (!rt1316->hw_init) + return 0; + + regcache_cache_only(rt1316->regmap, true); + + return 0; +} + +#define RT1316_PROBE_TIMEOUT 2000 + +static int __maybe_unused rt1316_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct rt1316_sdw_priv *rt1316 = dev_get_drvdata(dev); + unsigned long time; + + if (!rt1316->hw_init) + return 0; + + if (!slave->unattach_request) + goto regmap_sync; + + time = wait_for_completion_timeout(&slave->initialization_complete, + msecs_to_jiffies(RT1316_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(rt1316->regmap, false); + regcache_sync(rt1316->regmap); + + return 0; +} + +static const struct dev_pm_ops rt1316_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt1316_dev_suspend, rt1316_dev_resume) + SET_RUNTIME_PM_OPS(rt1316_dev_suspend, rt1316_dev_resume, NULL) +}; + +static struct sdw_driver rt1316_sdw_driver = { + .driver = { + .name = "rt1316-sdca", + .owner = THIS_MODULE, + .pm = &rt1316_pm, + }, + .probe = rt1316_sdw_probe, + .ops = &rt1316_slave_ops, + .id_table = rt1316_id, +}; +module_sdw_driver(rt1316_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT1316 driver SDCA SDW"); +MODULE_AUTHOR("Shuming Fan shumingf@realtek.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt1316-sdw.h b/sound/soc/codecs/rt1316-sdw.h new file mode 100644 index 000000000000..50e51bf2bf88 --- /dev/null +++ b/sound/soc/codecs/rt1316-sdw.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt1316-sdw.h -- RT1316 SDCA ALSA SoC audio driver header + * + * Copyright(c) 2020 Realtek Semiconductor Corp. + */ + +#ifndef __RT1316_SDW_H__ +#define __RT1316_SDW_H__ + +#include <linux/regmap.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <linux/soundwire/sdw_registers.h> +#include <sound/soc.h> + +/* RT1316 SDCA function topology */ +#define FUN_SMART_AMP 0x04 + +/* RT1316 SDCA entity */ +#define RT1316_SDCA_ENT_PDE23 0x31 +#define RT1316_SDCA_ENT_PDE27 0x32 +#define RT1316_SDCA_ENT_PDE22 0x33 +#define RT1316_SDCA_ENT_PDE24 0x34 +#define RT1316_SDCA_ENT_XU24 0x24 +#define RT1316_SDCA_ENT_FU21 0x03 +#define RT1316_SDCA_ENT_UDMPU21 0x02 + +/* RT1316 SDCA control */ +#define RT1316_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 +#define RT1316_SDCA_CTL_REQ_POWER_STATE 0x01 +#define RT1316_SDCA_CTL_BYPASS 0x01 +#define RT1316_SDCA_CTL_FU_MUTE 0x01 +#define RT1316_SDCA_CTL_FU_VOLUME 0x02 +#define RT1316_SDCA_CTL_UDMPU_CLUSTER 0x10 + +/* RT1316 SDCA channel */ +#define CH_L 0x01 +#define CH_R 0x02 + +/* Power State */ +#define PS0 0x00 +#define PS3 0x03 + +/* Mute Control */ +#define UNMUTE 0x00 +#define MUTE 0x01 + +static const struct reg_default rt1316_reg_defaults[] = { + { 0x3004, 0x00 }, + { 0x3005, 0x00 }, + { 0x3206, 0x00 }, + { 0xc001, 0x00 }, + { 0xc002, 0x00 }, + { 0xc003, 0x00 }, + { 0xc004, 0x00 }, + { 0xc005, 0x00 }, + { 0xc006, 0x00 }, + { 0xc007, 0x00 }, + { 0xc008, 0x00 }, + { 0xc009, 0x00 }, + { 0xc00a, 0x00 }, + { 0xc00b, 0x00 }, + { 0xc00c, 0x00 }, + { 0xc00d, 0x00 }, + { 0xc00e, 0x00 }, + { 0xc00f, 0x00 }, + { 0xc010, 0xa5 }, + { 0xc011, 0x00 }, + { 0xc012, 0xff }, + { 0xc013, 0xff }, + { 0xc014, 0x40 }, + { 0xc015, 0x00 }, + { 0xc016, 0x00 }, + { 0xc017, 0x00 }, + { 0xc605, 0x30 }, + { 0xc700, 0x0a }, + { 0xc701, 0xaa }, + { 0xc702, 0x1a }, + { 0xc703, 0x0a }, + { 0xc710, 0x80 }, + { 0xc711, 0x00 }, + { 0xc712, 0x3e }, + { 0xc713, 0x80 }, + { 0xc714, 0x80 }, + { 0xc715, 0x06 }, + { 0xd101, 0x00 }, + { 0xd102, 0x30 }, + { 0xd103, 0x00 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_UDMPU21, RT1316_SDCA_CTL_UDMPU_CLUSTER, 0), 0x00 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_L), 0x01 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_FU21, RT1316_SDCA_CTL_FU_MUTE, CH_R), 0x01 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_XU24, RT1316_SDCA_CTL_BYPASS, 0), 0x01 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE23, RT1316_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE22, RT1316_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUN_SMART_AMP, RT1316_SDCA_ENT_PDE24, RT1316_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, +}; + +struct rt1316_sdw_priv { + struct snd_soc_component *component; + struct regmap *regmap; + struct sdw_slave *sdw_slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_hw_init; + int rx_mask; + int slots; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +#endif /* __RT1316_SDW_H__ */
From: Shuming Fan shumingf@realtek.com
This is the initial codec driver for rt711 SDCA version.
Signed-off-by: Shuming Fan shumingf@realtek.com Reviewed-by: Pierre-Louis Bossart pierre-louis.bossart@linux.intel.com Signed-off-by: Bard Liao yung-chuan.liao@linux.intel.com --- sound/soc/codecs/Kconfig | 7 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/rt711-sdca-sdw.c | 424 +++++++++ sound/soc/codecs/rt711-sdca-sdw.h | 101 ++ sound/soc/codecs/rt711-sdca.c | 1482 +++++++++++++++++++++++++++++ sound/soc/codecs/rt711-sdca.h | 246 +++++ 6 files changed, 2262 insertions(+) create mode 100644 sound/soc/codecs/rt711-sdca-sdw.c create mode 100644 sound/soc/codecs/rt711-sdca-sdw.h create mode 100644 sound/soc/codecs/rt711-sdca.c create mode 100644 sound/soc/codecs/rt711-sdca.h
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ebc124142f90..a9064f279d33 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -176,6 +176,7 @@ config SND_SOC_ALL_CODECS imply SND_SOC_RT5682_SDW imply SND_SOC_RT700_SDW imply SND_SOC_RT711_SDW + imply SND_SOC_RT711_SDCA_SDW imply SND_SOC_RT715_SDW imply SND_SOC_RT715_SDCA_SDW imply SND_SOC_RT1308_SDW @@ -1214,6 +1215,12 @@ config SND_SOC_RT711_SDW select SND_SOC_RT711 select REGMAP_SOUNDWIRE
+config SND_SOC_RT711_SDCA_SDW + tristate "Realtek RT711 SDCA Codec - SDW" + depends on SOUNDWIRE + select REGMAP_SOUNDWIRE + select REGMAP_SOUNDWIRE_MBQ + config SND_SOC_RT715 tristate
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 601bbb8b46e7..8bdd587bbd3b 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -188,6 +188,7 @@ snd-soc-rt5682-sdw-objs := rt5682-sdw.o snd-soc-rt5682-i2c-objs := rt5682-i2c.o snd-soc-rt700-objs := rt700.o rt700-sdw.o snd-soc-rt711-objs := rt711.o rt711-sdw.o +snd-soc-rt711-sdca-objs := rt711-sdca.o rt711-sdca-sdw.o snd-soc-rt715-objs := rt715.o rt715-sdw.o snd-soc-rt715-sdca-objs := rt715-sdca.o rt715-sdca-sdw.o snd-soc-sgtl5000-objs := sgtl5000.o @@ -500,6 +501,7 @@ obj-$(CONFIG_SND_SOC_RT5682_I2C) += snd-soc-rt5682-i2c.o obj-$(CONFIG_SND_SOC_RT5682_SDW) += snd-soc-rt5682-sdw.o obj-$(CONFIG_SND_SOC_RT700) += snd-soc-rt700.o obj-$(CONFIG_SND_SOC_RT711) += snd-soc-rt711.o +obj-$(CONFIG_SND_SOC_RT711_SDCA_SDW) += snd-soc-rt711-sdca.o obj-$(CONFIG_SND_SOC_RT715) += snd-soc-rt715.o obj-$(CONFIG_SND_SOC_RT715_SDCA_SDW) += snd-soc-rt715-sdca.o obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o diff --git a/sound/soc/codecs/rt711-sdca-sdw.c b/sound/soc/codecs/rt711-sdca-sdw.c new file mode 100644 index 000000000000..6aaf9e09c118 --- /dev/null +++ b/sound/soc/codecs/rt711-sdca-sdw.c @@ -0,0 +1,424 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt711-sdw-sdca.c -- rt711 SDCA ALSA SoC audio driver +// +// Copyright(c) 2020 Realtek Semiconductor Corp. +// +// + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/mod_devicetable.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/module.h> + +#include "rt711-sdca.h" +#include "rt711-sdca-sdw.h" + +static bool rt711_sdca_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SDW_DP0_INT: + case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INTMASK4: + case 0x201a ... 0x2027: + case 0x2029 ... 0x202a: + case 0x202d ... 0x2034: + case 0x2200 ... 0x2204: + case 0x2206 ... 0x2212: + case 0x2220 ... 0x2223: + case 0x2230 ... 0x2239: + case 0x2f01 ... 0x2f0f: + case 0x2f30 ... 0x2f36: + case 0x2f50 ... 0x2f5a: + case 0x2f60: + case 0x3200 ... 0x3212: + case SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_SELECTED_MODE, 0): + case SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): + case RT711_BUF_ADDR_HID1 ... RT711_BUF_ADDR_HID2: + return true; + default: + return false; + } +} + +static bool rt711_sdca_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SDW_DP0_INT: + case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INT4: + case 0x201b: + case 0x201c: + case 0x201d: + case 0x201f: + case 0x2021: + case 0x2023: + case 0x2230: + case 0x202d ... 0x202f: /* BRA */ + case 0x2200 ... 0x2212: /* i2c debug */ + case RT711_RC_CAL_STATUS: + case SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_DETECTED_MODE, 0): + case SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_CURRENT_OWNER, 0) ... + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_MESSAGE_LENGTH, 0): + case RT711_BUF_ADDR_HID1 ... RT711_BUF_ADDR_HID2: + return true; + default: + return false; + } +} + +static bool rt711_sdca_mbq_readable_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000 ... 0x20000ff: + case 0x5600000 ... 0x56000ff: + case 0x5700000 ... 0x57000ff: + case 0x5800000 ... 0x58000ff: + case 0x5900000 ... 0x59000ff: + case 0x5b00000 ... 0x5b000ff: + case 0x5f00000 ... 0x5f000ff: + case 0x6100000 ... 0x61000ff: + return true; + default: + return false; + } +} + +static bool rt711_sdca_mbq_volatile_register(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0x2000000: + case 0x200001a: + case 0x2000046: + case 0x2000080: + case 0x2000081: + case 0x2000083: + case 0x5800000: + case 0x5800001: + case 0x5f00001: + case 0x6100008: + return true; + default: + return false; + } +} + +static const struct regmap_config rt711_sdca_regmap = { + .reg_bits = 32, + .val_bits = 8, + .readable_reg = rt711_sdca_readable_register, + .volatile_reg = rt711_sdca_volatile_register, + .max_register = 0x44ffffff, + .reg_defaults = rt711_sdca_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rt711_sdca_reg_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static const struct regmap_config rt711_sdca_mbq_regmap = { + .name = "sdw-mbq", + .reg_bits = 32, + .val_bits = 16, + .readable_reg = rt711_sdca_mbq_readable_register, + .volatile_reg = rt711_sdca_mbq_volatile_register, + .max_register = 0x40800f12, + .reg_defaults = rt711_sdca_mbq_defaults, + .num_reg_defaults = ARRAY_SIZE(rt711_sdca_mbq_defaults), + .cache_type = REGCACHE_RBTREE, + .use_single_read = true, + .use_single_write = true, +}; + +static int rt711_sdca_update_status(struct sdw_slave *slave, + enum sdw_slave_status status) +{ + struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); + + /* Update the status */ + rt711->status = status; + + if (status == SDW_SLAVE_UNATTACHED) + rt711->hw_init = false; + + if (status == SDW_SLAVE_ATTACHED) { + if (rt711->hs_jack) { + regcache_cache_only(rt711->regmap, false); + regcache_cache_bypass(rt711->regmap, true); + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + regcache_cache_bypass(rt711->regmap, false); + } else { + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK1, 0); + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK2, 0); + } + } + + /* + * 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_sdca_io_init(&slave->dev, slave); +} + +static int rt711_sdca_read_prop(struct sdw_slave *slave) +{ + struct sdw_slave_prop *prop = &slave->prop; + int nval; + int i, j; + u32 bit; + unsigned long addr; + struct sdw_dpn_prop *dpn; + + prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY; + prop->quirks = SDW_SLAVE_QUIRKS_INVALID_INITIAL_PARITY; + prop->is_sdca = true; + + prop->paging_support = true; + + /* 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); + 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); + prop->sink_dpn_prop = devm_kcalloc(&slave->dev, nval, + sizeof(*prop->sink_dpn_prop), GFP_KERNEL); + if (!prop->sink_dpn_prop) + return -ENOMEM; + + j = 0; + dpn = prop->sink_dpn_prop; + addr = prop->sink_ports; + for_each_set_bit(bit, &addr, 32) { + dpn[j].num = bit; + dpn[j].type = SDW_DPN_FULL; + dpn[j].simple_ch_prep_sm = true; + dpn[j].ch_prep_timeout = 10; + j++; + } + + /* set the timeout values */ + prop->clk_stop_timeout = 20; + + /* wake-up event */ + prop->wake_capable = 1; + + return 0; +} + +static int rt711_sdca_interrupt_callback(struct sdw_slave *slave, + struct sdw_slave_intr_status *status) +{ + struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); + int ret, stat; + int count = 0, retry = 3; + unsigned int sdca_cascade, scp_sdca_stat1, scp_sdca_stat2 = 0; + unsigned int buf; + + dev_dbg(&slave->dev, + "%s control_port_stat=%x, sdca_cascade=%x", __func__, + status->control_port, status->sdca_cascade); + + if (cancel_delayed_work_sync(&rt711->jack_detect_work)) { + dev_warn(&slave->dev, "%s the pending delayed_work was cancelled", __func__); + /* avoid the HID owner doesn't change to device */ + if (rt711->scp_sdca_stat2) + scp_sdca_stat2 = rt711->scp_sdca_stat2; + } + + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT1, &rt711->scp_sdca_stat1); + if (ret < 0) + goto io_error; + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT2, &rt711->scp_sdca_stat2); + if (ret < 0) + goto io_error; + if (scp_sdca_stat2) + rt711->scp_sdca_stat2 |= scp_sdca_stat2; + + do { + /* clear flag */ + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT1, &buf); + if (ret < 0) + goto io_error; + if (buf & SDW_SCP_SDCA_INTMASK_SDCA_0) { + ret = regmap_write(rt711->regmap, SDW_SCP_SDCA_INT1, SDW_SCP_SDCA_INTMASK_SDCA_0); + if (ret < 0) + goto io_error; + } + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT2, &buf); + if (ret < 0) + goto io_error; + if (buf & SDW_SCP_SDCA_INTMASK_SDCA_8) { + ret = regmap_write(rt711->regmap, SDW_SCP_SDCA_INT2, SDW_SCP_SDCA_INTMASK_SDCA_8); + if (ret < 0) + goto io_error; + } + + /* check if flag clear or not */ + ret = regmap_read(rt711->regmap, SDW_DP0_INT, &buf); + if (ret < 0) + goto io_error; + sdca_cascade = buf & SDW_DP0_SDCA_CASCADE; + + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT1, &buf); + if (ret < 0) + goto io_error; + scp_sdca_stat1 = buf & SDW_SCP_SDCA_INTMASK_SDCA_0; + + ret = regmap_read(rt711->regmap, SDW_SCP_SDCA_INT2, &buf); + if (ret < 0) + goto io_error; + scp_sdca_stat2 = buf & SDW_SCP_SDCA_INTMASK_SDCA_8; + + stat = scp_sdca_stat1 || scp_sdca_stat2 || sdca_cascade; + + count++; + } while (stat != 0 && count < retry); + + if (stat) + dev_warn(&slave->dev, + "%s scp_sdca_stat1=0x%x, scp_sdca_stat2=0x%x\n", __func__, + rt711->scp_sdca_stat1, rt711->scp_sdca_stat2); + + if (status->sdca_cascade) + mod_delayed_work(system_power_efficient_wq, + &rt711->jack_detect_work, msecs_to_jiffies(30)); + + return 0; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static struct sdw_slave_ops rt711_sdca_slave_ops = { + .read_prop = rt711_sdca_read_prop, + .interrupt_callback = rt711_sdca_interrupt_callback, + .update_status = rt711_sdca_update_status, +}; + +static int rt711_sdca_sdw_probe(struct sdw_slave *slave, + const struct sdw_device_id *id) +{ + struct regmap *regmap, *mbq_regmap; + + /* Regmap Initialization */ + mbq_regmap = devm_regmap_init_sdw_mbq(slave, &rt711_sdca_mbq_regmap); + if (IS_ERR(mbq_regmap)) + return PTR_ERR(mbq_regmap); + + regmap = devm_regmap_init_sdw(slave, &rt711_sdca_regmap); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + return rt711_sdca_init(&slave->dev, regmap, mbq_regmap, slave); +} + +static int rt711_sdca_sdw_remove(struct sdw_slave *slave) +{ + struct rt711_sdca_priv *rt711 = dev_get_drvdata(&slave->dev); + + if (rt711 && rt711->hw_init) + cancel_delayed_work(&rt711->jack_detect_work); + + return 0; +} + +static const struct sdw_device_id rt711_sdca_id[] = { + SDW_SLAVE_ENTRY_EXT(0x025d, 0x711, 0x3, 0x1, 0), + {}, +}; +MODULE_DEVICE_TABLE(sdw, rt711_sdca_id); + +static int __maybe_unused rt711_sdca_dev_suspend(struct device *dev) +{ + struct rt711_sdca_priv *rt711 = dev_get_drvdata(dev); + + if (!rt711->hw_init) + return 0; + + cancel_delayed_work_sync(&rt711->jack_detect_work); + cancel_delayed_work_sync(&rt711->jack_btn_check_work); + + regcache_cache_only(rt711->regmap, true); + regcache_cache_only(rt711->mbq_regmap, true); + + return 0; +} + +#define RT711_PROBE_TIMEOUT 2000 + +static int __maybe_unused rt711_sdca_dev_resume(struct device *dev) +{ + struct sdw_slave *slave = dev_to_sdw_dev(dev); + struct rt711_sdca_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(rt711->regmap); + regcache_cache_only(rt711->mbq_regmap, false); + regcache_sync(rt711->mbq_regmap); + return 0; +} + +static const struct dev_pm_ops rt711_sdca_pm = { + SET_SYSTEM_SLEEP_PM_OPS(rt711_sdca_dev_suspend, rt711_sdca_dev_resume) + SET_RUNTIME_PM_OPS(rt711_sdca_dev_suspend, rt711_sdca_dev_resume, NULL) +}; + +static struct sdw_driver rt711_sdca_sdw_driver = { + .driver = { + .name = "rt711-sdca", + .owner = THIS_MODULE, + .pm = &rt711_sdca_pm, + }, + .probe = rt711_sdca_sdw_probe, + .remove = rt711_sdca_sdw_remove, + .ops = &rt711_sdca_slave_ops, + .id_table = rt711_sdca_id, +}; +module_sdw_driver(rt711_sdca_sdw_driver); + +MODULE_DESCRIPTION("ASoC RT711 SDCA SDW driver"); +MODULE_AUTHOR("Shuming Fan shumingf@realtek.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt711-sdca-sdw.h b/sound/soc/codecs/rt711-sdca-sdw.h new file mode 100644 index 000000000000..b0c0f1f65f40 --- /dev/null +++ b/sound/soc/codecs/rt711-sdca-sdw.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt711-sdw-sdca.h -- RT711 SDCA ALSA SoC audio driver header + * + * Copyright(c) 2020 Realtek Semiconductor Corp. + */ + +#ifndef __RT711_SDW_SDCA_H__ +#define __RT711_SDW_SDCA_H__ + +#include <linux/regmap.h> +#include <linux/soundwire/sdw_registers.h> + +static const struct reg_default rt711_sdca_reg_defaults[] = { + { 0x005c, 0x00 }, + { 0x005d, 0x00 }, + { 0x201a, 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 }, + { 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 }, + { 0x2f08, 0x00 }, + { 0x2f09, 0x00 }, + { 0x2f0a, 0x00 }, + { 0x2f0b, 0x00 }, + { 0x2f0c, 0x00 }, + { 0x2f0d, 0x00 }, + { 0x2f0e, 0x14 }, + { 0x2f0f, 0x00 }, + { 0x2f50, 0x03 }, + { 0x2f5a, 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_CS01, RT711_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), 0x09 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_MUTE, CH_L), 0x01 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_MUTE, CH_R), 0x01 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_MUTE, CH_L), 0x01 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_MUTE, CH_R), 0x01 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDE28, RT711_SDCA_CTL_REQ_POWER_STATE, 0), 0x03 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_MUTE, CH_L), 0x01 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_MUTE, CH_R), 0x01 }, +}; + +static const struct reg_default rt711_sdca_mbq_defaults[] = { + { 0x2000009, 0x1029 }, + { 0x2000011, 0x007a }, + { 0x200001a, 0x8003 }, + { 0x2000045, 0x5289 }, + { 0x2000048, 0x8049 }, + { 0x200004a, 0xa83b }, + { 0x200006b, 0x5064 }, + { 0x200006f, 0x058b }, + { 0x5800000, 0x0008 }, + { 0x5800001, 0x0000 }, + { 0x5f00001, 0x000a }, + { 0x6100000, 0x6100 }, + { 0x6100035, 0x0060 }, + { 0x6100036, 0x0029 }, + { 0x610003f, 0xff12 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_VOLUME, CH_L), 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_VOLUME, CH_R), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_VOLUME, CH_L), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_VOLUME, CH_R), 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_VOLUME, CH_L), 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_VOLUME, CH_R), 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PLATFORM_FU44, RT711_SDCA_CTL_FU_CH_GAIN, CH_L), 0x00 }, + { SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PLATFORM_FU44, RT711_SDCA_CTL_FU_CH_GAIN, CH_R), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PLATFORM_FU15, RT711_SDCA_CTL_FU_CH_GAIN, CH_L), 0x00 }, + { SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PLATFORM_FU15, RT711_SDCA_CTL_FU_CH_GAIN, CH_R), 0x00 }, +}; + +#endif /* __RT711_SDW_SDCA_H__ */ diff --git a/sound/soc/codecs/rt711-sdca.c b/sound/soc/codecs/rt711-sdca.c new file mode 100644 index 000000000000..19276180b329 --- /dev/null +++ b/sound/soc/codecs/rt711-sdca.c @@ -0,0 +1,1482 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// rt711-sdca.c -- rt711 SDCA ALSA SoC audio driver +// +// Copyright(c) 2020 Realtek Semiconductor Corp. +// +// + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm_runtime.h> +#include <linux/soundwire/sdw_registers.h> +#include <linux/slab.h> +#include <linux/bitops.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <sound/jack.h> + +#include "rt711-sdca.h" + +static int rt711_sdca_index_write(struct rt711_sdca_priv *rt711, + unsigned int nid, unsigned int reg, unsigned int value) +{ + int ret; + struct regmap *regmap = rt711->mbq_regmap; + unsigned int addr = (nid << 20) | reg; + + ret = regmap_write(regmap, addr, value); + if (ret < 0) + dev_err(rt711->component->dev, + "Failed to set private value: %06x <= %04x ret=%d\n", + addr, value, ret); + + return ret; +} + +static int rt711_sdca_index_read(struct rt711_sdca_priv *rt711, + unsigned int nid, unsigned int reg, unsigned int *value) +{ + int ret; + struct regmap *regmap = rt711->mbq_regmap; + unsigned int addr = (nid << 20) | reg; + + ret = regmap_read(regmap, addr, value); + if (ret < 0) + dev_err(rt711->component->dev, + "Failed to get private value: %06x => %04x ret=%d\n", + addr, *value, ret); + + return ret; +} + +static int rt711_sdca_index_update_bits(struct rt711_sdca_priv *rt711, + unsigned int nid, unsigned int reg, unsigned int mask, unsigned int val) +{ + unsigned int tmp; + int ret; + + ret = rt711_sdca_index_read(rt711, nid, reg, &tmp); + if (ret < 0) + return ret; + + set_mask_bits(&tmp, mask, val); + return rt711_sdca_index_write(rt711, nid, reg, tmp); +} + +static void rt711_sdca_reset(struct rt711_sdca_priv *rt711) +{ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_PARA_VERB_CTL, RT711_HIDDEN_REG_SW_RESET, + RT711_HIDDEN_REG_SW_RESET); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_RESET_CTL, 0x1, 0x1); +} + +static int rt711_sdca_calibration(struct rt711_sdca_priv *rt711) +{ + unsigned int val, loop_rc = 0, loop_dc = 0; + struct device *dev; + struct regmap *regmap = rt711->regmap; + int chk_cnt = 100; + int ret = 0; + + mutex_lock(&rt711->calibrate_mutex); + dev = regmap_get_device(regmap); + + regmap_read(rt711->regmap, RT711_RC_CAL_STATUS, &val); + /* RC calibration */ + if (!(val & 0x40)) + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_ANALOG_CTL, + RT711_MISC_POWER_CTL0, 0x0010, 0x0010); + + for (loop_rc = 0; loop_rc < chk_cnt && !(val & 0x40); loop_rc++) { + usleep_range(10000, 11000); + ret = regmap_read(rt711->regmap, RT711_RC_CAL_STATUS, &val); + if (ret < 0) + goto _cali_fail_; + } + if (loop_rc == chk_cnt) + dev_err(dev, "%s, RC calibration time-out!\n", __func__); + + /* HP calibration by manual mode setting */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_FSM_CTL, 0x2000, 0x2000); + + /* Calibration manual mode */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_FSM_CTL, 0xf, RT711_CALI_CTL); + + /* reset HP calibration */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_FORCE_CALI_RST, 0x00); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_FORCE_CALI_RST, + RT711_DAC_DC_FORCE_CALI_RST); + + /* cal_clk_en_reg */ + if (rt711->hw_ver == RT711_VER_VD0) + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_CLK_EN, + RT711_DAC_DC_CALI_CLK_EN); + + /* trigger */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, RT711_DAC_DC_CALI_TRIGGER, + RT711_DAC_DC_CALI_TRIGGER); + + /* wait for calibration process */ + rt711_sdca_index_read(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, &val); + + for (loop_dc = 0; loop_dc < chk_cnt && + (val & RT711_DAC_DC_CALI_TRIGGER); loop_dc++) { + usleep_range(10000, 11000); + ret = rt711_sdca_index_read(rt711, RT711_VENDOR_CALI, + RT711_DAC_DC_CALI_CTL1, &val); + if (ret < 0) + goto _cali_fail_; + } + if (loop_dc == chk_cnt) + dev_err(dev, "%s, calibration time-out!\n", __func__); + + if (loop_dc == chk_cnt || loop_rc == chk_cnt) + ret = -ETIMEDOUT; + +_cali_fail_: + /* enable impedance sense */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_FSM_CTL, RT711_FSM_IMP_EN, RT711_FSM_IMP_EN); + + /* release HP-JD and trigger FSM */ + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_DIGITAL_MISC_CTRL4, 0x201b); + + mutex_unlock(&rt711->calibrate_mutex); + dev_dbg(dev, "%s calibration complete, ret=%d\n", __func__, ret); + return ret; +} + +static unsigned int rt711_sdca_button_detect(struct rt711_sdca_priv *rt711) +{ + unsigned int btn_type = 0, offset, idx, val, owner; + int ret; + unsigned char buf[3]; + + /* get current UMP message owner */ + ret = regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_CURRENT_OWNER, 0), + &owner); + if (ret < 0) + return 0; + + /* if owner is device then there is no button event from device */ + if (owner == 1) + return 0; + + /* read UMP message offset */ + ret = regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0), + &offset); + if (ret < 0) + goto _end_btn_det_; + + for (idx = 0; idx < sizeof(buf); idx++) { + ret = regmap_read(rt711->regmap, + RT711_BUF_ADDR_HID1 + offset + idx, &val); + if (ret < 0) + goto _end_btn_det_; + buf[idx] = val & 0xff; + } + + if (buf[0] == 0x11) { + switch (buf[1] & 0xf0) { + case 0x10: + btn_type |= SND_JACK_BTN_0; + break; + case 0x20: + btn_type |= SND_JACK_BTN_1; + break; + case 0x40: + btn_type |= SND_JACK_BTN_2; + break; + case 0x80: + btn_type |= SND_JACK_BTN_3; + break; + } + switch (buf[2]) { + case 0x01: + case 0x10: + btn_type |= SND_JACK_BTN_0; + break; + case 0x02: + case 0x20: + btn_type |= SND_JACK_BTN_1; + break; + case 0x04: + case 0x40: + btn_type |= SND_JACK_BTN_2; + break; + case 0x08: + case 0x80: + btn_type |= SND_JACK_BTN_3; + break; + } + } + +_end_btn_det_: + /* Host is owner, so set back to device */ + if (owner == 0) + /* set owner to device */ + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, + RT711_SDCA_CTL_HIDTX_SET_OWNER_TO_DEVICE, 0), 0x01); + + return btn_type; +} + +static int rt711_sdca_headset_detect(struct rt711_sdca_priv *rt711) +{ + unsigned int det_mode; + int ret; + + /* get detected_mode */ + ret = regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_DETECTED_MODE, 0), + &det_mode); + if (ret < 0) + goto io_error; + + switch (det_mode) { + case 0x00: + rt711->jack_type = 0; + break; + case 0x03: + rt711->jack_type = SND_JACK_HEADPHONE; + break; + case 0x05: + rt711->jack_type = SND_JACK_HEADSET; + break; + } + + /* write selected_mode */ + if (det_mode) { + ret = regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_SELECTED_MODE, 0), + det_mode); + if (ret < 0) + goto io_error; + } + + dev_dbg(&rt711->slave->dev, + "%s, detected_mode=0x%x\n", __func__, det_mode); + + return 0; + +io_error: + pr_err_ratelimited("IO error in %s, ret %d\n", __func__, ret); + return ret; +} + +static void rt711_sdca_jack_detect_handler(struct work_struct *work) +{ + struct rt711_sdca_priv *rt711 = + container_of(work, struct rt711_sdca_priv, jack_detect_work.work); + int btn_type = 0, ret; + + if (!rt711->hs_jack) + return; + + if (!rt711->component->card->instantiated) + return; + + /* SDW_SCP_SDCA_INT_SDCA_0 is used for jack detection */ + if (rt711->scp_sdca_stat1 & SDW_SCP_SDCA_INT_SDCA_0) { + ret = rt711_sdca_headset_detect(rt711); + if (ret < 0) + return; + } + + /* SDW_SCP_SDCA_INT_SDCA_8 is used for button detection */ + if (rt711->scp_sdca_stat2 & SDW_SCP_SDCA_INT_SDCA_8) + btn_type = rt711_sdca_button_detect(rt711); + + if (rt711->jack_type == 0) + btn_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); + dev_dbg(&rt711->slave->dev, + "in %s, scp_sdca_stat1=0x%x, scp_sdca_stat2=0x%x\n", __func__, + rt711->scp_sdca_stat1, rt711->scp_sdca_stat2); + + 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)); + } +} + +static void rt711_sdca_btn_check_handler(struct work_struct *work) +{ + struct rt711_sdca_priv *rt711 = + container_of(work, struct rt711_sdca_priv, jack_btn_check_work.work); + int btn_type = 0, ret, idx; + unsigned int det_mode, offset, val; + unsigned char buf[3]; + + ret = regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, RT711_SDCA_CTL_DETECTED_MODE, 0), + &det_mode); + if (ret < 0) + goto io_error; + + /* pin attached */ + if (det_mode) { + /* read UMP message offset */ + ret = regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_HID, RT711_SDCA_ENT_HID01, RT711_SDCA_CTL_HIDTX_MESSAGE_OFFSET, 0), + &offset); + if (ret < 0) + goto io_error; + + for (idx = 0; idx < sizeof(buf); idx++) { + ret = regmap_read(rt711->regmap, + RT711_BUF_ADDR_HID1 + offset + idx, &val); + if (ret < 0) + goto io_error; + buf[idx] = val & 0xff; + } + + if (buf[0] == 0x11) { + switch (buf[1] & 0xf0) { + case 0x10: + btn_type |= SND_JACK_BTN_0; + break; + case 0x20: + btn_type |= SND_JACK_BTN_1; + break; + case 0x40: + btn_type |= SND_JACK_BTN_2; + break; + case 0x80: + btn_type |= SND_JACK_BTN_3; + break; + } + switch (buf[2]) { + case 0x01: + case 0x10: + btn_type |= SND_JACK_BTN_0; + break; + case 0x02: + case 0x20: + btn_type |= SND_JACK_BTN_1; + break; + case 0x04: + case 0x40: + btn_type |= SND_JACK_BTN_2; + break; + case 0x08: + case 0x80: + btn_type |= SND_JACK_BTN_3; + break; + } + } + } else + rt711->jack_type = 0; + + dev_dbg(&rt711->slave->dev, "%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_sdca_jack_init(struct rt711_sdca_priv *rt711) +{ + mutex_lock(&rt711->calibrate_mutex); + + if (rt711->hs_jack) { + /* Enable HID1 event & set button RTC mode */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_PUSH_BTN_INT_CTL6, 0x80f0, 0x8000); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_PUSH_BTN_INT_CTL2, 0x11dd, 0x11dd); + rt711_sdca_index_write(rt711, RT711_VENDOR_HDA_CTL, + RT711_PUSH_BTN_INT_CTL7, 0xffff); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_PUSH_BTN_INT_CTL9, 0xf000, 0x0000); + + /* GE_mode_change_event_en & Hid1_push_button_event_en */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_GE_MODE_RELATED_CTL, 0x0c00, 0x0c00); + + switch (rt711->jd_src) { + case RT711_JD1: + /* default settings was already for JD1 */ + break; + case RT711_JD2: + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_JD_CTL1, RT711_JD2_DIGITAL_MODE_SEL, + RT711_JD2_DIGITAL_MODE_SEL); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_JD_CTL2, RT711_JD2_2PORT_200K_DECODE_HP | RT711_HP_JD_SEL_JD2, + RT711_JD2_2PORT_200K_DECODE_HP | RT711_HP_JD_SEL_JD2); + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_CC_DET1, + RT711_HP_JD_FINAL_RESULT_CTL_JD12, + RT711_HP_JD_FINAL_RESULT_CTL_JD12); + break; + default: + dev_warn(rt711->component->dev, "Wrong JD source\n"); + break; + } + + /* set SCP_SDCA_IntMask1[0]=1 */ + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK1, + SDW_SCP_SDCA_INTMASK_SDCA_0); + /* set SCP_SDCA_IntMask2[0]=1 */ + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK2, + SDW_SCP_SDCA_INTMASK_SDCA_8); + dev_dbg(&rt711->slave->dev, "in %s enable\n", __func__); + } else { + /* disable HID 1/2 event */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_GE_MODE_RELATED_CTL, 0x0c00, 0x0000); + + /* set SCP_SDCA_IntMask1[0]=0 */ + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK1, 0); + /* set SCP_SDCA_IntMask2[0]=0 */ + regmap_write(rt711->regmap, SDW_SCP_SDCA_INTMASK2, 0); + dev_dbg(&rt711->slave->dev, "in %s disable\n", __func__); + } + + mutex_unlock(&rt711->calibrate_mutex); +} + +static int rt711_sdca_set_jack_detect(struct snd_soc_component *component, + struct snd_soc_jack *hs_jack, void *data) +{ + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + rt711->hs_jack = hs_jack; + + if (!rt711->hw_init) { + dev_dbg(&rt711->slave->dev, + "%s hw_init not ready yet\n", __func__); + return 0; + } + + rt711_sdca_jack_init(rt711); + return 0; +} + +/* For SDCA control DAC/ADC Gain */ +static int rt711_sdca_set_gain_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + unsigned int read_l, read_r, gain_l_val, gain_r_val; + unsigned int i, adc_vol_flag = 0; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume") || + strstr(ucontrol->id.name, "FU0F Capture Volume")) + adc_vol_flag = 1; + + /* control value to 2's complement value */ + /* L Channel */ + gain_l_val = ucontrol->value.integer.value[0]; + if (gain_l_val > mc->max) + gain_l_val = mc->max; + read_l = gain_l_val; + + if (mc->shift == 8) /* boost gain */ + gain_l_val = (gain_l_val * 10) << mc->shift; + else { /* ADC/DAC gain */ + if (adc_vol_flag && gain_l_val > mc->shift) + gain_l_val = (gain_l_val - mc->shift) * 75; + else + gain_l_val = (mc->shift - gain_l_val) * 75; + gain_l_val <<= 8; + gain_l_val /= 100; + if (!(adc_vol_flag && read_l > mc->shift)) { + gain_l_val = ~gain_l_val; + gain_l_val += 1; + } + gain_l_val &= 0xffff; + } + + /* R Channel */ + gain_r_val = ucontrol->value.integer.value[1]; + if (gain_r_val > mc->max) + gain_r_val = mc->max; + read_r = gain_r_val; + + if (mc->shift == 8) /* boost gain */ + gain_r_val = (gain_r_val * 10) << mc->shift; + else { /* ADC/DAC gain */ + if (adc_vol_flag && gain_r_val > mc->shift) + gain_r_val = (gain_r_val - mc->shift) * 75; + else + gain_r_val = (mc->shift - gain_r_val) * 75; + gain_r_val <<= 8; + gain_r_val /= 100; + if (!(adc_vol_flag && read_r > mc->shift)) { + gain_r_val = ~gain_r_val; + gain_r_val += 1; + } + gain_r_val &= 0xffff; + } + + for (i = 0; i < 3; i++) { /* retry 3 times at most */ + /* Lch*/ + regmap_write(rt711->mbq_regmap, mc->reg, gain_l_val); + + /* Rch */ + regmap_write(rt711->mbq_regmap, mc->rreg, gain_r_val); + + regmap_read(rt711->mbq_regmap, mc->reg, &read_l); + regmap_read(rt711->mbq_regmap, mc->rreg, &read_r); + if (read_r == gain_r_val && read_l == gain_l_val) + break; + } + + return i == 3 ? -EIO : 0; +} + +static int rt711_sdca_set_gain_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int read_l, read_r, ctl_l = 0, ctl_r = 0; + unsigned int adc_vol_flag = 0, neg_flag = 0; + + if (strstr(ucontrol->id.name, "FU1E Capture Volume") || + strstr(ucontrol->id.name, "FU0F Capture Volume")) + adc_vol_flag = 1; + + regmap_read(rt711->mbq_regmap, mc->reg, &read_l); + regmap_read(rt711->mbq_regmap, mc->rreg, &read_r); + + /* 2's complement value to control value */ + if (mc->shift == 8) /* boost gain */ + ctl_l = (read_l >> mc->shift) / 10; + else { /* ADC/DAC gain */ + ctl_l = read_l; + if (read_l & BIT(15)) { + ctl_l = 0xffff & ~(read_l - 1); + neg_flag = 1; + } + ctl_l *= 100; + ctl_l >>= 8; + if (adc_vol_flag) { + if (neg_flag) + ctl_l = mc->shift - (ctl_l / 75); + else + ctl_l = mc->shift + (ctl_l / 75); + } else + ctl_l = mc->max - (ctl_l / 75); + } + + neg_flag = 0; + if (read_l != read_r) { + if (mc->shift == 8) /* boost gain */ + ctl_r = (read_r >> mc->shift) / 10; + else { /* ADC/DAC gain */ + ctl_r = read_r; + if (read_r & BIT(15)) { + ctl_r = 0xffff & ~(read_r - 1); + neg_flag = 1; + } + ctl_r *= 100; + ctl_r >>= 8; + if (adc_vol_flag) { + if (neg_flag) + ctl_r = mc->shift - (ctl_r / 75); + else + ctl_r = mc->shift + (ctl_r / 75); + } else + ctl_r = mc->max - (ctl_r / 75); + } + } else + ctl_r = ctl_l; + + ucontrol->value.integer.value[0] = ctl_l; + ucontrol->value.integer.value[1] = ctl_r; + + return 0; +} + +static const DECLARE_TLV_DB_SCALE(out_vol_tlv, -6525, 75, 0); +static const DECLARE_TLV_DB_SCALE(in_vol_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(mic_vol_tlv, 0, 1000, 0); + +static const struct snd_kcontrol_new rt711_sdca_snd_controls[] = { + SOC_DOUBLE_R_EXT_TLV("FU05 Playback Volume", + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, RT711_SDCA_CTL_FU_VOLUME, CH_R), + 0x57, 0x57, 0, + rt711_sdca_set_gain_get, rt711_sdca_set_gain_put, out_vol_tlv), + SOC_DOUBLE_R("FU1E Capture Switch", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_MUTE, CH_L), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_MUTE, CH_R), + 0, 1, 1), + SOC_DOUBLE_R("FU0F Capture Switch", + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_MUTE, CH_L), + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_MUTE, CH_R), + 0, 1, 1), + SOC_DOUBLE_R_EXT_TLV("FU1E Capture Volume", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, RT711_SDCA_CTL_FU_VOLUME, CH_R), + 0x17, 0x3f, 0, + rt711_sdca_set_gain_get, rt711_sdca_set_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU0F Capture Volume", + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_VOLUME, CH_L), + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, RT711_SDCA_CTL_FU_VOLUME, CH_R), + 0x17, 0x3f, 0, + rt711_sdca_set_gain_get, rt711_sdca_set_gain_put, in_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU44 Gain Volume", + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PLATFORM_FU44, RT711_SDCA_CTL_FU_CH_GAIN, CH_L), + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PLATFORM_FU44, RT711_SDCA_CTL_FU_CH_GAIN, CH_R), + 8, 3, 0, + rt711_sdca_set_gain_get, rt711_sdca_set_gain_put, mic_vol_tlv), + SOC_DOUBLE_R_EXT_TLV("FU15 Gain Volume", + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PLATFORM_FU15, RT711_SDCA_CTL_FU_CH_GAIN, CH_L), + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PLATFORM_FU15, RT711_SDCA_CTL_FU_CH_GAIN, CH_R), + 8, 3, 0, + rt711_sdca_set_gain_get, rt711_sdca_set_gain_put, mic_vol_tlv), +}; + +static int rt711_sdca_mux_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + unsigned int val = 0, mask_sft; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 10; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + mask_sft = 13; + else + return -EINVAL; + + rt711_sdca_index_read(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_MUX_CTL1, &val); + + ucontrol->value.enumerated.item[0] = (val >> mask_sft) & 0x7; + + return 0; +} + +static int rt711_sdca_mux_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component = + snd_soc_dapm_kcontrol_component(kcontrol); + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned int *item = ucontrol->value.enumerated.item; + unsigned int val, val2 = 0, change, mask_sft; + + if (item[0] >= e->items) + return -EINVAL; + + if (strstr(ucontrol->id.name, "ADC 22 Mux")) + mask_sft = 10; + else if (strstr(ucontrol->id.name, "ADC 23 Mux")) + mask_sft = 13; + else + return -EINVAL; + + val = snd_soc_enum_item_to_val(e, item[0]) << e->shift_l; + + rt711_sdca_index_read(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_MUX_CTL1, &val2); + val2 = (val2 >> mask_sft) & 0x7; + + if (val == val2) + change = 0; + else + change = 1; + + if (change) + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_MUX_CTL1, 0x7 << mask_sft, + val << mask_sft); + + snd_soc_dapm_mux_update_power(dapm, kcontrol, + item[0], e, NULL); + + return change; +} + +static const char * const adc_mux_text[] = { + "MIC2", + "LINE1", + "LINE2", + "DMIC", +}; + +static SOC_ENUM_SINGLE_DECL( + rt711_adc22_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static SOC_ENUM_SINGLE_DECL( + rt711_adc23_enum, SND_SOC_NOPM, 0, adc_mux_text); + +static const struct snd_kcontrol_new rt711_sdca_adc22_mux = + SOC_DAPM_ENUM_EXT("ADC 22 Mux", rt711_adc22_enum, + rt711_sdca_mux_get, rt711_sdca_mux_put); + +static const struct snd_kcontrol_new rt711_sdca_adc23_mux = + SOC_DAPM_ENUM_EXT("ADC 23 Mux", rt711_adc23_enum, + rt711_sdca_mux_get, rt711_sdca_mux_put); + +static int rt711_sdca_fu05_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, + RT711_SDCA_CTL_FU_MUTE, CH_L), + UNMUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, + RT711_SDCA_CTL_FU_MUTE, CH_R), + UNMUTE); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, + RT711_SDCA_CTL_FU_MUTE, CH_L), + MUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU05, + RT711_SDCA_CTL_FU_MUTE, CH_R), + MUTE); + break; + } + return 0; +} + +static int rt711_sdca_fu0f_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, + RT711_SDCA_CTL_FU_MUTE, CH_L), + UNMUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, + RT711_SDCA_CTL_FU_MUTE, CH_R), + UNMUTE); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, + RT711_SDCA_CTL_FU_MUTE, CH_L), + MUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_USER_FU0F, + RT711_SDCA_CTL_FU_MUTE, CH_R), + MUTE); + break; + } + return 0; +} + +static int rt711_sdca_fu1e_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, + RT711_SDCA_CTL_FU_MUTE, CH_L), + UNMUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, + RT711_SDCA_CTL_FU_MUTE, CH_R), + UNMUTE); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, + RT711_SDCA_CTL_FU_MUTE, CH_L), + MUTE); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_USER_FU1E, + RT711_SDCA_CTL_FU_MUTE, CH_R), + MUTE); + break; + } + return 0; +} + +static int rt711_sdca_pde28_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDE28, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDE28, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + } + return 0; +} + +static int rt711_sdca_pde29_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDE29, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDE29, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + } + return 0; +} + +static int rt711_sdca_pde2a_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PDE2A, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_PDE2A, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + } + return 0; +} + +static int rt711_sdca_line1_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + static unsigned int sel_mode = 0xffff; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_read(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, + RT711_SDCA_CTL_SELECTED_MODE, 0), + &sel_mode); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_LINE1, + RT711_SDCA_CTL_VENDOR_DEF, 0), + 0x1); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, + RT711_SDCA_CTL_SELECTED_MODE, 0), + 0x7); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_LINE1, + RT711_SDCA_CTL_VENDOR_DEF, 0), + 0x0); + if (sel_mode != 0xffff) + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_GE49, + RT711_SDCA_CTL_SELECTED_MODE, 0), + sel_mode); + break; + } + + return 0; +} + +static int rt711_sdca_line2_power_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_component *component = + snd_soc_dapm_to_component(w->dapm); + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDELINE2, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS0); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_LINE2, + RT711_SDCA_CTL_VENDOR_DEF, 0), + 0x1); + break; + case SND_SOC_DAPM_PRE_PMD: + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_LINE2, + RT711_SDCA_CTL_VENDOR_DEF, 0), + 0x0); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_PDELINE2, + RT711_SDCA_CTL_REQ_POWER_STATE, 0), + PS3); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget rt711_sdca_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("HP"), + SND_SOC_DAPM_INPUT("MIC2"), + SND_SOC_DAPM_INPUT("DMIC1"), + SND_SOC_DAPM_INPUT("DMIC2"), + SND_SOC_DAPM_INPUT("LINE1"), + SND_SOC_DAPM_INPUT("LINE2"), + + SND_SOC_DAPM_PGA_E("LINE1 Power", SND_SOC_NOPM, + 0, 0, NULL, 0, rt711_sdca_line1_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_PGA_E("LINE2 Power", SND_SOC_NOPM, + 0, 0, NULL, 0, rt711_sdca_line2_power_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_SUPPLY("PDE 28", SND_SOC_NOPM, 0, 0, + rt711_sdca_pde28_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE 29", SND_SOC_NOPM, 0, 0, + rt711_sdca_pde29_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_SUPPLY("PDE 2A", SND_SOC_NOPM, 0, 0, + rt711_sdca_pde2a_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + SND_SOC_DAPM_DAC_E("FU 05", NULL, SND_SOC_NOPM, 0, 0, + rt711_sdca_fu05_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU 0F", NULL, SND_SOC_NOPM, 0, 0, + rt711_sdca_fu0f_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_ADC_E("FU 1E", NULL, SND_SOC_NOPM, 0, 0, + rt711_sdca_fu1e_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MUX("ADC 22 Mux", SND_SOC_NOPM, 0, 0, + &rt711_sdca_adc22_mux), + SND_SOC_DAPM_MUX("ADC 23 Mux", SND_SOC_NOPM, 0, 0, + &rt711_sdca_adc23_mux), + + SND_SOC_DAPM_AIF_IN("DP3RX", "DP3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP2TX", "DP2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DP4TX", "DP4 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route rt711_sdca_audio_map[] = { + {"FU 05", NULL, "DP3RX"}, + {"DP2TX", NULL, "FU 0F"}, + {"DP4TX", NULL, "FU 1E"}, + + {"LINE1 Power", NULL, "LINE1"}, + {"LINE2 Power", NULL, "LINE2"}, + {"HP", NULL, "PDE 28"}, + {"FU 0F", NULL, "PDE 29"}, + {"FU 1E", NULL, "PDE 2A"}, + + {"FU 0F", NULL, "ADC 22 Mux"}, + {"FU 1E", NULL, "ADC 23 Mux"}, + {"ADC 22 Mux", "DMIC", "DMIC1"}, + {"ADC 22 Mux", "LINE1", "LINE1 Power"}, + {"ADC 22 Mux", "LINE2", "LINE2 Power"}, + {"ADC 22 Mux", "MIC2", "MIC2"}, + {"ADC 23 Mux", "DMIC", "DMIC2"}, + {"ADC 23 Mux", "LINE1", "LINE1 Power"}, + {"ADC 23 Mux", "LINE2", "LINE2 Power"}, + {"ADC 23 Mux", "MIC2", "MIC2"}, + + {"HP", NULL, "FU 05"}, +}; + +static int rt711_sdca_parse_dt(struct rt711_sdca_priv *rt711, struct device *dev) +{ + device_property_read_u32(dev, "realtek,jd-src", &rt711->jd_src); + + return 0; +} + +static int rt711_sdca_probe(struct snd_soc_component *component) +{ + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + + rt711_sdca_parse_dt(rt711, &rt711->slave->dev); + rt711->component = component; + + return 0; +} + +static const struct snd_soc_component_driver soc_sdca_dev_rt711 = { + .probe = rt711_sdca_probe, + .controls = rt711_sdca_snd_controls, + .num_controls = ARRAY_SIZE(rt711_sdca_snd_controls), + .dapm_widgets = rt711_sdca_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(rt711_sdca_dapm_widgets), + .dapm_routes = rt711_sdca_audio_map, + .num_dapm_routes = ARRAY_SIZE(rt711_sdca_audio_map), + .set_jack = rt711_sdca_set_jack_detect, +}; + +static int rt711_sdca_set_sdw_stream(struct snd_soc_dai *dai, void *sdw_stream, + int direction) +{ + struct sdw_stream_data *stream; + + if (!sdw_stream) + return 0; + + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + + stream->sdw_stream = sdw_stream; + + /* Use tx_mask or rx_mask to configure stream tag and set dma_data */ + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + dai->playback_dma_data = stream; + else + dai->capture_dma_data = stream; + + return 0; +} + +static void rt711_sdca_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct sdw_stream_data *stream; + + stream = snd_soc_dai_get_dma_data(dai, substream); + snd_soc_dai_set_dma_data(dai, substream, NULL); + kfree(stream); +} + +static int rt711_sdca_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + struct sdw_stream_config stream_config; + struct sdw_port_config port_config; + enum sdw_data_direction direction; + struct sdw_stream_data *stream; + int retval, port, num_channels; + unsigned int sampling_rate; + + dev_dbg(dai->dev, "%s %s", __func__, dai->name); + stream = snd_soc_dai_get_dma_data(dai, substream); + + if (!stream) + return -EINVAL; + + if (!rt711->slave) + return -EINVAL; + + /* SoundWire specific configuration */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + direction = SDW_DATA_DIR_RX; + port = 3; + } else { + direction = SDW_DATA_DIR_TX; + if (dai->id == RT711_AIF1) + port = 2; + else if (dai->id == RT711_AIF2) + port = 4; + else + return -EINVAL; + } + + stream_config.frame_rate = params_rate(params); + stream_config.ch_count = params_channels(params); + stream_config.bps = snd_pcm_format_width(params_format(params)); + stream_config.direction = direction; + + num_channels = params_channels(params); + port_config.ch_mask = GENMASK(num_channels - 1, 0); + port_config.num = port; + + retval = sdw_stream_add_slave(rt711->slave, &stream_config, + &port_config, 1, stream->sdw_stream); + if (retval) { + dev_err(dai->dev, "Unable to configure port\n"); + return retval; + } + + if (params_channels(params) > 16) { + dev_err(component->dev, "Unsupported channels %d\n", + params_channels(params)); + return -EINVAL; + } + + /* sampling rate configuration */ + switch (params_rate(params)) { + case 44100: + sampling_rate = RT711_SDCA_RATE_44100HZ; + break; + case 48000: + sampling_rate = RT711_SDCA_RATE_48000HZ; + break; + case 96000: + sampling_rate = RT711_SDCA_RATE_96000HZ; + break; + case 192000: + sampling_rate = RT711_SDCA_RATE_192000HZ; + break; + default: + dev_err(component->dev, "Rate %d is not supported\n", + params_rate(params)); + return -EINVAL; + } + + /* set sampling frequency */ + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_CS01, RT711_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), + sampling_rate); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_CS11, RT711_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), + sampling_rate); + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_MIC_ARRAY, RT711_SDCA_ENT_CS1F, RT711_SDCA_CTL_SAMPLE_FREQ_INDEX, 0), + sampling_rate); + + return 0; +} + +static int rt711_sdca_pcm_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_component *component = dai->component; + struct rt711_sdca_priv *rt711 = snd_soc_component_get_drvdata(component); + struct sdw_stream_data *stream = + snd_soc_dai_get_dma_data(dai, substream); + + if (!rt711->slave) + return -EINVAL; + + sdw_stream_remove_slave(rt711->slave, stream->sdw_stream); + return 0; +} + +#define RT711_STEREO_RATES (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) +#define RT711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops rt711_sdca_ops = { + .hw_params = rt711_sdca_pcm_hw_params, + .hw_free = rt711_sdca_pcm_hw_free, + .set_sdw_stream = rt711_sdca_set_sdw_stream, + .shutdown = rt711_sdca_shutdown, +}; + +static struct snd_soc_dai_driver rt711_sdca_dai[] = { + { + .name = "rt711-sdca-aif1", + .id = RT711_AIF1, + .playback = { + .stream_name = "DP3 Playback", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .capture = { + .stream_name = "DP2 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .ops = &rt711_sdca_ops, + }, + { + .name = "rt711-sdca-aif2", + .id = RT711_AIF2, + .capture = { + .stream_name = "DP4 Capture", + .channels_min = 1, + .channels_max = 2, + .rates = RT711_STEREO_RATES, + .formats = RT711_FORMATS, + }, + .ops = &rt711_sdca_ops, + } +}; + +int rt711_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave) +{ + struct rt711_sdca_priv *rt711; + int ret; + + rt711 = devm_kzalloc(dev, sizeof(*rt711), GFP_KERNEL); + if (!rt711) + return -ENOMEM; + + dev_set_drvdata(dev, rt711); + rt711->slave = slave; + rt711->regmap = regmap; + rt711->mbq_regmap = mbq_regmap; + + /* + * Mark hw_init to false + * HW init will be performed when device reports present + */ + rt711->hw_init = false; + rt711->first_hw_init = false; + + /* JD source uses JD2 in default */ + rt711->jd_src = RT711_JD2; + + ret = devm_snd_soc_register_component(dev, + &soc_sdca_dev_rt711, + rt711_sdca_dai, + ARRAY_SIZE(rt711_sdca_dai)); + + dev_dbg(&slave->dev, "%s\n", __func__); + + return ret; +} + +static void rt711_sdca_vd0_io_init(struct rt711_sdca_priv *rt711) +{ + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_GPIO_TEST_MODE_CTL2, 0x0e00); + rt711_sdca_index_write(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_GPIO_CTL, 0x0008); + + regmap_write(rt711->regmap, 0x2f5a, 0x01); + + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_ADC27_VOL_SET, 0x8728); + + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL3, 0xa472); + + regmap_write(rt711->regmap, 0x2f50, 0x02); + + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_ANALOG_CTL, + RT711_MISC_POWER_CTL4, 0x6000, 0x6000); + + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL3, 0x000c, 0x000c); + + rt711_sdca_index_write(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_CONFIG_CTL, 0x0000); + + rt711_sdca_index_write(rt711, RT711_VENDOR_VAD, + RT711_VAD_SRAM_CTL1, 0x0050); +} + +static void rt711_sdca_vd1_io_init(struct rt711_sdca_priv *rt711) +{ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_UNSOLICITED_CTL, 0x0300, 0x0000); + + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_COMBO_JACK_AUTO_CTL3, 0xa43e); + + regmap_write(rt711->regmap, 0x2f5a, 0x05); + + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_JD_CTRL6, 0x0500); + + rt711_sdca_index_write(rt711, RT711_VENDOR_REG, + RT711_DMIC_CTL1, 0x6173); + + rt711_sdca_index_write(rt711, RT711_VENDOR_HDA_CTL, + RT711_HDA_LEGACY_CONFIG_CTL, 0x0000); + + rt711_sdca_index_write(rt711, RT711_VENDOR_VAD, + RT711_VAD_SRAM_CTL1, 0x0050); +} + +int rt711_sdca_io_init(struct device *dev, struct sdw_slave *slave) +{ + struct rt711_sdca_priv *rt711 = dev_get_drvdata(dev); + int ret = 0; + unsigned int val; + + if (rt711->hw_init) + return 0; + + if (rt711->first_hw_init) { + regcache_cache_only(rt711->regmap, false); + regcache_cache_bypass(rt711->regmap, true); + } else { + /* + * PM runtime is only enabled when a Slave reports as Attached + */ + + /* set autosuspend parameters */ + pm_runtime_set_autosuspend_delay(&slave->dev, 3000); + pm_runtime_use_autosuspend(&slave->dev); + + /* update count of parent 'active' children */ + pm_runtime_set_active(&slave->dev); + + /* make sure the device does not suspend immediately */ + pm_runtime_mark_last_busy(&slave->dev); + + pm_runtime_enable(&slave->dev); + } + + pm_runtime_get_noresume(&slave->dev); + + rt711_sdca_reset(rt711); + + rt711_sdca_index_read(rt711, RT711_VENDOR_REG, RT711_JD_PRODUCT_NUM, &val); + rt711->hw_ver = val & 0xf; + + if (rt711->hw_ver == RT711_VER_VD0) + rt711_sdca_vd0_io_init(rt711); + else + rt711_sdca_vd1_io_init(rt711); + + /* DP4 mux select from 08_filter_Out_pri */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_REG, + RT711_FILTER_SRC_SEL, 0x1800, 0x0800); + + /* ge_exclusive_inbox_en disable */ + rt711_sdca_index_update_bits(rt711, RT711_VENDOR_HDA_CTL, + RT711_PUSH_BTN_INT_CTL0, 0x20, 0x00); + + if (!rt711->first_hw_init) { + INIT_DELAYED_WORK(&rt711->jack_detect_work, + rt711_sdca_jack_detect_handler); + INIT_DELAYED_WORK(&rt711->jack_btn_check_work, + rt711_sdca_btn_check_handler); + mutex_init(&rt711->calibrate_mutex); + } + + /* calibration */ + ret = rt711_sdca_calibration(rt711); + if (ret < 0) + dev_err(dev, "%s, calibration failed!\n", __func__); + + /* HP output enable */ + regmap_write(rt711->regmap, + SDW_SDCA_CTL(FUN_JACK_CODEC, RT711_SDCA_ENT_OT1, RT711_SDCA_CTL_VENDOR_DEF, 0), 0x4); + + /* + * if set_jack callback occurred early than io_init, + * we set up the jack detection function now + */ + if (rt711->hs_jack) + rt711_sdca_jack_init(rt711); + + if (rt711->first_hw_init) { + regcache_cache_bypass(rt711->regmap, false); + regcache_mark_dirty(rt711->regmap); + } else + rt711->first_hw_init = true; + + /* Mark Slave initialization complete */ + rt711->hw_init = true; + + pm_runtime_mark_last_busy(&slave->dev); + pm_runtime_put_autosuspend(&slave->dev); + + dev_dbg(&slave->dev, "%s hw_init complete\n", __func__); + return 0; +} + +MODULE_DESCRIPTION("ASoC RT711 SDCA SDW driver"); +MODULE_AUTHOR("Shuming Fan shumingf@realtek.com"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/rt711-sdca.h b/sound/soc/codecs/rt711-sdca.h new file mode 100644 index 000000000000..c85b0afd7343 --- /dev/null +++ b/sound/soc/codecs/rt711-sdca.h @@ -0,0 +1,246 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * rt711-sdca.h -- RT711 SDCA ALSA SoC audio driver header + * + * Copyright(c) 2020 Realtek Semiconductor Corp. + */ + +#ifndef __RT711_SDCA_H__ +#define __RT711_SDCA_H__ + +#include <linux/pm.h> +#include <linux/regmap.h> +#include <linux/soundwire/sdw.h> +#include <linux/soundwire/sdw_type.h> +#include <sound/soc.h> +#include <linux/workqueue.h> + +struct rt711_sdca_priv { + struct regmap *regmap, *mbq_regmap; + struct snd_soc_component *component; + struct sdw_slave *slave; + enum sdw_slave_status status; + struct sdw_bus_params params; + bool hw_init; + bool first_hw_init; + struct snd_soc_jack *hs_jack; + struct delayed_work jack_detect_work; + struct delayed_work jack_btn_check_work; + struct mutex calibrate_mutex; /* for headset calibration */ + int jack_type, jd_src; + unsigned int scp_sdca_stat1, scp_sdca_stat2; + int hw_ver; +}; + +struct sdw_stream_data { + struct sdw_stream_runtime *sdw_stream; +}; + +/* NID */ +#define RT711_AUDIO_FUNCTION_GROUP 0x01 +#define RT711_DAC_OUT2 0x03 +#define RT711_ADC_IN1 0x09 +#define RT711_ADC_IN2 0x08 +#define RT711_DMIC1 0x12 +#define RT711_DMIC2 0x13 +#define RT711_MIC2 0x19 +#define RT711_LINE1 0x1a +#define RT711_LINE2 0x1b +#define RT711_BEEP 0x1d +#define RT711_VENDOR_REG 0x20 +#define RT711_HP_OUT 0x21 +#define RT711_MIXER_IN1 0x22 +#define RT711_MIXER_IN2 0x23 +#define RT711_INLINE_CMD 0x55 +#define RT711_VENDOR_CALI 0x58 +#define RT711_VENDOR_IMS_DRE 0x5b +#define RT711_VENDOR_VAD 0x5e +#define RT711_VENDOR_ANALOG_CTL 0x5f +#define RT711_VENDOR_HDA_CTL 0x61 + +/* Index (NID:20h) */ +#define RT711_JD_PRODUCT_NUM 0x00 +#define RT711_DMIC_CTL1 0x06 +#define RT711_JD_CTL1 0x08 +#define RT711_JD_CTL2 0x09 +#define RT711_CC_DET1 0x11 +#define RT711_PARA_VERB_CTL 0x1a +#define RT711_COMBO_JACK_AUTO_CTL1 0x45 +#define RT711_COMBO_JACK_AUTO_CTL2 0x46 +#define RT711_COMBO_JACK_AUTO_CTL3 0x47 +#define RT711_INLINE_CMD_CTL 0x48 +#define RT711_DIGITAL_MISC_CTRL4 0x4a +#define RT711_JD_CTRL6 0x6a +#define RT711_VREFOUT_CTL 0x6b +#define RT711_GPIO_TEST_MODE_CTL2 0x6d +#define RT711_FSM_CTL 0x6f +#define RT711_IRQ_FLAG_TABLE1 0x80 +#define RT711_IRQ_FLAG_TABLE2 0x81 +#define RT711_IRQ_FLAG_TABLE3 0x82 +#define RT711_HP_FSM_CTL 0x83 +#define RT711_TX_RX_MUX_CTL 0x91 +#define RT711_FILTER_SRC_SEL 0xb0 +#define RT711_ADC27_VOL_SET 0xb7 + +/* Index (NID:58h) */ +#define RT711_DAC_DC_CALI_CTL1 0x00 +#define RT711_DAC_DC_CALI_CTL2 0x01 + +/* Index (NID:5bh) */ +#define RT711_IMS_DIGITAL_CTL1 0x00 +#define RT711_HP_IMS_RESULT_L 0x20 +#define RT711_HP_IMS_RESULT_R 0x21 + +/* Index (NID:5eh) */ +#define RT711_VAD_SRAM_CTL1 0x10 + +/* Index (NID:5fh) */ +#define RT711_MISC_POWER_CTL0 0x01 +#define RT711_MISC_POWER_CTL4 0x05 + +/* Index (NID:61h) */ +#define RT711_HDA_LEGACY_MUX_CTL1 0x00 +#define RT711_HDA_LEGACY_UNSOLICITED_CTL 0x03 +#define RT711_HDA_LEGACY_CONFIG_CTL 0x06 +#define RT711_HDA_LEGACY_RESET_CTL 0x08 +#define RT711_HDA_LEGACY_GPIO_CTL 0x0a +#define RT711_ADC08_09_PDE_CTL 0x24 +#define RT711_GE_MODE_RELATED_CTL 0x35 +#define RT711_PUSH_BTN_INT_CTL0 0x36 +#define RT711_PUSH_BTN_INT_CTL1 0x37 +#define RT711_PUSH_BTN_INT_CTL2 0x38 +#define RT711_PUSH_BTN_INT_CTL6 0x3c +#define RT711_PUSH_BTN_INT_CTL7 0x3d +#define RT711_PUSH_BTN_INT_CTL9 0x3f + +/* DAC DC offset calibration control-1 (0x00)(NID:20h) */ +#define RT711_DAC_DC_CALI_TRIGGER (0x1 << 15) +#define RT711_DAC_DC_CALI_CLK_EN (0x1 << 14) +#define RT711_DAC_DC_FORCE_CALI_RST (0x1 << 3) + +/* jack detect control 1 (0x08)(NID:20h) */ +#define RT711_JD2_DIGITAL_MODE_SEL (0x1 << 1) + +/* jack detect control 2 (0x09)(NID:20h) */ +#define RT711_JD2_2PORT_200K_DECODE_HP (0x1 << 13) +#define RT711_HP_JD_SEL_JD1 (0x0 << 1) +#define RT711_HP_JD_SEL_JD2 (0x1 << 1) + +/* CC DET1 (0x11)(NID:20h) */ +#define RT711_HP_JD_FINAL_RESULT_CTL_JD12 (0x1 << 10) +#define RT711_HP_JD_FINAL_RESULT_CTL_CCDET (0x0 << 10) + +/* Parameter & Verb control (0x1a)(NID:20h) */ +#define RT711_HIDDEN_REG_SW_RESET (0x1 << 14) + +/* combo jack auto switch control 2 (0x46)(NID:20h) */ +#define RT711_COMBOJACK_AUTO_DET_STATUS (0x1 << 11) +#define RT711_COMBOJACK_AUTO_DET_TRS (0x1 << 10) +#define RT711_COMBOJACK_AUTO_DET_CTIA (0x1 << 9) +#define RT711_COMBOJACK_AUTO_DET_OMTP (0x1 << 8) + +/* FSM control (0x6f)(NID:20h) */ +#define RT711_CALI_CTL (0x0 << 0) +#define RT711_COMBOJACK_CTL (0x1 << 0) +#define RT711_IMS_CTL (0x2 << 0) +#define RT711_DEPOP_CTL (0x3 << 0) +#define RT711_FSM_IMP_EN (0x1 << 6) + +/* Impedance Sense Digital Control 1 (0x00)(NID:5bh) */ +#define RT711_TRIGGER_IMS (0x1 << 15) +#define RT711_IMS_EN (0x1 << 6) + +#define RT711_EAPD_HIGH 0x2 +#define RT711_EAPD_LOW 0x0 +#define RT711_MUTE_SFT 7 +/* set input/output mapping to payload[14][15] separately */ +#define RT711_DIR_IN_SFT 6 +#define RT711_DIR_OUT_SFT 7 + +/* RC Calibration register */ +#define RT711_RC_CAL_STATUS 0x320c + +/* Buffer address for HID */ +#define RT711_BUF_ADDR_HID1 0x44030000 +#define RT711_BUF_ADDR_HID2 0x44030020 + +/* RT711 SDCA function topology */ +#define FUN_JACK_CODEC 0x01 +#define FUN_MIC_ARRAY 0x02 +#define FUN_HID 0x03 + +/* RT711 SDCA entity */ +#define RT711_SDCA_ENT_HID01 0x01 +#define RT711_SDCA_ENT_GE49 0x49 +#define RT711_SDCA_ENT_USER_FU05 0x05 +#define RT711_SDCA_ENT_USER_FU0F 0x0f +#define RT711_SDCA_ENT_USER_FU1E 0x1e +#define RT711_SDCA_ENT_PLATFORM_FU15 0x15 +#define RT711_SDCA_ENT_PLATFORM_FU44 0x44 +#define RT711_SDCA_ENT_PDE28 0x28 +#define RT711_SDCA_ENT_PDE29 0x29 +#define RT711_SDCA_ENT_PDE2A 0x2a +#define RT711_SDCA_ENT_CS01 0x01 +#define RT711_SDCA_ENT_CS11 0x11 +#define RT711_SDCA_ENT_CS1F 0x1f +#define RT711_SDCA_ENT_OT1 0x06 +#define RT711_SDCA_ENT_LINE1 0x09 +#define RT711_SDCA_ENT_LINE2 0x31 +#define RT711_SDCA_ENT_PDELINE2 0x36 +#define RT711_SDCA_ENT_USER_FU9 0x41 + +/* RT711 SDCA control */ +#define RT711_SDCA_CTL_SAMPLE_FREQ_INDEX 0x10 +#define RT711_SDCA_CTL_FU_CH_GAIN 0x0b +#define RT711_SDCA_CTL_FU_MUTE 0x01 +#define RT711_SDCA_CTL_FU_VOLUME 0x02 +#define RT711_SDCA_CTL_HIDTX_CURRENT_OWNER 0x10 +#define RT711_SDCA_CTL_HIDTX_SET_OWNER_TO_DEVICE 0x11 +#define RT711_SDCA_CTL_HIDTX_MESSAGE_OFFSET 0x12 +#define RT711_SDCA_CTL_HIDTX_MESSAGE_LENGTH 0x13 +#define RT711_SDCA_CTL_SELECTED_MODE 0x01 +#define RT711_SDCA_CTL_DETECTED_MODE 0x02 +#define RT711_SDCA_CTL_REQ_POWER_STATE 0x01 +#define RT711_SDCA_CTL_VENDOR_DEF 0x30 + +/* RT711 SDCA channel */ +#define CH_L 0x01 +#define CH_R 0x02 + +/* Power State */ +#define PS0 0x00 +#define PS3 0x03 + +/* Mute Control */ +#define UNMUTE 0x00 +#define MUTE 0x01 + +/* sample frequency index */ +#define RT711_SDCA_RATE_44100HZ 0x08 +#define RT711_SDCA_RATE_48000HZ 0x09 +#define RT711_SDCA_RATE_96000HZ 0x0b +#define RT711_SDCA_RATE_192000HZ 0x0d + +enum { + RT711_AIF1, + RT711_AIF2, + RT711_AIFS, +}; + +enum rt711_sdca_jd_src { + RT711_JD_NULL, + RT711_JD1, + RT711_JD2 +}; + +enum rt711_sdca_ver { + RT711_VER_VD0, + RT711_VER_VD1 +}; + +int rt711_sdca_io_init(struct device *dev, struct sdw_slave *slave); +int rt711_sdca_init(struct device *dev, struct regmap *regmap, + struct regmap *mbq_regmap, struct sdw_slave *slave); + +int rt711_sdca_jack_detect(struct rt711_sdca_priv *rt711, bool *hp, bool *mic); +#endif /* __RT711_SDCA_H__ */
-----Original Message----- From: Bard Liao yung-chuan.liao@linux.intel.com Sent: Monday, November 30, 2020 10:40 PM To: alsa-devel@alsa-project.org; vkoul@kernel.org Cc: vinod.koul@linaro.org; linux-kernel@vger.kernel.org; tiwai@suse.de; broonie@kernel.org; gregkh@linuxfoundation.org; jank@cadence.com; srinivas.kandagatla@linaro.org; rander.wang@linux.intel.com; ranjani.sridharan@linux.intel.com; hui.wang@canonical.com; pierre- louis.bossart@linux.intel.com; Kale, Sanyog R sanyog.r.kale@intel.com; Liao, Bard bard.liao@intel.com Subject: [PATCH v2 0/5] regmap/SoundWire/ASoC: Add SoundWire SDCA support
The MIPI SoundWire Device Class standard will define audio functionality beyond the scope of the existing SoundWire 1.2 standard, which is limited to the bus and interface.
The description is inspired by the USB Audio Class, with "functions", "entities", "control selectors", "audio clusters". The main difference with the USB Audio class is that the devices are typically on a motherboard and descriptors stored in platform firmware instead of being retrieved from the device.
The current set of devices managed in this patchset are conformant with the SDCA 0.6 specification and require dedicated drivers since the descriptors and platform firmware specification is not complete at this time. They do however rely on the hierarchical addressing required by the SDCA standard. Future devices conformant with SDCA 1.0 should rely on a class driver.
This series adds support for the hierarchical SDCA addressing and extends regmap. It then provides 3 codecs for RT711-sdca headset codec, RT1316 amplifier and RT715-scda microphone codec.
Note that the release of this code before the formal adoption of the SDCA 1.0 specification was formally endorsed by the MIPI Board to make sure there is no delay for Linux-based support of this specification.
v2:
- rt715-sdca: Use rt715_sdca prefix to avoid compiling issue.
- rt715-sdca: Merge multiple mute/volume operation into single
mute/volume operation
- rt711-sdca: Initial ret = 0 as it could be used uninitialized.
Jack Yu (1): ASoC/SoundWire: rt715-sdca: First version of rt715 sdw sdca codec driver
Pierre-Louis Bossart (2): soundwire: SDCA: add helper macro to access controls regmap/SoundWire: sdw: add support for SoundWire 1.2 MBQ
Shuming Fan (2): ASoC/SoundWire: rt1316: Add RT1316 SDCA vendor-specific driver ASoC/SoundWire: rt711-sdca: Add RT711 SDCA vendor-specific driver
Hi Vinod/Mark,
Could we take this series into Vinod's tree with Mark's Acked-by? It failed to build on Mark's tree.
-- 2.17.1
On 11/30/20 10:11 PM, Vinod Koul wrote:
On 01-12-20, 02:42, Liao, Bard wrote:
Hi Vinod/Mark,
Could we take this series into Vinod's tree with Mark's Acked-by? It failed to build on Mark's tree.
I see Mark has already applied 1-3 ..
Sorry, I thought Mark had reversed the entire series.
Vinod, would you mind providing a tag for Mark then? The following commit is needed to compile:
b7cab9be7c161 ('soundwire: SDCA: detect sdca_cascade interrupt')
Thank you Sir!
On Tue, Dec 01, 2020 at 08:35:42AM -0600, Pierre-Louis Bossart wrote:
On 11/30/20 10:11 PM, Vinod Koul wrote:
I see Mark has already applied 1-3 ..
Sorry, I thought Mark had reversed the entire series.
Yeah, I just backed out the one change for the driver.
Vinod, would you mind providing a tag for Mark then? The following commit is needed to compile:
b7cab9be7c161 ('soundwire: SDCA: detect sdca_cascade interrupt')
That'd work, looks like there's only one fix patch it's based off.
Hi Mark,
On 01-12-20, 14:53, Mark Brown wrote:
On Tue, Dec 01, 2020 at 08:35:42AM -0600, Pierre-Louis Bossart wrote:
On 11/30/20 10:11 PM, Vinod Koul wrote:
I see Mark has already applied 1-3 ..
Sorry, I thought Mark had reversed the entire series.
Yeah, I just backed out the one change for the driver.
Vinod, would you mind providing a tag for Mark then? The following commit is needed to compile:
b7cab9be7c161 ('soundwire: SDCA: detect sdca_cascade interrupt')
That'd work, looks like there's only one fix patch it's based off.
I have created the tag, please pull:
The following changes since commit 3650b228f83adda7e5ee532e2b90429c03f7b9ec:
Linux 5.10-rc1 (2020-10-25 15:14:11 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git tags/soundwire-for-asoc-5.11
for you to fetch changes up to b7cab9be7c16128a0de21ed7ae67211838813437:
soundwire: SDCA: detect sdca_cascade interrupt (2020-11-24 14:09:31 +0530)
---------------------------------------------------------------- soundwire-for-asoc-5.11
Tag for asoc to resolve build dependency with commit b7cab9be7c16 ("soundwire: SDCA: detect sdca_cascade interrupt")
---------------------------------------------------------------- Pierre-Louis Bossart (1): soundwire: SDCA: detect sdca_cascade interrupt
Srinivas Kandagatla (1): soundwire: Fix DEBUG_LOCKS_WARN_ON for uninitialized attribute
drivers/soundwire/bus.c | 28 +++++++++++++++++++++++++++- drivers/soundwire/sysfs_slave_dpn.c | 1 + include/linux/soundwire/sdw.h | 4 ++++ 3 files changed, 32 insertions(+), 1 deletion(-)
On Wed, Dec 02, 2020 at 12:44:05PM +0530, Vinod Koul wrote:
Linux 5.10-rc1 (2020-10-25 15:14:11 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/vkoul/soundwire.git tags/soundwire-for-asoc-5.11
for you to fetch changes up to b7cab9be7c16128a0de21ed7ae67211838813437:
soundwire: SDCA: detect sdca_cascade interrupt (2020-11-24 14:09:31 +0530)
Pulled in, thanks.
On Mon, 30 Nov 2020 22:40:15 +0800, Bard Liao wrote:
The MIPI SoundWire Device Class standard will define audio functionality beyond the scope of the existing SoundWire 1.2 standard, which is limited to the bus and interface.
The description is inspired by the USB Audio Class, with "functions", "entities", "control selectors", "audio clusters". The main difference with the USB Audio class is that the devices are typically on a motherboard and descriptors stored in platform firmware instead of being retrieved from the device.
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[3/5] ASoC/SoundWire: rt715-sdca: First version of rt715 sdw sdca codec driver commit: 6f4a038b99677f4db737841b81b9d45ed4b54966 [4/5] ASoC/SoundWire: rt1316: Add RT1316 SDCA vendor-specific driver (no commit info) [5/5] ASoC/SoundWire: rt711-sdca: Add RT711 SDCA vendor-specific driver (no commit info)
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (5)
-
Bard Liao
-
Liao, Bard
-
Mark Brown
-
Pierre-Louis Bossart
-
Vinod Koul