[PATCH 0/6] Enable audio playback in AM62x SoC

This series enables audio playback over u-boot for TI AM62x SoC. It ports MCASP driver which acts as I2S audio signal generator and TLV320AIC3106 which converts the digital audio to analog. Lastly, this also adds sound driver to complete the audio graph and enables audio playback using u-boot command.
Audio can be played over at u-boot prompt using below command : sound play <duration in ms> <frequency> This plays a beep sound for the given duration and of the given frequency.
Scaria Kochidanadu (6): sound: ti: Add sound support for am625 board in Uboot sound: ti: Add TLV320AIC3106 Codec sound: ti: Add MCASP driver for transfer of Audio data to sound codec arm: dts: k3-am625-sk-u-boot.dtsi: Add sound driver nodes configs: am62x_evm_a53_defconfig: Enable sound and I2C commands linux: bitmap.h: Add for_each_set_bit_from() function
MAINTAINERS | 5 + arch/arm/dts/k3-am625-sk-u-boot.dtsi | 85 ++ configs/am62x_evm_a53_defconfig | 9 + drivers/sound/Kconfig | 18 + drivers/sound/Makefile | 3 + drivers/sound/davinci-mcasp.h | 413 ++++++++++ drivers/sound/mcasp_i2s.c | 1123 ++++++++++++++++++++++++++ drivers/sound/ti_sound.c | 119 +++ drivers/sound/tlv320aic3106.c | 758 +++++++++++++++++ drivers/sound/tlv320aic3106.h | 336 ++++++++ include/linux/bitmap.h | 5 + 11 files changed, 2874 insertions(+) create mode 100644 drivers/sound/davinci-mcasp.h create mode 100644 drivers/sound/mcasp_i2s.c create mode 100644 drivers/sound/ti_sound.c create mode 100644 drivers/sound/tlv320aic3106.c create mode 100644 drivers/sound/tlv320aic3106.h

Add a UCLASS_SOUND driver for Texas Instruments SoCs which ties together the tlv320aic3106 audio codec and MCASP I2S controller. Enable audio playback functionality by taking a data pointer and data size as the sound data. The uboot sound play command takes time and frequency as input and creates the data for a beep sound with the given parameters, which is then passed to the sound play function.
The code is based on the samsung sound driver[1] in uboot.
Link: https://gitlab.com/u-boot/u-boot/-/blob/master/drivers/sound/samsung_sound.c [1]
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- MAINTAINERS | 1 + drivers/sound/Kconfig | 10 ++++ drivers/sound/Makefile | 1 + drivers/sound/ti_sound.c | 119 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 drivers/sound/ti_sound.c
diff --git a/MAINTAINERS b/MAINTAINERS index f8afd7d51e..785afff1a7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -738,6 +738,7 @@ F: drivers/reset/reset-ti-sci.c F: drivers/rtc/davinci.c F: drivers/serial/serial_omap.c F: drivers/soc/ti/ +F: drivers/sound/ti_sound.c F: drivers/sysreset/sysreset-ti-sci.c F: drivers/thermal/ti-bandgap.c F: drivers/timer/omap-timer.c diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig index 0948d8caab..be9f18b6c7 100644 --- a/drivers/sound/Kconfig +++ b/drivers/sound/Kconfig @@ -148,4 +148,14 @@ config SOUND_WM8994 audio data and I2C for codec control. At present it only works with the Samsung I2S driver.
+config I2S_TI + bool "Enable I2S support for AM62x SoCs" + depends on I2S + help + TI AM62x SoCs support an I2S interface for sending audio + data to an audio codec. This option enables support for this, + using one of the available audio codec drivers. Enabling this + option provides an implementation for sound_init() and + sound_play(). + endmenu diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 9b40c8012f..95aaa8521c 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -23,3 +23,4 @@ obj-$(CONFIG_SOUND_I8254) += i8254_beep.o obj-$(CONFIG_SOUND_RT5677) += rt5677.o obj-$(CONFIG_INTEL_BROADWELL) += broadwell_i2s.o broadwell_sound.o obj-$(CONFIG_SOUND_IVYBRIDGE) += ivybridge_sound.o +obj-$(CONFIG_I2S_TI) += ti_sound.o diff --git a/drivers/sound/ti_sound.c b/drivers/sound/ti_sound.c new file mode 100644 index 0000000000..a424a99b72 --- /dev/null +++ b/drivers/sound/ti_sound.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/ + * Scaria M Kochidanadu, s-kochidanadu@ti.com + * + * based on the uboot samsung sound driver, which is + * + * Copyright 2018 Google, LLC + * Written by Simon Glass sjg@chromium.org + */ + +#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <audio_codec.h> +#include <dm.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <asm/gpio.h> + +static int ti_sound_setup(struct udevice *dev) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + struct i2s_uc_priv *i2s_priv = dev_get_uclass_priv(uc_priv->i2s); + int ret; + + if (uc_priv->setup_done) + return -EALREADY; + + ret = audio_codec_set_params(uc_priv->codec, i2s_priv->id, + i2s_priv->samplingrate, + i2s_priv->samplingrate * i2s_priv->rfs, + i2s_priv->bitspersample, + i2s_priv->channels); + + if (ret) { + return ret; + printf("failed in set_params\n"); + } + uc_priv->setup_done = true; + + return 0; +} + +static int ti_sound_play(struct udevice *dev, void *data, uint data_size) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + + return i2s_tx_data(uc_priv->i2s, data, data_size); +} + +static int ti_sound_stop_play(struct udevice *dev) +{ + /* This function is necassary to satisfy the function calls + * in the Uboot command: sound play + */ + return 0; +} + +static int ti_sound_probe(struct udevice *dev) +{ + struct sound_uc_priv *uc_priv = dev_get_uclass_priv(dev); + struct ofnode_phandle_args args; + ofnode node; + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_AUDIO_CODEC, dev, + "ti,codec", + &uc_priv->codec); + if (ret) { + debug("Failed to probe audio codec\n"); + return ret; + } + + node = ofnode_find_subnode(dev_ofnode(dev), "simple-audio-card,cpu"); + if (!ofnode_valid(node)) { + debug("Failed to find /cpu subnode\n"); + return -EINVAL; + } + + ret = ofnode_parse_phandle_with_args(node, "sound-dai", + "#sound-dai-cells", 0, 0, &args); + if (ret) { + debug("Cannot find phandle: %d\n", ret); + return ret; + } + + ret = uclass_get_device_by_ofnode(UCLASS_I2S, args.node, &uc_priv->i2s); + if (ret) { + debug("Cannot find i2s: %d\n", ret); + return ret; + } + debug("Probed sound '%s' with codec '%s' and i2s '%s'\n", dev->name, + uc_priv->codec->name, uc_priv->i2s->name); + + return 0; +} + +static const struct sound_ops ti_sound_ops = { + .setup = ti_sound_setup, + .play = ti_sound_play, + .stop_play = ti_sound_stop_play, +}; + +static const struct udevice_id ti_sound_ids[] = { + { .compatible = "simple-audio-card" }, + { } +}; + +U_BOOT_DRIVER(ti_sound) = { + .name = "ti_sound", + .id = UCLASS_SOUND, + .of_match = ti_sound_ids, + .probe = ti_sound_probe, + .ops = &ti_sound_ops, +};

On Mon, Jul 08, 2024 at 01:22:02PM +0530, Scaria Kochidanadu wrote:
Add a UCLASS_SOUND driver for Texas Instruments SoCs which ties together the tlv320aic3106 audio codec and MCASP I2S controller. Enable audio playback functionality by taking a data pointer and data size as the sound data. The uboot sound play command takes time and frequency as input and creates the data for a beep sound with the given parameters, which is then passed to the sound play function.
[snip]
+++ b/drivers/sound/ti_sound.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/
- Scaria M Kochidanadu, s-kochidanadu@ti.com
- based on the uboot samsung sound driver, which is
- Copyright 2018 Google, LLC
- Written by Simon Glass sjg@chromium.org
- */
+#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <audio_codec.h> +#include <dm.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <asm/gpio.h>
Please audit this list. I really am confused by <env_internal.h>.
+static int ti_sound_stop_play(struct udevice *dev) +{
- /* This function is necassary to satisfy the function calls
* in the Uboot command: sound play
*/
- return 0;
/* * Like this... */
[snip]
+static const struct udevice_id ti_sound_ids[] = {
- { .compatible = "simple-audio-card" },
- { }
+};
Adding an actual simple-audio-card framework for U-Boot would be great. This driver implies (and is?) TI-centric, instead. So lets rework this to be split appropriately? Thanks.

On 08/07/24 21:16, Tom Rini wrote:
On Mon, Jul 08, 2024 at 01:22:02PM +0530, Scaria Kochidanadu wrote:
Add a UCLASS_SOUND driver for Texas Instruments SoCs which ties together the tlv320aic3106 audio codec and MCASP I2S controller. Enable audio playback functionality by taking a data pointer and data size as the sound data. The uboot sound play command takes time and frequency as input and creates the data for a beep sound with the given parameters, which is then passed to the sound play function.
[snip]
+++ b/drivers/sound/ti_sound.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/
- Scaria M Kochidanadu, s-kochidanadu@ti.com
- based on the uboot samsung sound driver, which is
- Copyright 2018 Google, LLC
- Written by Simon Glass sjg@chromium.org
- */
+#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <audio_codec.h> +#include <dm.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <asm/gpio.h>
Please audit this list. I really am confused by <env_internal.h>.
I will be removing this in v2 as it is not required.
+static int ti_sound_stop_play(struct udevice *dev) +{
- /* This function is necassary to satisfy the function calls
* in the Uboot command: sound play
*/
- return 0;
/*
- Like this...
*/
[snip]
+static const struct udevice_id ti_sound_ids[] = {
- { .compatible = "simple-audio-card" },
- { }
+};
Adding an actual simple-audio-card framework for U-Boot would be great. This driver implies (and is?) TI-centric, instead. So lets rework this to be split appropriately? Thanks.
I wanted to reuse the sound card DT node in the upstream kernel device-tree. The U-boot upstrem repo does not have the node as it is using an older version of DT. But if using the simple-audio-card node is not preferred, then can we create TI specific compatible and a separate DT node?

On Tue, Jul 09, 2024 at 04:56:45PM +0530, Scaria Kochidanadu wrote:
On 08/07/24 21:16, Tom Rini wrote:
On Mon, Jul 08, 2024 at 01:22:02PM +0530, Scaria Kochidanadu wrote:
Add a UCLASS_SOUND driver for Texas Instruments SoCs which ties together the tlv320aic3106 audio codec and MCASP I2S controller. Enable audio playback functionality by taking a data pointer and data size as the sound data. The uboot sound play command takes time and frequency as input and creates the data for a beep sound with the given parameters, which is then passed to the sound play function.
[snip]
+++ b/drivers/sound/ti_sound.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*
- (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/
- Scaria M Kochidanadu, s-kochidanadu@ti.com
- based on the uboot samsung sound driver, which is
- Copyright 2018 Google, LLC
- Written by Simon Glass sjg@chromium.org
- */
+#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <audio_codec.h> +#include <dm.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <asm/gpio.h>
Please audit this list. I really am confused by <env_internal.h>.
I will be removing this in v2 as it is not required.
+static int ti_sound_stop_play(struct udevice *dev) +{
- /* This function is necassary to satisfy the function calls
* in the Uboot command: sound play
*/
- return 0;
/*
- Like this...
*/
[snip]
+static const struct udevice_id ti_sound_ids[] = {
- { .compatible = "simple-audio-card" },
- { }
+};
Adding an actual simple-audio-card framework for U-Boot would be great. This driver implies (and is?) TI-centric, instead. So lets rework this to be split appropriately? Thanks.
I wanted to reuse the sound card DT node in the upstream kernel device-tree. The U-boot upstrem repo does not have the node as it is using an older version of DT.
When was the node upstreamed? Use the cherry-pick option to dts/update-dts-subtree.sh if needed.
But if using the simple-audio-card node is not preferred, then can we create TI specific compatible and a separate DT node?
No, I would like to see the start of simple-audio-card support be worked on, so that other platforms can leverage on top of this. Thanks.

On 16:56-20240709, Scaria Kochidanadu wrote:
I wanted to reuse the sound card DT node in the upstream kernel device-tree. The U-boot upstrem repo does not have the node as it is using an older version of DT.
Please DONOT do this. migrate to OF_UPSTREAM is it is not done (and as I recollect I have now spend close to an year getting u-boot TI dts to get to kernel.org dts and OF_UPSTREAM should now be enabled on master for am625), so what are you talking about? be specific what node is missing - contribute upstream kernel to enable the node and then do the changes.
But if using the simple-audio-card node is not preferred, then can we create TI specific compatible and a separate DT node?

This patch adds driver for TLV320AIC3106 audio Codec. Initialize and start the codec[1] with appropriate settings and hardware parameters according to the audio data and the sound card defined in the devicetree. The codec audio data bus is programmable for I2S, DSP or TDM modes, and it also has a programmable PLL for clock generation. Below default settings are used as u-boot doesn't support file playback: Codec being the clock and frame master, using the I2S audio format, sampling rate and width of audio data in one slot.
The data sheet for the codec can be obtained here for further details about its settings and working-
Link: https://www.ti.com/product/TLV320AIC3106 [1]
The driver is based on the kernel codec driver[2]. Link: https://gitlab.com/linux-kernel/linux-next/-/blob/master/sound/soc/codecs/tl... [2]
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- MAINTAINERS | 2 + drivers/sound/Kconfig | 8 + drivers/sound/Makefile | 1 + drivers/sound/tlv320aic3106.c | 758 ++++++++++++++++++++++++++++++++++ drivers/sound/tlv320aic3106.h | 336 +++++++++++++++ 5 files changed, 1105 insertions(+) create mode 100644 drivers/sound/tlv320aic3106.c create mode 100644 drivers/sound/tlv320aic3106.h
diff --git a/MAINTAINERS b/MAINTAINERS index 785afff1a7..5d7c260bfc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -739,6 +739,8 @@ F: drivers/rtc/davinci.c F: drivers/serial/serial_omap.c F: drivers/soc/ti/ F: drivers/sound/ti_sound.c +F: drivers/sound/tlv320aic3106.c +F: drivers/sound/tlv320aic3106.h F: drivers/sysreset/sysreset-ti-sci.c F: drivers/thermal/ti-bandgap.c F: drivers/timer/omap-timer.c diff --git a/drivers/sound/Kconfig b/drivers/sound/Kconfig index be9f18b6c7..3f124cfb2b 100644 --- a/drivers/sound/Kconfig +++ b/drivers/sound/Kconfig @@ -158,4 +158,12 @@ config I2S_TI option provides an implementation for sound_init() and sound_play().
+config SOUND_TLV320AIC3106 + bool "Support TLV320AIC3106 audio codec" + depends on I2S_TI + help + Enable the TLV320AIC3106 audio codec. This is connected via I2S for + audio data and I2C for codec control. At present it only works + with the MCASP driver. + endmenu diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 95aaa8521c..1e9fbd1000 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -24,3 +24,4 @@ obj-$(CONFIG_SOUND_RT5677) += rt5677.o obj-$(CONFIG_INTEL_BROADWELL) += broadwell_i2s.o broadwell_sound.o obj-$(CONFIG_SOUND_IVYBRIDGE) += ivybridge_sound.o obj-$(CONFIG_I2S_TI) += ti_sound.o +obj-$(CONFIG_SOUND_TLV320AIC3106) += tlv320aic3106.o diff --git a/drivers/sound/tlv320aic3106.c b/drivers/sound/tlv320aic3106.c new file mode 100644 index 0000000000..b80bdddd70 --- /dev/null +++ b/drivers/sound/tlv320aic3106.c @@ -0,0 +1,758 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/ + * Scaria M Kochidanadu, s-kochidanadu@ti.com + * + * based on the linux tlv320aic3x codec driver, which is + * + * Author: Vladimir Barinov, vbarinov@embeddedalley.com + * Copyright: (C) 2007 MontaVista Software, Inc., source@mvista.com + */ + +#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <audio_codec.h> +#include <dm.h> +#include <div64.h> +#include <fdtdec.h> +#include <i2c.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <linux/delay.h> +#include "tlv320aic3106.h" + +enum aic3x_micbias_voltage { + AIC3X_MICBIAS_OFF = 0, + AIC3X_MICBIAS_2_0V = 1, + AIC3X_MICBIAS_2_5V = 2, + AIC3X_MICBIAS_AVDDV = 3, +}; + +enum snd_soc_bias_level { + SND_SOC_BIAS_OFF = 0, + SND_SOC_BIAS_STANDBY = 1, + SND_SOC_BIAS_PREPARE = 2, + SND_SOC_BIAS_ON = 3, +}; + +struct reg_values { + unsigned int reg; + unsigned int def; +}; + +static const struct reg_values aic3x_default[] = { + { 0, 0x00 }, { 1, 0x00 }, { 2, 0x00 }, { 3, 0x10 }, + { 4, 0x04 }, { 5, 0x00 }, { 6, 0x00 }, { 7, 0x00 }, + { 8, 0x00 }, { 9, 0x00 }, { 10, 0x00 }, { 11, 0x01 }, + { 12, 0x00 }, { 13, 0x00 }, { 14, 0x00 }, { 15, 0x80 }, + { 16, 0x80 }, { 17, 0xff }, { 18, 0xff }, { 19, 0x78 }, + { 20, 0x78 }, { 21, 0x78 }, { 22, 0x78 }, { 23, 0x78 }, + { 24, 0x78 }, { 25, 0x00 }, { 26, 0x00 }, { 27, 0xfe }, + { 28, 0x00 }, { 29, 0x00 }, { 30, 0xfe }, { 31, 0x00 }, + { 32, 0x18 }, { 33, 0x18 }, { 34, 0x00 }, { 35, 0x00 }, + { 36, 0x00 }, { 37, 0x00 }, { 38, 0x00 }, { 39, 0x00 }, + { 40, 0x00 }, { 41, 0x00 }, { 42, 0x00 }, { 43, 0x80 }, + { 44, 0x80 }, { 45, 0x00 }, { 46, 0x00 }, { 47, 0x00 }, + { 48, 0x00 }, { 49, 0x00 }, { 50, 0x00 }, { 51, 0x04 }, + { 52, 0x00 }, { 53, 0x00 }, { 54, 0x00 }, { 55, 0x00 }, + { 56, 0x00 }, { 57, 0x00 }, { 58, 0x04 }, { 59, 0x00 }, + { 60, 0x00 }, { 61, 0x00 }, { 62, 0x00 }, { 63, 0x00 }, + { 64, 0x00 }, { 65, 0x04 }, { 66, 0x00 }, { 67, 0x00 }, + { 68, 0x00 }, { 69, 0x00 }, { 70, 0x00 }, { 71, 0x00 }, + { 72, 0x04 }, { 73, 0x00 }, { 74, 0x00 }, { 75, 0x00 }, + { 76, 0x00 }, { 77, 0x00 }, { 78, 0x00 }, { 79, 0x00 }, + { 80, 0x00 }, { 81, 0x00 }, { 82, 0x00 }, { 83, 0x00 }, + { 84, 0x00 }, { 85, 0x00 }, { 86, 0x00 }, { 87, 0x00 }, + { 88, 0x00 }, { 89, 0x00 }, { 90, 0x00 }, { 91, 0x00 }, + { 92, 0x00 }, { 93, 0x00 }, { 94, 0x00 }, { 95, 0x00 }, + { 96, 0x00 }, { 97, 0x00 }, { 98, 0x00 }, { 99, 0x00 }, + { 100, 0x00 }, { 101, 0x00 }, { 102, 0x02 }, { 103, 0x00 }, + { 104, 0x00 }, { 105, 0x00 }, { 106, 0x00 }, { 107, 0x00 }, + { 108, 0x00 }, { 109, 0x00 }, +}; + +/* codec private data */ +struct tlv320aic3106_priv { + struct udevice *dev; + enum aic3x_micbias_voltage micbias_vg; + unsigned int sysclk; + unsigned int dai_fmt; + unsigned int tdm_delay; + unsigned int slot_width; + int master; + int power; + bool bias_level; + /*output common voltage*/ + u8 ocmv; +}; + +static int aic3106_i2c_write(struct tlv320aic3106_priv *priv, unsigned int reg, unsigned char data) +{ + unsigned char val[1]; + + val[0] = data; + debug("Write Addr : 0x%04X, Data : 0x%04X\n", reg, data); + + return dm_i2c_write(priv->dev, reg, val, 1); +} + +static unsigned int aic3106_i2c_read(struct tlv320aic3106_priv *priv, unsigned int reg, + unsigned char *data) +{ + unsigned char val[1]; + int ret; + + ret = dm_i2c_read(priv->dev, reg, val, 1); + if (ret != 0) { + debug("%s: Error while reading register %#04x\n", + __func__, reg); + return -1; + } + *data = val[0]; + + return 0; +} + +static int aic3106_i2c_update_bits(struct tlv320aic3106_priv *priv, unsigned int reg, + unsigned char mask, unsigned char data) +{ + unsigned char reg_data; + + int ret = aic3106_i2c_read(priv, reg - 1, ®_data); + + if (ret < 0) { + debug("failed to read ID register\n"); + return ret; + } + + unsigned char final_data = (reg_data & (~mask)) | data; + + ret = aic3106_i2c_write(priv, reg, final_data); + if (ret < 0) { + debug("failed to write ID register\n"); + return ret; + } + return 0; +} + +static int aic3106_set_dai_sysclk(struct tlv320aic3106_priv *priv, int clk_id, unsigned int freq) +{ + int ret; + + ret = aic3106_i2c_update_bits(priv, AIC3X_CLKGEN_CTRL_REG, PLLCLK_IN_MASK, + clk_id << PLLCLK_IN_SHIFT); + ret |= aic3106_i2c_update_bits(priv, AIC3X_CLKGEN_CTRL_REG, CLKDIV_IN_MASK, + clk_id << CLKDIV_IN_SHIFT); + + priv->sysclk = freq; + + if (ret < 0) { + debug("%s sysclk failed", __func__); + return ret; + } + return 0; +} + +static int aic3106_set_dai_fmt(struct tlv320aic3106_priv *priv, unsigned int fmt) +{ + int ret; + u8 iface_areg, iface_breg; + + ret = aic3106_i2c_read(priv, AIC3X_ASD_INTF_CTRLA - 1, &iface_areg); + if (ret < 0) + debug("%s error reading reg\n", __func__); + iface_areg &= 0x3f; + + ret = aic3106_i2c_read(priv, AIC3X_ASD_INTF_CTRLB - 1, &iface_breg); + if (ret < 0) + debug("%s error reading reg\n", __func__); + iface_breg &= 0x3f; + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_CBP_CFP: + priv->master = 1; + iface_areg |= BIT_CLK_MASTER | WORD_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBC_CFC: + priv->master = 0; + iface_areg &= ~(BIT_CLK_MASTER | WORD_CLK_MASTER); + break; + case SND_SOC_DAIFMT_CBP_CFC: + priv->master = 1; + iface_areg |= BIT_CLK_MASTER; + iface_areg &= ~WORD_CLK_MASTER; + break; + case SND_SOC_DAIFMT_CBC_CFP: + priv->master = 1; + iface_areg |= WORD_CLK_MASTER; + iface_areg &= ~BIT_CLK_MASTER; + break; + default: + return -EINVAL; + } + /* + * match both interface format and signal polarities since they + * are fixed + */ + switch (fmt & (SND_SOC_DAIFMT_FORMAT_MASK | + SND_SOC_DAIFMT_INV_MASK)) { + case (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF_UBT): + break; + case (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF): + case (SND_SOC_DAIFMT_DSP_B | SND_SOC_DAIFMT_IB_NF): + iface_breg |= (0x01 << 6); + break; + case (SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF_UBT): + iface_breg |= (0x02 << 6); + break; + case (SND_SOC_DAIFMT_LEFT_J | SND_SOC_DAIFMT_NB_NF_UBT): + iface_breg |= (0x03 << 6); + break; + default: + return -EINVAL; + } + + priv->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + ret = aic3106_i2c_write(priv, AIC3X_ASD_INTF_CTRLA, iface_areg); + ret |= aic3106_i2c_write(priv, AIC3X_ASD_INTF_CTRLB, iface_breg); + + if (ret < 0) + return ret; + return 0; +} + +static int aic3106_hw_params(struct tlv320aic3106_priv *priv, int rate) +{ + int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; + u8 data, j, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; + + u16 d, pll_d = 1; + int clk; + int width = priv->slot_width; + + if (!width) + width = 16; + + int ret = aic3106_i2c_read(priv, AIC3X_ASD_INTF_CTRLB - 1, &data); + + if (ret < 0) + return ret; + + data &= (~(0x3 << 4)); + + switch (width) { + case 16: + break; + case 20: + data |= (0x01 << 4); + break; + case 24: + data |= (0x02 << 4); + break; + case 32: + data |= (0x03 << 4); + break; + } + + aic3106_i2c_write(priv, AIC3X_ASD_INTF_CTRLB, data); + + fsref = (rate % 11025 == 0) ? 44100 : 48000; + + /* Try to find a value for Q which allows us to bypass the PLL and + * generate CODEC_CLK directly. + */ + for (pll_q = 2; pll_q < 18; pll_q++) + if (priv->sysclk / (128 * pll_q) == fsref) { + bypass_pll = 1; + break; + } + + if (bypass_pll) { + pll_q &= 0xf; + aic3106_i2c_write(priv, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); + aic3106_i2c_write(priv, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); + /* disable PLL if it is bypassed */ + aic3106_i2c_update_bits(priv, AIC3X_PLL_PROGA_REG, PLL_ENABLE, 0); + + } else { + aic3106_i2c_write(priv, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + /* enable PLL when it is used */ + aic3106_i2c_update_bits(priv, AIC3X_PLL_PROGA_REG, + PLL_ENABLE, PLL_ENABLE); + } + + /* Route Left DAC to left channel input and + * right DAC to right channel input + */ + data = (LDAC2LCH | RDAC2RCH); + data |= (fsref == 44100) ? FSREF_44100 : FSREF_48000; + + if (rate >= 64000) + data |= DUAL_RATE_MODE; + aic3106_i2c_write(priv, AIC3X_CODEC_DATAPATH_REG, data); + + /* codec sample rate select */ + data = (fsref * 20) / rate; + if (rate < 64000) + data /= 2; + data /= 5; + data -= 2; + data |= (data << 4); + aic3106_i2c_write(priv, AIC3X_SAMPLE_RATE_SEL_REG, data); + + if (bypass_pll) + return 0; + + /* Use PLL, compute appropriate setup for j, d, r and p, the closest + * one wins the game. Try with d==0 first, next with d!=0. + * Constraints for j are according to the datasheet. + * The sysclk is divided by 1000 to prevent integer overflows. + */ + + codec_clk = (2048 * fsref) / (priv->sysclk / 1000); + + for (r = 1; r <= 16; r++) + for (p = 1; p <= 8; p++) { + for (j = 4; j <= 55; j++) { + /* This is actually 1000*((j+(d/10000))*r)/p + * The term had to be converted to get + * rid of the division by 10000; d = 0 here + */ + int tmp_clk = (1000 * j * r) / p; + + /* Check whether this values get closer than + * the best ones we had before + */ + if (abs(codec_clk - tmp_clk) < + abs(codec_clk - last_clk)) { + pll_j = j; pll_d = 0; + pll_r = r; pll_p = p; + last_clk = tmp_clk; + } + + /* Early exit for exact matches */ + if (tmp_clk == codec_clk) + goto found; + } + } + + /* try with d != 0 */ + for (p = 1; p <= 8; p++) { + j = codec_clk * p / 1000; + + if (j < 4 || j > 11) + continue; + + /* do not use codec_clk here since we'd loose precision */ + d = ((2048 * p * fsref) - j * priv->sysclk) + * 100 / (priv->sysclk / 100); + + clk = (10000 * j + d) / (10 * p); + + /* check whether this values get closer than the best + * ones we had before + */ + if (abs(codec_clk - clk) < abs(codec_clk - last_clk)) { + pll_j = j; pll_d = d; pll_r = 1; pll_p = p; + last_clk = clk; + } + + /* Early exit for exact matches */ + if (clk == codec_clk) + goto found; + } + + if (last_clk == 0) { + printf("%s(): unable to setup PLL\n", __func__); + return -EINVAL; + } + +found: + aic3106_i2c_update_bits(priv, AIC3X_PLL_PROGA_REG, PLLP_MASK, pll_p); + aic3106_i2c_write(priv, AIC3X_OVRF_STATUS_AND_PLLR_REG, + pll_r << PLLR_SHIFT); + aic3106_i2c_write(priv, AIC3X_PLL_PROGB_REG, pll_j << PLLJ_SHIFT); + aic3106_i2c_write(priv, AIC3X_PLL_PROGC_REG, + (pll_d >> 6) << PLLD_MSB_SHIFT); + aic3106_i2c_write(priv, AIC3X_PLL_PROGD_REG, + (pll_d & 0x3F) << PLLD_LSB_SHIFT); + + return 0; +} + +static int aic3106_prepare(struct tlv320aic3106_priv *priv) +{ + int delay = 0; + int width = priv->slot_width; + + if (!width) + width = 16; + + /* TDM slot selection only valid in DSP_A/_B mode */ + if (priv->dai_fmt == SND_SOC_DAIFMT_DSP_A) + delay += (priv->tdm_delay * width + 1); + else if (priv->dai_fmt == SND_SOC_DAIFMT_DSP_B) + delay += priv->tdm_delay * width; + + /* Configure data delay */ + int ret = aic3106_i2c_write(priv, AIC3X_ASD_INTF_CTRLC, delay); + + if (ret < 0) + return ret; + + return 0; +} + +static int aic3106_mute(struct tlv320aic3106_priv *priv, int mute) +{ + u8 ldac_reg, rdac_reg; + int ret; + + ret = aic3106_i2c_read(priv, LDAC_VOL - 1, &ldac_reg); + if (ret < 0) + debug("%s error in reading reg\n", __func__); + ret = aic3106_i2c_read(priv, RDAC_VOL - 1, &rdac_reg); + if (ret < 0) + debug("%s error in reading reg\n", __func__); + + ldac_reg &= ~MUTE_ON; + rdac_reg &= ~MUTE_ON; + + if (mute) { + aic3106_i2c_write(priv, LDAC_VOL, ldac_reg | MUTE_ON); + aic3106_i2c_write(priv, RDAC_VOL, rdac_reg | MUTE_ON); + } else { + aic3106_i2c_write(priv, LDAC_VOL, ldac_reg); + aic3106_i2c_write(priv, RDAC_VOL, rdac_reg); + } + + return 0; +} + +static int aic3106_set_power(struct tlv320aic3106_priv *priv, bool power) +{ + u8 pll_c, pll_d; + int ret; + + if (power) { + priv->power = 1; + + /* Rewrite paired PLL D registers in case cached sync skipped + * writing one of them and thus caused other one also not + * being written + */ + ret = aic3106_i2c_read(priv, AIC3X_PLL_PROGC_REG - 1, &pll_c); + ret |= aic3106_i2c_read(priv, AIC3X_PLL_PROGD_REG - 1, &pll_d); + if (pll_c == aic3x_default[AIC3X_PLL_PROGC_REG].def || + pll_d == aic3x_default[AIC3X_PLL_PROGD_REG].def) { + ret |= aic3106_i2c_write(priv, AIC3X_PLL_PROGC_REG, pll_c); + ret |= aic3106_i2c_write(priv, AIC3X_PLL_PROGD_REG, pll_d); + } + + if (ret < 0) + return ret; + + /* + * Delay is needed to reduce pop-noise after syncing back the + * registers + */ + udelay(50 * 1000); + + } else { + /* + * Do soft reset to this codec instance in order to clear + * possible VDD leakage currents in case the supply regulators + * remain on + */ + ret = aic3106_i2c_write(priv, AIC3X_RESET, SOFT_RESET); + if (ret < 0) + return ret; + + priv->power = 0; + } + return 0; +} + +static int aic3106_set_bias_level(struct tlv320aic3106_priv *priv, bool level) +{ + int ret; + + if (level && !priv->bias_level && priv->master) { + ret = aic3106_set_power(priv, 1); + /*enable PLL */ + ret |= aic3106_i2c_update_bits(priv, AIC3X_PLL_PROGA_REG, PLL_ENABLE, PLL_ENABLE); + if (ret < 0) + return ret; + + priv->bias_level = true; + } + + if (!level && priv->bias_level && priv->master) { + /*disable PLL */ + ret = aic3106_i2c_update_bits(priv, AIC3X_PLL_PROGA_REG, PLL_ENABLE, 0); + priv->bias_level = false; + ret |= aic3106_set_power(priv, 0); + if (ret < 0) + return ret; + } + + return 0; +} + +static void aic3106_mono_init(struct tlv320aic3106_priv *priv) +{ + aic3106_i2c_write(priv, DACL1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + aic3106_i2c_write(priv, DACR1_2_MONOLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* unmute all outputs */ + aic3106_i2c_update_bits(priv, MONOLOPM_CTRL, UNMUTE, UNMUTE); + + /* PGA to Mono Line Out default volume, disconnect from Output Mixer */ + aic3106_i2c_write(priv, PGAL_2_MONOLOPM_VOL, DEFAULT_VOL); + + aic3106_i2c_write(priv, PGAR_2_MONOLOPM_VOL, DEFAULT_VOL); + + /* Line2 to Mono Out default volume, disconnect from Output Mixer */ + aic3106_i2c_write(priv, LINE2L_2_MONOLOPM_VOL, DEFAULT_VOL); + + aic3106_i2c_write(priv, LINE2R_2_MONOLOPM_VOL, DEFAULT_VOL); +} + +static int tlv320aic3106_init(struct tlv320aic3106_priv *priv) +{ + int ret = aic3106_i2c_write(priv, AIC3X_PAGE_SELECT, PAGE0_SELECT); + + ret |= aic3106_i2c_write(priv, AIC3X_RESET, SOFT_RESET); + + /*Powering up LDAC and RDAC*/ + ret |= aic3106_i2c_write(priv, 37, 0xc0); + + /* DAC default volume and mute */ + /* Increasing the Volume since default volume is low */ + ret |= aic3106_i2c_write(priv, LDAC_VOL, 0x0d | MUTE_ON); + + ret |= aic3106_i2c_write(priv, RDAC_VOL, 0x0d | MUTE_ON); + + /* DAC to HP default volume and route to Output mixer */ + ret |= aic3106_i2c_write(priv, DACL1_2_HPLOUT_VOL, DEFAULT_VOL | ROUTE_ON); + + ret |= aic3106_i2c_write(priv, DACR1_2_HPROUT_VOL, DEFAULT_VOL | ROUTE_ON); + + ret |= aic3106_i2c_write(priv, DACL1_2_HPLCOM_VOL, DEFAULT_VOL | ROUTE_ON); + + ret |= aic3106_i2c_write(priv, DACR1_2_HPRCOM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* DAC to Line Out default volume and route to Output mixer */ + ret |= aic3106_i2c_write(priv, DACL1_2_LLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + ret |= aic3106_i2c_write(priv, DACR1_2_RLOPM_VOL, DEFAULT_VOL | ROUTE_ON); + + /* Powering on the outputs*/ + ret |= aic3106_i2c_update_bits(priv, HPLOUT_CTRL, HPLOUT_PWR_ON, HPLOUT_PWR_ON); + + ret |= aic3106_i2c_update_bits(priv, HPLCOM_CTRL, HPLCOM_PWR_ON, HPLCOM_PWR_ON); + + ret |= aic3106_i2c_update_bits(priv, HPROUT_CTRL, HPROUT_PWR_ON, HPROUT_PWR_ON); + + ret |= aic3106_i2c_update_bits(priv, HPRCOM_CTRL, HPRCOM_PWR_ON, HPRCOM_PWR_ON); + + /* unmute all outputs */ + ret |= aic3106_i2c_update_bits(priv, LLOPM_CTRL, UNMUTE, UNMUTE); + + ret |= aic3106_i2c_update_bits(priv, RLOPM_CTRL, UNMUTE, UNMUTE); + + ret |= aic3106_i2c_update_bits(priv, HPLOUT_CTRL, UNMUTE, UNMUTE); + + ret |= aic3106_i2c_update_bits(priv, HPROUT_CTRL, UNMUTE, UNMUTE); + + ret |= aic3106_i2c_update_bits(priv, HPLCOM_CTRL, UNMUTE, UNMUTE); + + ret |= aic3106_i2c_update_bits(priv, HPRCOM_CTRL, UNMUTE, UNMUTE); + + /* ADC default volume and unmute */ + ret |= aic3106_i2c_write(priv, LADC_VOL, 0x6b); + + ret |= aic3106_i2c_write(priv, RADC_VOL, 0x6b); + + /* Some changes*/ + + ret |= aic3106_i2c_write(priv, MIC3LR_2_LADC_CTRL, 0xf0); + + ret |= aic3106_i2c_write(priv, MIC3LR_2_RADC_CTRL, 0xf0); + + /* By default route Line1 to ADC PGA mixer */ + ret |= aic3106_i2c_write(priv, LINE1L_2_LADC_CTRL, 0x0); + + ret |= aic3106_i2c_write(priv, LINE1R_2_RADC_CTRL, 0x0); + + /* PGA to HP Bypass default volume, disconnect from Output Mixer */ + ret |= aic3106_i2c_write(priv, PGAL_2_HPLOUT_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, PGAR_2_HPROUT_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, PGAL_2_HPLCOM_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, PGAR_2_HPRCOM_VOL, DEFAULT_VOL); + /* PGA to Line Out default volume, disconnect from Output Mixer */ + + ret |= aic3106_i2c_write(priv, PGAL_2_LLOPM_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, PGAR_2_RLOPM_VOL, DEFAULT_VOL); + + /* Line2 to HP Bypass default volume, disconnect from Output Mixer */ + ret |= aic3106_i2c_write(priv, LINE2L_2_HPLOUT_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, LINE2R_2_HPROUT_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, LINE2L_2_HPLCOM_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, LINE2R_2_HPRCOM_VOL, DEFAULT_VOL); + + /* Line2 Line Out default volume, disconnect from Output Mixer */ + ret |= aic3106_i2c_write(priv, LINE2L_2_LLOPM_VOL, DEFAULT_VOL); + + ret |= aic3106_i2c_write(priv, LINE2R_2_RLOPM_VOL, DEFAULT_VOL); + + aic3106_mono_init(priv); + + ret |= aic3106_i2c_update_bits(priv, HPOUT_SC, HPOUT_SC_OCMV_MASK, + priv->ocmv << HPOUT_SC_OCMV_SHIFT); + + if (ret) { + debug("error in init()\n"); + return ret; + } + + return 0; +} + +static int tlv320aic3106_set_params(struct udevice *dev, int interface, int rate, + int mclk_freq, int bits_per_sample, uint channels) +{ + struct tlv320aic3106_priv *priv = dev_get_priv(dev); + + int ret; + + ret = aic3106_set_dai_sysclk(priv, interface, mclk_freq); + if (ret) { + debug(" %s failure in setting sysclk\n", __func__); + return ret; + } + + int fmt = SND_SOC_DAIFMT_CBP_CFP | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + + ret = aic3106_set_dai_fmt(priv, fmt); + if (ret) { + debug(" %s failure in setting dai format\n", __func__); + return ret; + } + + ret = aic3106_hw_params(priv, rate); + if (ret) { + debug(" %s failure in setting hw params\n", __func__); + return ret; + } + + ret = aic3106_prepare(priv); + if (ret) { + debug(" %s failure\n", __func__); + return ret; + } + + ret = aic3106_set_bias_level(priv, 1); + if (ret) { + debug(" %s failure\n", __func__); + return ret; + } + + ret = aic3106_mute(priv, 0); + if (ret) { + debug(" %s failure\n", __func__); + return ret; + } + + return 0; +} + +static int tlv320aic3106_probe(struct udevice *dev) +{ + struct tlv320aic3106_priv *priv = dev_get_priv(dev); + int ret, value; + + priv->dev = dev; + + /* Getting Micbias volage and OCMV value from DT */ + if (!dev_read_u32u(dev, "ai3x-micbias-vg", &value)) { + switch (value) { + case 1: + priv->micbias_vg = AIC3X_MICBIAS_2_0V; + break; + case 2: + priv->micbias_vg = AIC3X_MICBIAS_2_5V; + break; + case 3: + priv->micbias_vg = AIC3X_MICBIAS_AVDDV; + break; + default: + priv->micbias_vg = AIC3X_MICBIAS_OFF; + debug("Unsuitable MicBias voltage found in DT\n"); + } + } else { + priv->micbias_vg = AIC3X_MICBIAS_OFF; + } + + if (!dev_read_u32u(dev, "ai3x-ocmv", &value)) { + /* OCMV setting is forced by DT */ + if (value <= 3) + priv->ocmv = value; + } + + ret = tlv320aic3106_init(priv); + if (ret < 0) { + printf("error in init()\n"); + return ret; + } + + switch (priv->micbias_vg) { + case AIC3X_MICBIAS_2_0V: + case AIC3X_MICBIAS_2_5V: + case AIC3X_MICBIAS_AVDDV: + aic3106_i2c_update_bits(priv, MICBIAS_CTRL, + MICBIAS_LEVEL_MASK, + (priv->micbias_vg) << MICBIAS_LEVEL_SHIFT); + break; + case AIC3X_MICBIAS_OFF: + /* + * noting to do. target won't enter here. This is just to avoid + * compile time warning "warning: enumeration value + * 'AIC3X_MICBIAS_OFF' not handled in switch" + */ + break; + } + + return 0; +} + +static const struct audio_codec_ops tlv320aic3106_ops = { + .set_params = tlv320aic3106_set_params, +}; + +static const struct udevice_id tlv320aic3106_ids[] = { + { .compatible = "ti,tlv320aic3106" }, + { } +}; + +U_BOOT_DRIVER(tlv320aic3106) = { + .name = "tlv320aic3106", + .id = UCLASS_AUDIO_CODEC, + .of_match = tlv320aic3106_ids, + .probe = tlv320aic3106_probe, + .ops = &tlv320aic3106_ops, + .priv_auto = sizeof(struct tlv320aic3106_priv), +}; diff --git a/drivers/sound/tlv320aic3106.h b/drivers/sound/tlv320aic3106.h new file mode 100644 index 0000000000..55dc497a7e --- /dev/null +++ b/drivers/sound/tlv320aic3106.h @@ -0,0 +1,336 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA SoC TLV320AIC3X codec driver + * + * Author: Vladimir Barinov, vbarinov@embeddedalley.com + * Copyright: (C) 2007 MontaVista Software, Inc., source@mvista.com + */ + +#ifndef _TLV320AIC3106_H +#define _TLV320AIC3106_H + +#define AIC3X_MODEL_3X 0 +#define AIC3X_MODEL_33 1 +#define AIC3X_MODEL_3007 2 +#define AIC3X_MODEL_3104 3 +#define AIC3X_MODEL_3106 4 + +/* AIC3X register space */ +#define AIC3X_CACHEREGNUM 110 + +/* Page select register */ +#define AIC3X_PAGE_SELECT 0 +/* Software reset register */ +#define AIC3X_RESET 1 +/* Codec Sample rate select register */ +#define AIC3X_SAMPLE_RATE_SEL_REG 2 +/* PLL progrramming register A */ +#define AIC3X_PLL_PROGA_REG 3 +/* PLL progrramming register B */ +#define AIC3X_PLL_PROGB_REG 4 +/* PLL progrramming register C */ +#define AIC3X_PLL_PROGC_REG 5 +/* PLL progrramming register D */ +#define AIC3X_PLL_PROGD_REG 6 +/* Codec datapath setup register */ +#define AIC3X_CODEC_DATAPATH_REG 7 +/* Audio serial data interface control register A */ +#define AIC3X_ASD_INTF_CTRLA 8 +/* Audio serial data interface control register B */ +#define AIC3X_ASD_INTF_CTRLB 9 +/* Audio serial data interface control register C */ +#define AIC3X_ASD_INTF_CTRLC 10 +/* Audio overflow status and PLL R value programming register */ +#define AIC3X_OVRF_STATUS_AND_PLLR_REG 11 +/* Audio codec digital filter control register */ +#define AIC3X_CODEC_DFILT_CTRL 12 +/* Headset/button press detection register */ +#define AIC3X_HEADSET_DETECT_CTRL_A 13 +#define AIC3X_HEADSET_DETECT_CTRL_B 14 +/* ADC PGA Gain control registers */ +#define LADC_VOL 15 +#define RADC_VOL 16 +/* MIC3 control registers */ +#define MIC3LR_2_LADC_CTRL 17 +#define MIC3LR_2_RADC_CTRL 18 +/* Line1 Input control registers */ +#define LINE1L_2_LADC_CTRL 19 +#define LINE1R_2_LADC_CTRL 21 +#define LINE1R_2_RADC_CTRL 22 +#define LINE1L_2_RADC_CTRL 24 +/* Line2 Input control registers */ +#define LINE2L_2_LADC_CTRL 20 +#define LINE2R_2_RADC_CTRL 23 +/* MICBIAS Control Register */ +#define MICBIAS_CTRL 25 + +/* AGC Control Registers A, B, C */ +#define LAGC_CTRL_A 26 +#define LAGC_CTRL_B 27 +#define LAGC_CTRL_C 28 +#define RAGC_CTRL_A 29 +#define RAGC_CTRL_B 30 +#define RAGC_CTRL_C 31 + +/* DAC Power and Left High Power Output control registers */ +#define DAC_PWR 37 +#define HPLCOM_CFG 37 +/* Right High Power Output control registers */ +#define HPRCOM_CFG 38 +/* High Power Output Stage Control Register */ +#define HPOUT_SC 40 +/* DAC Output Switching control registers */ +#define DAC_LINE_MUX 41 +/* High Power Output Driver Pop Reduction registers */ +#define HPOUT_POP_REDUCTION 42 +/* DAC Digital control registers */ +#define LDAC_VOL 43 +#define RDAC_VOL 44 +/* Left High Power Output control registers */ +#define LINE2L_2_HPLOUT_VOL 45 +#define PGAL_2_HPLOUT_VOL 46 +#define DACL1_2_HPLOUT_VOL 47 +#define LINE2R_2_HPLOUT_VOL 48 +#define PGAR_2_HPLOUT_VOL 49 +#define DACR1_2_HPLOUT_VOL 50 +#define HPLOUT_CTRL 51 +/* Left High Power COM control registers */ +#define LINE2L_2_HPLCOM_VOL 52 +#define PGAL_2_HPLCOM_VOL 53 +#define DACL1_2_HPLCOM_VOL 54 +#define LINE2R_2_HPLCOM_VOL 55 +#define PGAR_2_HPLCOM_VOL 56 +#define DACR1_2_HPLCOM_VOL 57 +#define HPLCOM_CTRL 58 +/* Right High Power Output control registers */ +#define LINE2L_2_HPROUT_VOL 59 +#define PGAL_2_HPROUT_VOL 60 +#define DACL1_2_HPROUT_VOL 61 +#define LINE2R_2_HPROUT_VOL 62 +#define PGAR_2_HPROUT_VOL 63 +#define DACR1_2_HPROUT_VOL 64 +#define HPROUT_CTRL 65 +/* Right High Power COM control registers */ +#define LINE2L_2_HPRCOM_VOL 66 +#define PGAL_2_HPRCOM_VOL 67 +#define DACL1_2_HPRCOM_VOL 68 +#define LINE2R_2_HPRCOM_VOL 69 +#define PGAR_2_HPRCOM_VOL 70 +#define DACR1_2_HPRCOM_VOL 71 +#define HPRCOM_CTRL 72 +/* Mono Line Output Plus/Minus control registers */ +#define LINE2L_2_MONOLOPM_VOL 73 +#define PGAL_2_MONOLOPM_VOL 74 +#define DACL1_2_MONOLOPM_VOL 75 +#define LINE2R_2_MONOLOPM_VOL 76 +#define PGAR_2_MONOLOPM_VOL 77 +#define DACR1_2_MONOLOPM_VOL 78 +#define MONOLOPM_CTRL 79 +/* Class-D speaker driver on tlv320aic3007 */ +#define CLASSD_CTRL 73 +/* Left Line Output Plus/Minus control registers */ +#define LINE2L_2_LLOPM_VOL 80 +#define PGAL_2_LLOPM_VOL 81 +#define DACL1_2_LLOPM_VOL 82 +#define LINE2R_2_LLOPM_VOL 83 +#define PGAR_2_LLOPM_VOL 84 +#define DACR1_2_LLOPM_VOL 85 +#define LLOPM_CTRL 86 +/* Right Line Output Plus/Minus control registers */ +#define LINE2L_2_RLOPM_VOL 87 +#define PGAL_2_RLOPM_VOL 88 +#define DACL1_2_RLOPM_VOL 89 +#define LINE2R_2_RLOPM_VOL 90 +#define PGAR_2_RLOPM_VOL 91 +#define DACR1_2_RLOPM_VOL 92 +#define RLOPM_CTRL 93 +/* GPIO/IRQ registers */ +#define AIC3X_STICKY_IRQ_FLAGS_REG 96 +#define AIC3X_RT_IRQ_FLAGS_REG 97 +#define AIC3X_GPIO1_REG 98 +#define AIC3X_GPIO2_REG 99 +#define AIC3X_GPIOA_REG 100 +#define AIC3X_GPIOB_REG 101 +/* Clock generation control register */ +#define AIC3X_CLKGEN_CTRL_REG 102 +/* New AGC registers */ +#define LAGCN_ATTACK 103 +#define LAGCN_DECAY 104 +#define RAGCN_ATTACK 105 +#define RAGCN_DECAY 106 +/* New Programmable ADC Digital Path and I2C Bus Condition Register */ +#define NEW_ADC_DIGITALPATH 107 +/* Passive Analog Signal Bypass Selection During Powerdown Register */ +#define PASSIVE_BYPASS 108 +/* DAC Quiescent Current Adjustment Register */ +#define DAC_ICC_ADJ 109 + +/* Page select register bits */ +#define PAGE0_SELECT 0 +#define PAGE1_SELECT 1 + +/* Audio serial data interface control register A bits */ +#define BIT_CLK_MASTER 0x80 +#define WORD_CLK_MASTER 0x40 +#define DOUT_TRISTATE 0x20 + +/* Codec Datapath setup register 7 */ +#define FSREF_44100 BIT(7) +#define FSREF_48000 (0 << 7) +#define DUAL_RATE_MODE ((1 << 5) | (1 << 6)) +#define LDAC2LCH (0x1 << 3) +#define RDAC2RCH (0x1 << 1) +#define LDAC2RCH (0x2 << 3) +#define RDAC2LCH (0x2 << 1) +#define LDAC2MONOMIX (0x3 << 3) +#define RDAC2MONOMIX (0x3 << 1) + +/* PLL registers bitfields */ +#define PLLP_SHIFT 0 +#define PLLP_MASK 7 +#define PLLQ_SHIFT 3 +#define PLLR_SHIFT 0 +#define PLLJ_SHIFT 2 +#define PLLD_MSB_SHIFT 0 +#define PLLD_LSB_SHIFT 2 + +/* Clock generation register bits */ +#define CODEC_CLKIN_PLLDIV 0 +#define CODEC_CLKIN_CLKDIV 1 +#define PLL_CLKIN_SHIFT 4 +#define MCLK_SOURCE 0x0 +#define PLL_CLKDIV_SHIFT 0 +#define PLLCLK_IN_MASK 0x30 +#define PLLCLK_IN_SHIFT 4 +#define CLKDIV_IN_MASK 0xc0 +#define CLKDIV_IN_SHIFT 6 +/* clock in source */ +#define CLKIN_MCLK 0 +#define CLKIN_GPIO2 1 +#define CLKIN_BCLK 2 + +/* Software reset register bits */ +#define SOFT_RESET 0x80 + +/* PLL progrramming register A bits */ +#define PLL_ENABLE 0x80 + +/* Route bits */ +#define ROUTE_ON 0x80 + +/* Mute bits */ +#define UNMUTE 0x08 +#define MUTE_ON 0x80 + +/* Power bits */ +#define LADC_PWR_ON 0x04 +#define RADC_PWR_ON 0x04 +#define LDAC_PWR_ON 0x80 +#define RDAC_PWR_ON 0x40 +#define HPLOUT_PWR_ON 0x01 +#define HPROUT_PWR_ON 0x01 +#define HPLCOM_PWR_ON 0x01 +#define HPRCOM_PWR_ON 0x01 +#define MONOLOPM_PWR_ON 0x01 +#define LLOPM_PWR_ON 0x01 +#define RLOPM_PWR_ON 0x01 + +#define INVERT_VOL(val) (0x7f - (val)) + +/* Default output volume (inverted) */ +#define DEFAULT_VOL INVERT_VOL(0x50) +/* Default input volume */ +#define DEFAULT_GAIN 0x20 + +/* MICBIAS Control Register */ +#define MICBIAS_LEVEL_SHIFT (6) +#define MICBIAS_LEVEL_MASK (3 << 6) + +/* HPOUT_SC */ +#define HPOUT_SC_OCMV_MASK (3 << 6) +#define HPOUT_SC_OCMV_SHIFT (6) +#define HPOUT_SC_OCMV_1_35V 0 +#define HPOUT_SC_OCMV_1_5V 1 +#define HPOUT_SC_OCMV_1_65V 2 +#define HPOUT_SC_OCMV_1_8V 3 + +/* headset detection / button API */ + +/* The AIC3x supports detection of stereo headsets (GND + left + right signal) + * and cellular headsets (GND + speaker output + microphone input). + * It is recommended to enable MIC bias for this function to work properly. + * For more information, please refer to the datasheet. + */ +enum { + AIC3X_HEADSET_DETECT_OFF = 0, + AIC3X_HEADSET_DETECT_STEREO = 1, + AIC3X_HEADSET_DETECT_CELLULAR = 2, + AIC3X_HEADSET_DETECT_BOTH = 3 +}; + +enum { + AIC3X_HEADSET_DEBOUNCE_16MS = 0, + AIC3X_HEADSET_DEBOUNCE_32MS = 1, + AIC3X_HEADSET_DEBOUNCE_64MS = 2, + AIC3X_HEADSET_DEBOUNCE_128MS = 3, + AIC3X_HEADSET_DEBOUNCE_256MS = 4, + AIC3X_HEADSET_DEBOUNCE_512MS = 5 +}; + +enum { + AIC3X_BUTTON_DEBOUNCE_0MS = 0, + AIC3X_BUTTON_DEBOUNCE_8MS = 1, + AIC3X_BUTTON_DEBOUNCE_16MS = 2, + AIC3X_BUTTON_DEBOUNCE_32MS = 3 +}; + +#define AIC3X_HEADSET_DETECT_ENABLED 0x80 +#define AIC3X_HEADSET_DETECT_SHIFT 5 +#define AIC3X_HEADSET_DETECT_MASK 3 +#define AIC3X_HEADSET_DEBOUNCE_SHIFT 2 +#define AIC3X_HEADSET_DEBOUNCE_MASK 7 +#define AIC3X_BUTTON_DEBOUNCE_SHIFT 0 +#define AIC3X_BUTTON_DEBOUNCE_MASK 3 + +/* from soc-dai.h*/ + +#define SND_SOC_DAIFMT_FORMAT_MASK 0x000f +#define SND_SOC_DAIFMT_CLOCK_MASK 0x00f0 +#define SND_SOC_DAIFMT_INV_MASK 0x0f00 +#define SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK 0xf000 + +#define SND_SOC_DAIFMT_CBP_CFP BIT(12) /* codec clk provider & frame provider */ +#define SND_SOC_DAIFMT_CBC_CFP (2 << 12) /* codec clk consumer & frame provider */ +#define SND_SOC_DAIFMT_CBP_CFC (3 << 12) /* codec clk provider & frame consumer */ +#define SND_SOC_DAIFMT_CBC_CFC (4 << 12) /* codec clk consumer & frame consumer */ + +/* + * DAI hardware signal polarity. + * + * Specifies whether the DAI can also support inverted clocks for the specified + * format. + * + * BCLK: + * - "normal" polarity means signal is available at rising edge of BCLK + * - "inverted" polarity means signal is available at falling edge of BCLK + * + * FSYNC "normal" polarity depends on the frame format: + * - I2S: frame consists of left then right channel data. Left channel starts + * with falling FSYNC edge, right channel starts with rising FSYNC edge. + * - Left/Right Justified: frame consists of left then right channel data. + * Left channel starts with rising FSYNC edge, right channel starts with + * falling FSYNC edge. + * - DSP A/B: Frame starts with rising FSYNC edge. + * - AC97: Frame starts with rising FSYNC edge. + * + * "Negative" FSYNC polarity is the one opposite of "normal" polarity. + */ + +#define SND_SOC_DAIFMT_NB_NF_UBT (0 << 8) /* normal bit clock + frame */ +#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ +#define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ +#define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */ + +#define EINVAL 22 /* Invalid argument */ + +#endif /* _AIC3X_H */

This patch adds driver for I2S interface for TI AM62x Soc. Configure the MCASP [1] component with appropriate settings and hardware parameters according to the audio data and devicetree. Set up the MCASP pins and fill the transmit buffers. The driver uses polling method to transfer data to the codec, since u-boot does not have DMA driver or interrupt functionality.
The TRM for the AM62X SoC contain the information for the MCASP component- Link: https://www.ti.com/lit/ug/spruiv7b/spruiv7b.pdf [1] (Section 12.1.1 : Audio Peripherals-MCASP)
The driver is based on the kernel mcasp driver[2]. Link: https://gitlab.com/linux-kernel/linux-next/-/blob/master/sound/soc/ti/davinc... [2]
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- MAINTAINERS | 2 + drivers/sound/Makefile | 1 + drivers/sound/davinci-mcasp.h | 413 ++++++++++++ drivers/sound/mcasp_i2s.c | 1123 +++++++++++++++++++++++++++++++++ 4 files changed, 1539 insertions(+) create mode 100644 drivers/sound/davinci-mcasp.h create mode 100644 drivers/sound/mcasp_i2s.c
diff --git a/MAINTAINERS b/MAINTAINERS index 5d7c260bfc..6097b3ba62 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -738,6 +738,8 @@ F: drivers/reset/reset-ti-sci.c F: drivers/rtc/davinci.c F: drivers/serial/serial_omap.c F: drivers/soc/ti/ +F: drivers/sound/davinci-mcasp.h +F: drivers/sound/mcasp_i2s.c F: drivers/sound/ti_sound.c F: drivers/sound/tlv320aic3106.c F: drivers/sound/tlv320aic3106.h diff --git a/drivers/sound/Makefile b/drivers/sound/Makefile index 1e9fbd1000..2068649172 100644 --- a/drivers/sound/Makefile +++ b/drivers/sound/Makefile @@ -24,4 +24,5 @@ obj-$(CONFIG_SOUND_RT5677) += rt5677.o obj-$(CONFIG_INTEL_BROADWELL) += broadwell_i2s.o broadwell_sound.o obj-$(CONFIG_SOUND_IVYBRIDGE) += ivybridge_sound.o obj-$(CONFIG_I2S_TI) += ti_sound.o +obj-$(CONFIG_I2S_TI) += mcasp_i2s.o obj-$(CONFIG_SOUND_TLV320AIC3106) += tlv320aic3106.o diff --git a/drivers/sound/davinci-mcasp.h b/drivers/sound/davinci-mcasp.h new file mode 100644 index 0000000000..83c77ba26c --- /dev/null +++ b/drivers/sound/davinci-mcasp.h @@ -0,0 +1,413 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA SoC McASP Audio Layer for TI DAVINCI processor + * + * MCASP related definitions + * + * Author: Nirmal Pandey n-pandey@ti.com, + * Suresh Rajashekara suresh.r@ti.com + * Steve Chen <schen@.mvista.com> + * + * Copyright: (C) 2009 MontaVista Software, Inc., source@mvista.com + * Copyright: (C) 2009 Texas Instruments, India + */ + +#ifndef DAVINCI_MCASP_H +#define DAVINCI_MCASP_H + +/* + * McASP register definitions + */ +#define DAVINCI_MCASP_PID_REG 0x00 +#define DAVINCI_MCASP_PWREMUMGT_REG 0x04 + +#define DAVINCI_MCASP_PFUNC_REG 0x10 +#define DAVINCI_MCASP_PDIR_REG 0x14 +#define DAVINCI_MCASP_PDOUT_REG 0x18 +#define DAVINCI_MCASP_PDSET_REG 0x1c + +#define DAVINCI_MCASP_PDCLR_REG 0x20 + +#define DAVINCI_MCASP_TLGC_REG 0x30 +#define DAVINCI_MCASP_TLMR_REG 0x34 + +#define DAVINCI_MCASP_GBLCTL_REG 0x44 +#define DAVINCI_MCASP_AMUTE_REG 0x48 +#define DAVINCI_MCASP_LBCTL_REG 0x4c + +#define DAVINCI_MCASP_TXDITCTL_REG 0x50 + +#define DAVINCI_MCASP_GBLCTLR_REG 0x60 +#define DAVINCI_MCASP_RXMASK_REG 0x64 +#define DAVINCI_MCASP_RXFMT_REG 0x68 +#define DAVINCI_MCASP_RXFMCTL_REG 0x6c + +#define DAVINCI_MCASP_ACLKRCTL_REG 0x70 +#define DAVINCI_MCASP_AHCLKRCTL_REG 0x74 +#define DAVINCI_MCASP_RXTDM_REG 0x78 +#define DAVINCI_MCASP_EVTCTLR_REG 0x7c + +#define DAVINCI_MCASP_RXSTAT_REG 0x80 +#define DAVINCI_MCASP_RXTDMSLOT_REG 0x84 +#define DAVINCI_MCASP_RXCLKCHK_REG 0x88 +#define DAVINCI_MCASP_REVTCTL_REG 0x8c + +#define DAVINCI_MCASP_GBLCTLX_REG 0xa0 +#define DAVINCI_MCASP_TXMASK_REG 0xa4 +#define DAVINCI_MCASP_TXFMT_REG 0xa8 +#define DAVINCI_MCASP_TXFMCTL_REG 0xac + +#define DAVINCI_MCASP_ACLKXCTL_REG 0xb0 +#define DAVINCI_MCASP_AHCLKXCTL_REG 0xb4 +#define DAVINCI_MCASP_TXTDM_REG 0xb8 +#define DAVINCI_MCASP_EVTCTLX_REG 0xbc + +#define DAVINCI_MCASP_TXSTAT_REG 0xc0 +#define DAVINCI_MCASP_TXTDMSLOT_REG 0xc4 +#define DAVINCI_MCASP_TXCLKCHK_REG 0xc8 +#define DAVINCI_MCASP_XEVTCTL_REG 0xcc + +/* Left(even TDM Slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRA_REG 0x100 +/* Right(odd TDM slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRB_REG 0x118 +/* Left(even TDM slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRA_REG 0x130 +/* Right(odd TDM Slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRB_REG 0x148 + +/* Serializer n Control Register */ +#define DAVINCI_MCASP_XRSRCTL_BASE_REG 0x180 +#define DAVINCI_MCASP_XRSRCTL_REG(n) (DAVINCI_MCASP_XRSRCTL_BASE_REG + \ + ((n) << 2)) + +/* Transmit Buffer for Serializer n */ +#define DAVINCI_MCASP_TXBUF_REG(n) (0x200 + ((n) << 2)) +/* Receive Buffer for Serializer n */ +#define DAVINCI_MCASP_RXBUF_REG(n) (0x280 + ((n) << 2)) + +/* McASP FIFO Registers */ +#define DAVINCI_MCASP_V2_AFIFO_BASE (0x1010) +#define DAVINCI_MCASP_V3_AFIFO_BASE (0x1000) + +/* FIFO register offsets from AFIFO base */ +#define MCASP_WFIFOCTL_OFFSET (0x0) +#define MCASP_WFIFOSTS_OFFSET (0x4) +#define MCASP_RFIFOCTL_OFFSET (0x8) +#define MCASP_RFIFOSTS_OFFSET (0xc) + +/* + * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management + * Register Bits + */ +#define MCASP_FREE BIT(0) +#define MCASP_SOFT BIT(1) + +/* + * DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits + * DAVINCI_MCASP_PDIR_REG - Pin Direction Register Bits + * DAVINCI_MCASP_PDOUT_REG - Pin output in GPIO mode + * DAVINCI_MCASP_PDSET_REG - Pin input in GPIO mode + */ +#define PIN_BIT_AXR(n) (n) +#define PIN_BIT_AMUTE 25 +#define PIN_BIT_ACLKX 26 +#define PIN_BIT_AHCLKX 27 +#define PIN_BIT_AFSX 28 +#define PIN_BIT_ACLKR 29 +#define PIN_BIT_AHCLKR 30 +#define PIN_BIT_AFSR 31 + +/* + * DAVINCI_MCASP_TXDITCTL_REG - Transmit DIT Control Register Bits + */ +#define DITEN BIT(0) /* Transmit DIT mode enable/disable */ +#define VA BIT(2) +#define VB BIT(3) + +/* + * DAVINCI_MCASP_TXFMT_REG - Transmit Bitstream Format Register Bits + */ +#define TXROT(val) (val) +#define TXSEL BIT(3) +#define TXSSZ(val) ((val) << 4) +#define TXPBIT(val) ((val) << 8) +#define TXPAD(val) ((val) << 13) +#define TXORD BIT(15) +#define FSXDLY(val) ((val) << 16) + +/* + * DAVINCI_MCASP_RXFMT_REG - Receive Bitstream Format Register Bits + */ +#define RXROT(val) (val) +#define RXSEL BIT(3) +#define RXSSZ(val) ((val) << 4) +#define RXPBIT(val) ((val) << 8) +#define RXPAD(val) ((val) << 13) +#define RXORD BIT(15) +#define FSRDLY(val) ((val) << 16) + +/* + * DAVINCI_MCASP_TXFMCTL_REG - Transmit Frame Control Register Bits + */ +#define FSXPOL BIT(0) +#define AFSXE BIT(1) +#define FSXDUR BIT(4) +#define FSXMOD(val) ((val) << 7) + +/* + * DAVINCI_MCASP_RXFMCTL_REG - Receive Frame Control Register Bits + */ +#define FSRPOL BIT(0) +#define AFSRE BIT(1) +#define FSRDUR BIT(4) +#define FSRMOD(val) ((val) << 7) + +/* + * DAVINCI_MCASP_ACLKXCTL_REG - Transmit Clock Control Register Bits + */ +#define ACLKXDIV(val) (val) +#define ACLKXE BIT(5) +#define TX_ASYNC BIT(6) +#define ACLKXPOL BIT(7) +#define ACLKXDIV_MASK 0x1f + +/* + * DAVINCI_MCASP_ACLKRCTL_REG Receive Clock Control Register Bits + */ +#define ACLKRDIV(val) (val) +#define ACLKRE BIT(5) +#define RX_ASYNC BIT(6) +#define ACLKRPOL BIT(7) +#define ACLKRDIV_MASK 0x1f + +/* + * DAVINCI_MCASP_AHCLKXCTL_REG - High Frequency Transmit Clock Control + * Register Bits + */ +#define AHCLKXDIV(val) (val) +#define AHCLKXPOL BIT(14) +#define AHCLKXE BIT(15) +#define AHCLKXDIV_MASK 0xfff + +/* + * DAVINCI_MCASP_AHCLKRCTL_REG - High Frequency Receive Clock Control + * Register Bits + */ +#define AHCLKRDIV(val) (val) +#define AHCLKRPOL BIT(14) +#define AHCLKRE BIT(15) +#define AHCLKRDIV_MASK 0xfff + +/* + * DAVINCI_MCASP_XRSRCTL_BASE_REG - Serializer Control Register Bits + */ +#define MODE(val) (val) +#define DISMOD_3STATE (0x0) +#define DISMOD_LOW (0x2 << 2) +#define DISMOD_HIGH (0x3 << 2) +#define DISMOD_VAL(x) ((x) << 2) +#define DISMOD_MASK DISMOD_HIGH +#define TXSTATE BIT(4) +#define RXSTATE BIT(5) +#define SRMOD_MASK 3 +#define SRMOD_INACTIVE 0 + +/* + * DAVINCI_MCASP_LBCTL_REG - Loop Back Control Register Bits + */ +#define LBEN BIT(0) +#define LBORD BIT(1) +#define LBGENMODE(val) ((val) << 2) + +/* + * DAVINCI_MCASP_TXTDMSLOT_REG - Transmit TDM Slot Register configuration + */ +#define TXTDMS(n) (1 << (n)) + +/* + * DAVINCI_MCASP_RXTDMSLOT_REG - Receive TDM Slot Register configuration + */ +#define RXTDMS(n) (1 << (n)) + +/* + * DAVINCI_MCASP_GBLCTL_REG - Global Control Register Bits + */ +#define RXCLKRST BIT(0) /* Receiver Clock Divider Reset */ +#define RXHCLKRST BIT(1) /* Receiver High Frequency Clock Divider */ +#define RXSERCLR BIT(2) /* Receiver Serializer Clear */ +#define RXSMRST BIT(3) /* Receiver State Machine Reset */ +#define RXFSRST BIT(4) /* Frame Sync Generator Reset */ +#define TXCLKRST BIT(8) /* Transmitter Clock Divider Reset */ +#define TXHCLKRST BIT(9) /* Transmitter High Frequency Clock Divider*/ +#define TXSERCLR BIT(10) /* Transmit Serializer Clear */ +#define TXSMRST BIT(11) /* Transmitter State Machine Reset */ +#define TXFSRST BIT(12) /* Frame Sync Generator Reset */ + +/* + * DAVINCI_MCASP_TXSTAT_REG - Transmitter Status Register Bits + * DAVINCI_MCASP_RXSTAT_REG - Receiver Status Register Bits + */ +#define XRERR BIT(8) /* Transmit/Receive error */ +#define XRDATA BIT(5) /* Transmit/Receive data ready */ + +/* + * DAVINCI_MCASP_AMUTE_REG - Mute Control Register Bits + */ +#define MUTENA(val) (val) +#define MUTEINPOL BIT(2) +#define MUTEINENA BIT(3) +#define MUTEIN BIT(4) +#define MUTER BIT(5) +#define MUTEX BIT(6) +#define MUTEFSR BIT(7) +#define MUTEFSX BIT(8) +#define MUTEBADCLKR BIT(9) +#define MUTEBADCLKX BIT(10) +#define MUTERXDMAERR BIT(11) +#define MUTETXDMAERR BIT(12) + +/* + * DAVINCI_MCASP_REVTCTL_REG - Receiver DMA Event Control Register bits + */ +#define RXDATADMADIS BIT(0) + +/* + * DAVINCI_MCASP_XEVTCTL_REG - Transmitter DMA Event Control Register bits + */ +#define TXDATADMADIS BIT(0) + +/* + * DAVINCI_MCASP_EVTCTLR_REG - Receiver Interrupt Control Register Bits + */ +#define ROVRN BIT(0) + +/* + * DAVINCI_MCASP_EVTCTLX_REG - Transmitter Interrupt Control Register Bits + */ +#define XUNDRN BIT(0) + +/* + * DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits + */ +#define FIFO_ENABLE BIT(16) +#define NUMEVT_MASK (0xFF << 8) +#define NUMEVT(x) (((x) & 0xFF) << 8) +#define NUMDMA_MASK (0xFF) + +/* Source of High-frequency transmit/receive clock */ +#define MCASP_CLK_HCLK_AHCLK 0 /* AHCLKX/R */ +#define MCASP_CLK_HCLK_AUXCLK 1 /* Internal functional clock */ + +/* clock divider IDs */ +#define MCASP_CLKDIV_AUXCLK 0 /* HCLK divider from AUXCLK */ +#define MCASP_CLKDIV_BCLK 1 /* BCLK divider from HCLK */ +#define MCASP_CLKDIV_BCLK_FS_RATIO 2 /* to set BCLK FS ration */ + +#define INACTIVE_MODE 0 +#define TX_MODE 1 +#define RX_MODE 2 + +#define DAVINCI_MCASP_IIS_MODE 0 +#define DAVINCI_MCASP_DIT_MODE 1 + +enum { + SNDRV_PCM_STREAM_PLAYBACK = 0, + SNDRV_PCM_STREAM_CAPTURE, + SNDRV_PCM_STREAM_LAST = SNDRV_PCM_STREAM_CAPTURE, +}; + +enum { + MCASP_VERSION_1 = 0, /* DM646x */ + MCASP_VERSION_2, /* DA8xx/OMAPL1x */ + MCASP_VERSION_3, /* TI81xx/AM33xx */ + MCASP_VERSION_4, /* DRA7xxx */ + MCASP_VERSION_OMAP, /* OMAP4/5 */ +}; + +/* Some SND defaults */ + +#define SND_SOC_DAIFMT_FORMAT_MASK 0x000f +#define SND_SOC_DAIFMT_CLOCK_MASK 0x00f0 +#define SND_SOC_DAIFMT_INV_MASK 0x0f00 +#define SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK 0xf000 + +#define SND_SOC_DAIFMT_CBP_CFP BIT(12) /* codec clk provider & frame provider */ +#define SND_SOC_DAIFMT_CBC_CFP (2 << 12) /* codec clk consumer & frame provider */ +#define SND_SOC_DAIFMT_CBP_CFC (3 << 12) /* codec clk provider & frame consumer */ +#define SND_SOC_DAIFMT_CBC_CFC (4 << 12) /* codec clk consumer & frame consumer */ + +/* when passed to set_fmt directly indicate if the device is provider or consumer */ +#define SND_SOC_DAIFMT_BP_FP SND_SOC_DAIFMT_CBP_CFP +#define SND_SOC_DAIFMT_BC_FP SND_SOC_DAIFMT_CBC_CFP +#define SND_SOC_DAIFMT_BP_FC SND_SOC_DAIFMT_CBP_CFC +#define SND_SOC_DAIFMT_BC_FC SND_SOC_DAIFMT_CBC_CFC + +#define SND_SOC_DAIFMT_NB_NF_UBT (0 << 8) /* normal bit clock + frame */ +#define SND_SOC_DAIFMT_NB_IF (2 << 8) /* normal BCLK + inv FRM */ +#define SND_SOC_DAIFMT_IB_NF (3 << 8) /* invert BCLK + nor FRM */ +#define SND_SOC_DAIFMT_IB_IF (4 << 8) /* invert BCLK + FRM */ + +/* + * Master Clock Directions + */ +#define SND_SOC_CLOCK_IN 0 +#define SND_SOC_CLOCK_OUT 1 + +typedef int __bitwise snd_pcm_format_t; +#define SNDRV_PCM_FORMAT_S8 ((snd_pcm_format_t)0) +#define SNDRV_PCM_FORMAT_U8 ((snd_pcm_format_t)1) +#define SNDRV_PCM_FORMAT_S16_LE ((snd_pcm_format_t)2) +#define SNDRV_PCM_FORMAT_S16_BE ((snd_pcm_format_t)3) +#define SNDRV_PCM_FORMAT_U16_LE ((snd_pcm_format_t)4) +#define SNDRV_PCM_FORMAT_U16_BE ((snd_pcm_format_t)5) +#define SNDRV_PCM_FORMAT_S24_LE ((snd_pcm_format_t)6) /* low three bytes */ +#define SNDRV_PCM_FORMAT_S24_BE ((snd_pcm_format_t)7) /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_LE ((snd_pcm_format_t)8) /* low three bytes */ +#define SNDRV_PCM_FORMAT_U24_BE ((snd_pcm_format_t)9) /* low three bytes */ +#define SNDRV_PCM_FORMAT_S32_LE ((snd_pcm_format_t)10) +#define SNDRV_PCM_FORMAT_S32_BE ((snd_pcm_format_t)11) +#define SNDRV_PCM_FORMAT_U32_LE ((snd_pcm_format_t)12) +#define SNDRV_PCM_FORMAT_U32_BE ((snd_pcm_format_t)13) +#define SNDRV_PCM_FORMAT_FLOAT_LE ((snd_pcm_format_t)14) +#define SNDRV_PCM_FORMAT_FLOAT_BE ((snd_pcm_format_t)15) +#define SNDRV_PCM_FORMAT_FLOAT64_LE ((snd_pcm_format_t)16) +#define SNDRV_PCM_FORMAT_FLOAT64_BE ((snd_pcm_format_t)17) +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE ((snd_pcm_format_t)18) +#define SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE ((snd_pcm_format_t)19) +#define SNDRV_PCM_FORMAT_MU_LAW ((snd_pcm_format_t)20) +#define SNDRV_PCM_FORMAT_A_LAW ((snd_pcm_format_t)21) +#define SNDRV_PCM_FORMAT_IMA_ADPCM ((snd_pcm_format_t)22) +#define SNDRV_PCM_FORMAT_MPEG ((snd_pcm_format_t)23) +#define SNDRV_PCM_FORMAT_GSM ((snd_pcm_format_t)24) +#define SNDRV_PCM_FORMAT_S20_LE ((snd_pcm_format_t)25) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_S20_BE ((snd_pcm_format_t)26) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_LE ((snd_pcm_format_t)27) /* in four bytes, LSB justified */ +#define SNDRV_PCM_FORMAT_U20_BE ((snd_pcm_format_t)28) /* in four bytes, LSB justified */ +/* gap in the numbering for a future standard linear format */ +#define SNDRV_PCM_FORMAT_SPECIAL ((snd_pcm_format_t)31) +#define SNDRV_PCM_FORMAT_S24_3LE ((snd_pcm_format_t)32) /* in three bytes */ +#define SNDRV_PCM_FORMAT_S24_3BE ((snd_pcm_format_t)33) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3LE ((snd_pcm_format_t)34) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U24_3BE ((snd_pcm_format_t)35) /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3LE ((snd_pcm_format_t)36) /* in three bytes */ +#define SNDRV_PCM_FORMAT_S20_3BE ((snd_pcm_format_t)37) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3LE ((snd_pcm_format_t)38) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U20_3BE ((snd_pcm_format_t)39) /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3LE ((snd_pcm_format_t)40) /* in three bytes */ +#define SNDRV_PCM_FORMAT_S18_3BE ((snd_pcm_format_t)41) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3LE ((snd_pcm_format_t)42) /* in three bytes */ +#define SNDRV_PCM_FORMAT_U18_3BE ((snd_pcm_format_t)43) /* in three bytes */ +#define SNDRV_PCM_FORMAT_G723_24 ((snd_pcm_format_t)44) /* 8 samples in 3 bytes */ +#define SNDRV_PCM_FORMAT_G723_24_1B ((snd_pcm_format_t)45) /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_G723_40 ((snd_pcm_format_t)46) /* 8 Samples in 5 bytes */ +#define SNDRV_PCM_FORMAT_G723_40_1B ((snd_pcm_format_t)47) /* 1 sample in 1 byte */ +#define SNDRV_PCM_FORMAT_DSD_U8 ((snd_pcm_format_t)48) /* DSD, 1-byte samples DSD (x8) */ +#define SNDRV_PCM_FORMAT_DSD_U16_LE ((snd_pcm_format_t)49) +#define SNDRV_PCM_FORMAT_DSD_U32_LE ((snd_pcm_format_t)50) +#define SNDRV_PCM_FORMAT_DSD_U16_BE ((snd_pcm_format_t)51) +#define SNDRV_PCM_FORMAT_DSD_U32_BE ((snd_pcm_format_t)52) +#define SNDRV_PCM_FORMAT_LAST SNDRV_PCM_FORMAT_DSD_U32_BE +#define SNDRV_PCM_FORMAT_FIRST SNDRV_PCM_FORMAT_S8 + +#endif /* DAVINCI_MCASP_H */ diff --git a/drivers/sound/mcasp_i2s.c b/drivers/sound/mcasp_i2s.c new file mode 100644 index 0000000000..6da4badd9c --- /dev/null +++ b/drivers/sound/mcasp_i2s.c @@ -0,0 +1,1123 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2024 Texas Instruments Incorporated - https://www.ti.com/ + * Scaria M Kochidanadu, s-kochidanadu@ti.com + * + * based on the linux MCASP driver, which is + * + * Author: Nirmal Pandey n-pandey@ti.com, + * Suresh Rajashekara suresh.r@ti.com + * Steve Chen <schen@.mvista.com> + * + * Copyright: (C) 2009 MontaVista Software, Inc., source@mvista.com + * Copyright: (C) 2009 Texas Instruments, India + */ + +#include <asm/u-boot.h> /* boot information for Linux kernel */ +/* Pull in stuff for the build system */ +#ifdef DO_DEPS_ONLY +#include <env_internal.h> +#endif +#include <dm.h> +#include <i2s.h> +#include <log.h> +#include <sound.h> +#include <asm/io.h> +#include <linux/bitmap.h> +#include <linux/math64.h> +#include <linux/ioport.h> +#include "davinci-mcasp.h" + +#define MCASP_MAX_AFIFO_DEPTH 64 + +struct mcasp_priv { + void __iomem *base; + u32 fifo_base; + struct i2s_uc_priv *pdata; + struct udevice *dev; + unsigned int dai_fmt; + unsigned long dat_port_addr; + + /* McASP specific data */ + int tdm_slots; + u32 tdm_mask[2]; + int slot_width; + u8 op_mode; + u8 dismod; + u8 num_serializer; + u32 *serial_dir; + u8 version; + u8 bclk_div; + int streams; + + int sysclk_freq; + bool bclk_master; + + unsigned long pdir; /* Pin direction bitfield */ + + /* McASP FIFO related */ + u8 txnumevt; + u8 rxnumevt; + + bool dat_port; + + /* Used for comstraint setting on the second stream */ + u32 channels; + int max_format_width; + u8 active_serializers[2]; + + bool missing_audio_param; + +}; + +/* Register Data manipulation Functions */ + +static inline void set_bit_pdir(int nbit, unsigned long *pdir) +{ + *pdir = *pdir | BIT(nbit); +} + +static inline void clear_bit_pdir(int nbit, unsigned long *pdir) +{ + *pdir = *pdir & (~BIT(nbit)); +} + +static inline void mcasp_set_bits(struct mcasp_priv *mcasp, u32 offset, u32 val) +{ + void __iomem *reg = mcasp->base + offset; + + __raw_writel(__raw_readl(reg) | val, reg); +} + +static inline void mcasp_clr_bits(struct mcasp_priv *mcasp, u32 offset, + u32 val) +{ + void __iomem *reg = mcasp->base + offset; + + __raw_writel((__raw_readl(reg) & ~(val)), reg); +} + +static inline void mcasp_mod_bits(struct mcasp_priv *mcasp, u32 offset, + u32 val, u32 mask) +{ + void __iomem *reg = mcasp->base + offset; + + __raw_writel((__raw_readl(reg) & ~mask) | val, reg); +} + +static inline void mcasp_set_reg(struct mcasp_priv *mcasp, u32 offset, + u32 val) +{ + __raw_writel(val, mcasp->base + offset); +} + +static inline u32 mcasp_get_reg(struct mcasp_priv *mcasp, u32 offset) +{ + return (u32)__raw_readl(mcasp->base + offset); +} + +static void mcasp_set_ctl_reg(struct mcasp_priv *mcasp, u32 ctl_reg, u32 val) +{ + int i = 0; + + mcasp_set_bits(mcasp, ctl_reg, val); + + /* programming GBLCTL needs to read back from GBLCTL and verfiy */ + /* loop count is to avoid the lock-up */ + for (i = 0; i < 1000; i++) { + if ((mcasp_get_reg(mcasp, ctl_reg) & val) == val) + break; + } + + if (i == 1000 && ((mcasp_get_reg(mcasp, ctl_reg) & val) != val)) + printf("GBLCTL write error\n"); +} + +/********************************************************************/ +static bool mcasp_is_synchronous(struct mcasp_priv *mcasp) +{ + u32 rxfmctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_RXFMCTL_REG); + u32 aclkxctl = mcasp_get_reg(mcasp, DAVINCI_MCASP_ACLKXCTL_REG); + + return !(aclkxctl & TX_ASYNC) && rxfmctl & AFSRE; +} + +static inline void mcasp_set_clk_pdir(struct mcasp_priv *mcasp, bool enable) +{ + u32 bit = PIN_BIT_AMUTE; + + for_each_set_bit_from(bit, &mcasp->pdir, PIN_BIT_AFSR + 1) { + if (enable) + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + else + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + } +} + +static inline void mcasp_set_axr_pdir(struct mcasp_priv *mcasp, bool enable) +{ + u32 bit; + + for_each_set_bit(bit, &mcasp->pdir, PIN_BIT_AMUTE) { + if (enable) + mcasp_set_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + else + mcasp_clr_bits(mcasp, DAVINCI_MCASP_PDIR_REG, BIT(bit)); + } +} + +static int davinci_mcasp_get_config(struct mcasp_priv *mcasp) +{ + u32 val; + + if (!dev_read_u32u(mcasp->dev, "op-mode", &val)) { + mcasp->op_mode = val; + } else { + mcasp->missing_audio_param = true; + goto out; + } + + if (!dev_read_u32u(mcasp->dev, "tdm-slots", &val)) { + if (val < 2 || val > 32) + debug("tdm-slots must be in range[2-32]\n"); + + mcasp->tdm_slots = val; + } else if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + mcasp->missing_audio_param = true; + goto out; + } + + /* In u-boot we do not have DMA => We do not use DAT_port for transfer*/ + mcasp->dat_port = false; + mcasp->dat_port_addr = 0x02b18000; + + /* serial-dir */ + u32 *of_serial_dir32; + + if (!dev_read_u32_array(mcasp->dev, "serial-dir", of_serial_dir32, 16)) { + mcasp->serial_dir = of_serial_dir32; + mcasp->num_serializer = 16; + } else { + printf("error in reading serial-dir from DT\n"); + } + + if (!dev_read_u32u(mcasp->dev, "tx-num-evt", &val)) + mcasp->txnumevt = val; + else + printf("error in reading tx-num-evt\n"); + + if (!dev_read_u32u(mcasp->dev, "rx-num-evt", &val)) + mcasp->rxnumevt = val; + else + printf("error in reading tx-num-evt\n"); + + if (!dev_read_u32u(mcasp->dev, "dismod", &val)) { + if (val == 0 || val == 2 || val == 3) { + mcasp->dismod = DISMOD_VAL(val); + } else { + printf("Invalid dismod value: %u\n", val); + mcasp->dismod = DISMOD_LOW; + } + } else { + mcasp->dismod = DISMOD_LOW; + } + +out: + if (mcasp->missing_audio_param) { + printf("Insufficient DT parameter(s)\n"); + return -ENODEV; + } + /* sanity check for tdm slots parameter */ + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + if (mcasp->tdm_slots < 2) { + printf("invalid tdm slots: %d\n", + mcasp->tdm_slots); + mcasp->tdm_slots = 2; + } else if (mcasp->tdm_slots > 32) { + printf("invalid tdm slots: %d\n", + mcasp->tdm_slots); + mcasp->tdm_slots = 32; + } + } else { + mcasp->tdm_slots = 32; + } + return 0; +} + +static int mcasp_i2s_of_to_plat(struct udevice *dev) +{ + struct i2s_uc_priv *priv = dev_get_uclass_priv(dev); + ulong base; + + base = dev_read_addr(dev); + if (base == FDT_ADDR_T_NONE) { + debug("%s: Missing i2s base\n", __func__); + return -EINVAL; + } + priv->base_address = base; + + if (dev_read_u32u(dev, "ti,i2s-epll-clock-frequency", + &priv->audio_pll_clk)) + goto err; + debug("audio_pll_clk = %d\n", priv->audio_pll_clk); + if (dev_read_u32u(dev, "ti,i2s-sampling-rate", + &priv->samplingrate)) + goto err; + debug("samplingrate = %d\n", priv->samplingrate); + if (dev_read_u32u(dev, "ti,i2s-bits-per-sample", + &priv->bitspersample)) + goto err; + debug("bitspersample = %d\n", priv->bitspersample); + if (dev_read_u32u(dev, "ti,i2s-channels", &priv->channels)) + goto err; + debug("channels = %d\n", priv->channels); + if (dev_read_u32u(dev, "ti,i2s-lr-clk-framesize", &priv->rfs)) + goto err; + debug("rfs = %d\n", priv->rfs); + if (dev_read_u32u(dev, "ti,i2s-bit-clk-framesize", &priv->bfs)) + goto err; + debug("bfs = %d\n", priv->bfs); + + if (dev_read_u32u(dev, "ti,i2s-id", &priv->id)) + goto err; + debug("id = %d\n", priv->id); + return 0; + +err: + debug("fail to get sound i2s node properties\n"); + return -EINVAL; +} + +static int davinci_mcasp_set_dai_fmt(struct mcasp_priv *mcasp, + unsigned int fmt) +{ + int ret = 0; + u32 data_delay; + bool fs_pol_rising; + bool inv_fs = false; + + if (!fmt) + return 0; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* 1st data bit occur one ACLK cycle after the frame sync */ + data_delay = 1; + break; + case SND_SOC_DAIFMT_DSP_B: + case SND_SOC_DAIFMT_AC97: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* No delay after FS */ + data_delay = 0; + break; + case SND_SOC_DAIFMT_I2S: + /* configure a full-word SYNC pulse (LRCLK) */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* 1st data bit occur one ACLK cycle after the frame sync */ + data_delay = 1; + /* FS need to be inverted */ + inv_fs = true; + break; + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + /* configure a full-word SYNC pulse (LRCLK) */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + /* No delay after FS */ + data_delay = 0; + break; + default: + ret = -EINVAL; + goto out; + } + + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, FSXDLY(data_delay), + FSXDLY(3)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, FSRDLY(data_delay), + FSRDLY(3)); + + switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) { + case SND_SOC_DAIFMT_BP_FP: + /* codec is clock and frame slave */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + /* BCLK */ + + set_bit_pdir(PIN_BIT_ACLKX, &mcasp->pdir); + set_bit_pdir(PIN_BIT_ACLKR, &mcasp->pdir); + /* Frame Sync */ + set_bit_pdir(PIN_BIT_AFSX, &mcasp->pdir); + set_bit_pdir(PIN_BIT_AFSR, &mcasp->pdir); + + mcasp->bclk_master = 1; + break; + case SND_SOC_DAIFMT_BP_FC: + /* codec is clock slave and frame master */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + /* BCLK */ + set_bit_pdir(PIN_BIT_ACLKX, &mcasp->pdir); + set_bit_pdir(PIN_BIT_ACLKR, &mcasp->pdir); + /* Frame Sync */ + clear_bit_pdir(PIN_BIT_AFSX, &mcasp->pdir); + clear_bit_pdir(PIN_BIT_AFSR, &mcasp->pdir); + + mcasp->bclk_master = 1; + break; + case SND_SOC_DAIFMT_BC_FP: + /* codec is clock master and frame slave */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + /* BCLK */ + clear_bit_pdir(PIN_BIT_ACLKX, &mcasp->pdir); + clear_bit_pdir(PIN_BIT_ACLKR, &mcasp->pdir); + /* Frame Sync */ + set_bit_pdir(PIN_BIT_AFSX, &mcasp->pdir); + set_bit_pdir(PIN_BIT_AFSR, &mcasp->pdir); + + mcasp->bclk_master = 0; + break; + case SND_SOC_DAIFMT_BC_FC: + /* codec is clock and frame master */ + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + /* BCLK */ + clear_bit_pdir(PIN_BIT_ACLKX, &mcasp->pdir); + clear_bit_pdir(PIN_BIT_ACLKR, &mcasp->pdir); + /* Frame Sync */ + clear_bit_pdir(PIN_BIT_AFSX, &mcasp->pdir); + clear_bit_pdir(PIN_BIT_AFSR, &mcasp->pdir); + + mcasp->bclk_master = 0; + break; + default: + ret = -EINVAL; + goto out; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = true; + break; + case SND_SOC_DAIFMT_NB_IF: + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = false; + break; + case SND_SOC_DAIFMT_IB_IF: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = false; + break; + case SND_SOC_DAIFMT_NB_NF_UBT: + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + fs_pol_rising = true; + break; + default: + ret = -EINVAL; + goto out; + } + + if (inv_fs) + fs_pol_rising = !fs_pol_rising; + + if (fs_pol_rising) { + mcasp_clr_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + } else { + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + } + + mcasp->dai_fmt = fmt; +out: + return ret; +} + +static int mcasp_common_hw_param(struct mcasp_priv *mcasp, int stream, + int period_words, int channels) +{ + int i; + u8 tx_ser = 0; + u8 rx_ser = 0; + u8 slots = mcasp->tdm_slots; + u8 max_active_serializers, max_rx_serializers, max_tx_serializers; + int active_serializers, numevt; + u32 reg; + /* In DIT mode we only allow maximum of one serializers for now */ + if (mcasp->op_mode == DAVINCI_MCASP_DIT_MODE) + max_active_serializers = 1; + else + max_active_serializers = (channels + slots - 1) / slots; + + /* Default configuration */ + if (mcasp->version < MCASP_VERSION_3) + mcasp_set_bits(mcasp, DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + mcasp_set_bits(mcasp, DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS); + max_tx_serializers = max_active_serializers; + max_rx_serializers = + mcasp->active_serializers[SNDRV_PCM_STREAM_CAPTURE]; + } else { + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_REVTCTL_REG, RXDATADMADIS); + max_tx_serializers = + mcasp->active_serializers[SNDRV_PCM_STREAM_PLAYBACK]; + max_rx_serializers = max_active_serializers; + } + + for (i = 0; i < mcasp->num_serializer; i++) { + mcasp_set_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + mcasp->serial_dir[i]); + if (mcasp->serial_dir[i] == TX_MODE && tx_ser < max_tx_serializers) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + mcasp->dismod, DISMOD_MASK); + set_bit_pdir(PIN_BIT_AXR(i), &mcasp->pdir); + tx_ser++; + } else if (mcasp->serial_dir[i] == RX_MODE && + rx_ser < max_rx_serializers) { + clear_bit_pdir(PIN_BIT_AXR(i), &mcasp->pdir); + rx_ser++; + } else { + /* Inactive or unused pin, set it to inactive */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i), + SRMOD_INACTIVE, SRMOD_MASK); + /* If unused, set DISMOD for the pin */ + if (mcasp->serial_dir[i] != INACTIVE_MODE) + mcasp_mod_bits(mcasp, + DAVINCI_MCASP_XRSRCTL_REG(i), + mcasp->dismod, DISMOD_MASK); + clear_bit_pdir(PIN_BIT_AXR(i), &mcasp->pdir); + } + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + active_serializers = tx_ser; + numevt = mcasp->txnumevt; + reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + } else { + active_serializers = rx_ser; + numevt = mcasp->rxnumevt; + reg = mcasp->fifo_base + MCASP_RFIFOCTL_OFFSET; + } + + if (active_serializers < max_active_serializers) { + debug("stream has more channels (%d) than are enabled in mcasp (%d)\n", channels, + active_serializers * slots); + return -EINVAL; + } + + if (period_words % active_serializers) { + debug("Invalid combination of period words and active serializers: %d, %d\n", + period_words, active_serializers); + return -EINVAL; + } + + /* + * Calculate the optimal AFIFO depth for platform side: + * The number of words for numevt need to be in steps of active + * serializers. + */ + numevt = (numevt / active_serializers) * active_serializers; + + while (period_words % numevt && numevt > 0) + numevt -= active_serializers; + if (numevt <= 0) + numevt = active_serializers; + + mcasp_mod_bits(mcasp, reg, active_serializers, NUMDMA_MASK); + mcasp_mod_bits(mcasp, reg, NUMEVT(numevt), NUMEVT_MASK); + + mcasp->active_serializers[stream] = active_serializers; + + return 0; +} + +static int mcasp_i2s_hw_param(struct mcasp_priv *mcasp, int stream, + int channels) +{ + int i, active_slots; + int total_slots; + int active_serializers; + u32 mask = 0; + u32 busel = 0; + + total_slots = mcasp->tdm_slots; + + /* + * If more than one serializer is needed, then use them with + * all the specified tdm_slots. Otherwise, one serializer can + * cope with the transaction using just as many slots as there + * are channels in the stream. + */ + if (mcasp->tdm_mask[stream]) { + active_slots = hweight32(mcasp->tdm_mask[stream]); + active_serializers = (channels + active_slots - 1) / + active_slots; + if (active_serializers == 1) + active_slots = channels; + for (i = 0; i < total_slots; i++) { + if ((1 << i) & mcasp->tdm_mask[stream]) { + mask |= (1 << i); + if (--active_slots <= 0) + break; + } + } + } else { + active_serializers = (channels + total_slots - 1) / total_slots; + if (active_serializers == 1) + active_slots = channels; + else + active_slots = total_slots; + + for (i = 0; i < active_slots; i++) + mask |= (1 << i); + } + + mcasp_clr_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); + + if (!mcasp->dat_port) + busel = TXSEL; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXTDM_REG, mask); + mcasp_set_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, busel | TXORD); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, + FSXMOD(total_slots), FSXMOD(0x1FF)); + } else if (stream == SNDRV_PCM_STREAM_CAPTURE) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXTDM_REG, mask); + mcasp_set_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, busel | RXORD); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMCTL_REG, + FSRMOD(total_slots), FSRMOD(0x1FF)); + /* + * If McASP is set to be TX/RX synchronous and the playback is + * not running already we need to configure the TX slots in + * order to have correct FSX on the bus + */ + if (mcasp_is_synchronous(mcasp) && !mcasp->channels) + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMCTL_REG, + FSXMOD(total_slots), FSXMOD(0x1FF)); + } + + return 0; +} + +static int davinci_config_channel_size(struct mcasp_priv *mcasp, + int sample_width) +{ + u32 fmt; + u32 tx_rotate, rx_rotate, slot_width; + u32 mask = (1ULL << sample_width) - 1; + + if (mcasp->slot_width) + slot_width = mcasp->slot_width; + else if (mcasp->max_format_width) + slot_width = mcasp->max_format_width; + else + slot_width = sample_width; + /* + * TX rotation: + * right aligned formats: rotate w/ slot_width + * left aligned formats: rotate w/ sample_width + * + * RX rotation: + * right aligned formats: no rotation needed + * left aligned formats: rotate w/ (slot_width - sample_width) + */ + if ((mcasp->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) == + SND_SOC_DAIFMT_RIGHT_J) { + tx_rotate = (slot_width / 4) & 0x7; + rx_rotate = 0; + } else { + tx_rotate = (sample_width / 4) & 0x7; + rx_rotate = (slot_width - sample_width) / 4; + } + + /* mapping of the XSSZ bit-field as described in the datasheet */ + fmt = (slot_width >> 1) - 1; + + if (mcasp->op_mode != DAVINCI_MCASP_DIT_MODE) { + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXSSZ(fmt), + RXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(fmt), + TXSSZ(0x0F)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(tx_rotate), + TXROT(7)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_RXFMT_REG, RXROT(rx_rotate), + RXROT(7)); + mcasp_set_reg(mcasp, DAVINCI_MCASP_RXMASK_REG, mask); + } else { + /* + * according to the TRM it should be TXROT=0, this one works: + * 16 bit to 23-8 (TXROT=6, rotate 24 bits) + * 24 bit to 23-0 (TXROT=0, rotate 0 bits) + * + * TXROT = 0 only works with 24bit samples + */ + tx_rotate = (sample_width / 4 + 2) & 0x7; + + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXROT(tx_rotate), + TXROT(7)); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_TXFMT_REG, TXSSZ(15), + TXSSZ(0x0F)); + } + + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXMASK_REG, mask); + + return 0; +} + +static int __davinci_mcasp_set_clkdiv(struct mcasp_priv *mcasp, int div_id, + int div, bool explicit) +{ + switch (div_id) { + case MCASP_CLKDIV_AUXCLK: /* MCLK divider */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXDIV(div - 1), AHCLKXDIV_MASK); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRDIV(div - 1), AHCLKRDIV_MASK); + break; + + case MCASP_CLKDIV_BCLK: /* BCLK divider */ + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKXCTL_REG, + ACLKXDIV(div - 1), ACLKXDIV_MASK); + mcasp_mod_bits(mcasp, DAVINCI_MCASP_ACLKRCTL_REG, + ACLKRDIV(div - 1), ACLKRDIV_MASK); + if (explicit) + mcasp->bclk_div = div; + break; + + case MCASP_CLKDIV_BCLK_FS_RATIO: + /* + * BCLK/LRCLK ratio descries how many bit-clock cycles + * fit into one frame. The clock ratio is given for a + * full period of data (for I2S format both left and + * right channels), so it has to be divided by number + * of tdm-slots (for I2S - divided by 2). + * Instead of storing this ratio, we calculate a new + * tdm_slot width by dividing the ratio by the + * number of configured tdm slots. + */ + mcasp->slot_width = div / mcasp->tdm_slots; + if (div % mcasp->tdm_slots) + debug("%s(): BCLK/LRCLK %d is not divisible by %d tdm slots", + __func__, div, mcasp->tdm_slots); + break; + + default: + return -EINVAL; + } + return 0; +} + +static int davinci_mcasp_calc_clk_div(struct mcasp_priv *mcasp, + unsigned int sysclk_freq, + unsigned int bclk_freq, bool set) +{ + u32 reg = mcasp_get_reg(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG); + int div = sysclk_freq / bclk_freq; + int rem = sysclk_freq % bclk_freq; + int error_ppm; + int aux_div = 1; + + if (div > (ACLKXDIV_MASK + 1)) { + if (reg & AHCLKXE) { + aux_div = div / (ACLKXDIV_MASK + 1); + if (div % (ACLKXDIV_MASK + 1)) + aux_div++; + + sysclk_freq /= aux_div; + div = sysclk_freq / bclk_freq; + rem = sysclk_freq % bclk_freq; + } else if (set) { + debug("Too fast reference clock (%u)\n", + sysclk_freq); + } + } + + if (rem != 0) { + if (div == 0 || + ((sysclk_freq / div) - bclk_freq) > + (bclk_freq - (sysclk_freq / (div + 1)))) { + div++; + rem = rem - bclk_freq; + } + } + error_ppm = (div * 1000000 + (int)div64_long(1000000LL * rem, + (int)bclk_freq)) / div - 1000000; + + if (set) { + if (error_ppm) + debug("Sample-rate is off by %d PPM\n", + error_ppm); + + __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_BCLK, div, 0); + if (reg & AHCLKXE) + __davinci_mcasp_set_clkdiv(mcasp, MCASP_CLKDIV_AUXCLK, + aux_div, 0); + } + + return error_ppm; +} + +static int davinci_mcasp_hw_params(struct mcasp_priv *mcasp) +{ + int word_length; + int channels = mcasp->pdata->channels; + int period_size = 1000; + int ret; + int params_format = 2; + + switch (params_format) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + word_length = 8; + break; + + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + word_length = 16; + break; + + case SNDRV_PCM_FORMAT_U24_3LE: + case SNDRV_PCM_FORMAT_S24_3LE: + word_length = 24; + break; + + case SNDRV_PCM_FORMAT_U24_LE: + case SNDRV_PCM_FORMAT_S24_LE: + word_length = 24; + break; + + case SNDRV_PCM_FORMAT_U32_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_length = 32; + break; + + default: + debug("davinci-mcasp: unsupported PCM format\n"); + return -EINVAL; + } + + ret = davinci_mcasp_set_dai_fmt(mcasp, mcasp->dai_fmt); + if (ret) + return ret; + + /* + * If mcasp is BCLK master, and a BCLK divider was not provided by + * the machine driver, we need to calculate the ratio. + */ + if (mcasp->bclk_master && mcasp->bclk_div == 0 && mcasp->sysclk_freq) { + int slots = mcasp->tdm_slots; + // int rate = params_rate(params); + // int sbits = params_width(params); + int rate = mcasp->pdata->samplingrate; + int sbits = mcasp->pdata->bitspersample; + unsigned int bclk_target; + + if (mcasp->slot_width) + sbits = mcasp->slot_width; + + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) + bclk_target = rate * sbits * slots; + else + bclk_target = rate * 128; + + davinci_mcasp_calc_clk_div(mcasp, mcasp->sysclk_freq, + bclk_target, true); + } + + /* 0 is put in place of stream, because we only support transmission */ + ret = mcasp_common_hw_param(mcasp, 0, + period_size * channels, channels); + if (ret) + return ret; + + ret = mcasp_i2s_hw_param(mcasp, 0, + channels); + + if (ret) + return ret; + + davinci_config_channel_size(mcasp, word_length); + + if (mcasp->op_mode == DAVINCI_MCASP_IIS_MODE) { + mcasp->channels = channels; + if (!mcasp->max_format_width) + mcasp->max_format_width = word_length; + } + + return 0; +} + +static int davinci_mcasp_set_sysclk(struct mcasp_priv *mcasp, int clk_id, + unsigned int freq, int dir) +{ + if (dir == SND_SOC_CLOCK_IN) { + switch (clk_id) { + case MCASP_CLK_HCLK_AHCLK: + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + mcasp_clr_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + clear_bit_pdir(PIN_BIT_AHCLKX, &mcasp->pdir); + break; + case MCASP_CLK_HCLK_AUXCLK: + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + set_bit_pdir(PIN_BIT_AHCLKX, &mcasp->pdir); + break; + default: + debug("Invalid clk id: %d\n", clk_id); + goto out; + } + } else { + /* Select AUXCLK as HCLK */ + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXE); + mcasp_set_bits(mcasp, DAVINCI_MCASP_AHCLKRCTL_REG, AHCLKRE); + set_bit_pdir(PIN_BIT_AHCLKX, &mcasp->pdir); + } + /* + * When AHCLK X/R is selected to be output it means that the HCLK is + * the same clock - coming via AUXCLK. + */ + mcasp->sysclk_freq = freq; +out: + return 0; +} + +static void mcasp_start_tx(struct mcasp_priv *mcasp) +{ + if (mcasp->txnumevt) { /* enable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + mcasp_set_bits(mcasp, reg, FIFO_ENABLE); + } + + /* Start clocks */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); + mcasp_set_clk_pdir(mcasp, true); + + // Disabling DMA req + mcasp_set_bits(mcasp, DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS); +} + +static void mcasp_stop_tx(struct mcasp_priv *mcasp) +{ + u32 val = 0; + /* + * In synchronous mode keep TX clocks running if the capture stream is + * still running. + */ + if (mcasp_is_synchronous(mcasp) && mcasp->streams) + val = TXHCLKRST | TXCLKRST | TXFSRST; + else + mcasp_set_clk_pdir(mcasp, false); + + mcasp_set_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, val); + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + + if (mcasp->txnumevt) { /* disable FIFO */ + u32 reg = mcasp->fifo_base + MCASP_WFIFOCTL_OFFSET; + + mcasp_clr_bits(mcasp, reg, FIFO_ENABLE); + } + + mcasp_set_axr_pdir(mcasp, false); +} + +int i2s_transfer_data(struct mcasp_priv *mcasp, void *data, uint data_size) +{ + u32 *ptr; + + ptr = data; + + if (data_size < MCASP_MAX_AFIFO_DEPTH) { + debug("%s : Invalid data size\n", __func__); + return -ENODATA; + } + + /* Activate serializer(s) */ + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG, 0xFF); + + int error_cnt = 0; +stage_A: + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR); + + //Are serializers active + if (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_GBLCTLX_REG) & BIT(10))) { + debug("%s : serializer not active\n", __func__); + return -EINVAL; + } + + //is transmit slot 0 active in polling mode + if (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_TXTDMSLOT_REG) & BIT(0))) + printf("not active in polling mode\n"); + + //is XDATA set + if (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & XRDATA)) { + printf("XDATA is not 1\n"); + error_cnt++; + if (error_cnt > 10) { + debug("%s : XDATA is not set\n", __func__); + return -EINVAL; + } + goto stage_A; + } + + //getting the data + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXBUF_REG(0), *ptr++); + data_size -= sizeof(*ptr); + + /* wait for XDATA to be cleared */ + int cnt = 0; + + while ((mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & XRDATA) && + (cnt < 100000)) + cnt++; + + mcasp_set_axr_pdir(mcasp, true); + + /* Release TX state machine */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTL_REG, TXSMRST); + /* Release Frame Sync generator */ + mcasp_set_ctl_reg(mcasp, DAVINCI_MCASP_GBLCTL_REG, TXFSRST); + + while (data_size > 0) { + /*wait for XDATA to be set*/ + int cnt = 0; + + while (!(mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & XRDATA) && cnt < 1000000) + cnt++; + + for (int i = 0, j = 0; + i < mcasp->num_serializer && j < mcasp->active_serializers[0]; i++) { + if ((mcasp_get_reg(mcasp, DAVINCI_MCASP_XRSRCTL_REG(i)) & BIT(4)) && + (mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & BIT(5))) { + mcasp_set_reg(mcasp, DAVINCI_MCASP_TXBUF_REG(i), *ptr++); + data_size -= sizeof(*ptr); + j++; + } + } + + if ((mcasp_get_reg(mcasp, DAVINCI_MCASP_TXSTAT_REG) & BIT(0))) { + debug("Underrun fault has occurred\n"); + return -EINVAL; + } + } + + return 0; +} + +static int mcasp_i2s_tx_data(struct udevice *dev, void *data, uint data_size) +{ + struct mcasp_priv *mcasp = dev_get_priv(dev); + + mcasp_start_tx(mcasp); + + /*sound play*/ + + int ret = i2s_transfer_data(mcasp, data, data_size); + + if (ret) + printf("no audio playback"); + + mcasp_stop_tx(mcasp); + + return 0; +} + +static int mcasp_i2s_probe(struct udevice *dev) +{ + struct i2s_uc_priv *priv = dev_get_uclass_priv(dev); + struct mcasp_priv *mcasp = dev_get_priv(dev); + int ret; + + mcasp->dev = dev; + mcasp->pdata = priv; + mcasp->base = dev_remap_addr_name(dev, "mpu"); + mcasp->version = MCASP_VERSION_3; + ret = davinci_mcasp_get_config(mcasp); + if (ret) { + printf("error in get_config\n"); + goto err; + } + /* All pins as McASP*/ + mcasp_set_reg(mcasp, DAVINCI_MCASP_PFUNC_REG, 0x00000000); + + //FIFO base address + mcasp->fifo_base = DAVINCI_MCASP_V3_AFIFO_BASE; + + ret = davinci_mcasp_set_sysclk(mcasp, priv->id, priv->audio_pll_clk, SND_SOC_CLOCK_IN); + if (ret) { + printf("error in set_sysclk\n"); + goto err; + } + int fmt = SND_SOC_DAIFMT_BC_FC | SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + + ret = davinci_mcasp_set_dai_fmt(mcasp, fmt); + if (ret) { + printf("error in set_dai_fmt\n"); + goto err; + } + + ret = davinci_mcasp_hw_params(mcasp); + if (ret) { + printf("error in hw_params\n"); + goto err; + } + + return 0; + +err: + printf("error in probe\n"); + return ret; +} + +static const struct i2s_ops mcasp_i2s_ops = { + .tx_data = mcasp_i2s_tx_data, +}; + +static const struct udevice_id mcasp_i2s_ids[] = { + { .compatible = "ti,am33xx-mcasp-audio" }, + { } +}; + +U_BOOT_DRIVER(mcasp_i2s) = { + .name = "mcasp_i2s", + .id = UCLASS_I2S, + .of_match = mcasp_i2s_ids, + .probe = mcasp_i2s_probe, + .of_to_plat = mcasp_i2s_of_to_plat, + .ops = &mcasp_i2s_ops, + .priv_auto = sizeof(struct mcasp_priv) +};

Update the am625-sk-u-boot device tree to incorporate the sound card and sound drivers: MCASP and TLV320AIC3106 Codec
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- arch/arm/dts/k3-am625-sk-u-boot.dtsi | 85 ++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+)
diff --git a/arch/arm/dts/k3-am625-sk-u-boot.dtsi b/arch/arm/dts/k3-am625-sk-u-boot.dtsi index fa778b0ff4..74c273d940 100644 --- a/arch/arm/dts/k3-am625-sk-u-boot.dtsi +++ b/arch/arm/dts/k3-am625-sk-u-boot.dtsi @@ -10,6 +10,91 @@ chosen { tick-timer = &main_timer0; }; + + tlv320_mclk: clk-0 { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <12288000>; + }; + + codec_audio: sound { + compatible = "simple-audio-card"; + ti,codec = <&tlv320aic3106>; + simple-audio-card,name = "AM62x-SKEVM"; + simple-audio-card,widgets = + "Headphone", "Headphone Jack", + "Line", "Line In", + "Microphone", "Microphone Jack"; + simple-audio-card,routing = + "Headphone Jack", "HPLOUT", + "Headphone Jack", "HPROUT", + "LINE1L", "Line In", + "LINE1R", "Line In", + "MIC3R", "Microphone Jack", + "Microphone Jack", "Mic Bias"; + simple-audio-card,format = "dsp_b"; + simple-audio-card,bitclock-master = <&sound_master>; + simple-audio-card,frame-master = <&sound_master>; + simple-audio-card,bitclock-inversion; + + simple-audio-card,cpu { + sound-dai = <&mcasp1>; + }; + + sound_master: simple-audio-card,codec { + sound-dai = <&tlv320aic3106>; + clocks = <&tlv320_mclk>; + }; + }; +}; + +&mcasp1 { + status = "okay"; + #sound-dai-cells = <0>; + + pinctrl-names = "default"; + pinctrl-0 = <&main_mcasp1_pins_default>; + + op-mode = <0>; /* MCASP_IIS_MODE */ + tdm-slots = <2>; + + ti,i2s-epll-clock-frequency = <96000000>; + ti,i2s-sampling-rate = <48000>; + ti,i2s-bits-per-sample = <16>; + ti,i2s-channels = <2>; + ti,i2s-lr-clk-framesize = <256>; + ti,i2s-bit-clk-framesize = <32>; + ti,i2s-id = <0>; + + serial-dir = < + 1 0 2 0 + 0 0 0 0 + 0 0 0 0 + 0 0 0 0 + >; /* 0: INACTIVE, 1: TX, 2: RX */ + tx-num-evt = <0>; + rx-num-evt = <0>; +}; + +&main_i2c1 { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&main_i2c1_pins_default>; + clock-frequency = <100000>; + + tlv320aic3106: audio-codec@1b { + #sound-dai-cells = <0>; + compatible = "ti,tlv320aic3106"; + reg = <0x1b>; + ai3x-micbias-vg = <1>; /* 2.0V */ + ai3x-ocmv = <1>; + + /* Regulators */ + AVDD-supply = <&vcc_3v3_sys>; + IOVDD-supply = <&vcc_3v3_sys>; + DRVDD-supply = <&vcc_3v3_sys>; + DVDD-supply = <&vcc_1v8>; + }; };
&main_timer0 {

On Mon, Jul 08, 2024 at 01:22:05PM +0530, Scaria Kochidanadu wrote:
Update the am625-sk-u-boot device tree to incorporate the sound card and sound drivers: MCASP and TLV320AIC3106 Codec
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com
arch/arm/dts/k3-am625-sk-u-boot.dtsi | 85 ++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+)
Where's all of this in the upstream dts?

Add the tlv320aic3106 codec and sound driver in the defconfig, and enable the sound commands in Uboot. Also enable the I2C communication for transfer to and from the codec.
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- configs/am62x_evm_a53_defconfig | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/configs/am62x_evm_a53_defconfig b/configs/am62x_evm_a53_defconfig index 6c708dcb05..858946d7fc 100644 --- a/configs/am62x_evm_a53_defconfig +++ b/configs/am62x_evm_a53_defconfig @@ -51,8 +51,10 @@ CONFIG_SYS_SPI_U_BOOT_OFFS=0x280000 CONFIG_SPL_YMODEM_SUPPORT=y CONFIG_CMD_BOOTEFI_SELFTEST=y CONFIG_CMD_NVEDIT_EFI=y +CONFIG_CMD_I2C=y CONFIG_CMD_MMC=y CONFIG_CMD_EFIDEBUG=y +CONFIG_CMD_SOUND=y CONFIG_MMC_SPEED_MODE_SET=y CONFIG_OF_CONTROL=y CONFIG_SPL_OF_CONTROL=y @@ -71,6 +73,8 @@ CONFIG_CLK_TI_SCI=y CONFIG_DMA_CHANNELS=y CONFIG_TI_K3_NAVSS_UDMA=y CONFIG_TI_SCI_PROTOCOL=y +CONFIG_DM_I2C=y +CONFIG_SYS_I2C_OMAP24XX=y CONFIG_DM_MAILBOX=y CONFIG_K3_SEC_PROXY=y CONFIG_SUPPORT_EMMC_BOOT=y @@ -100,6 +104,11 @@ CONFIG_RESET_TI_SCI=y CONFIG_DM_RTC=y CONFIG_RTC_EMULATION=y CONFIG_DM_SERIAL=y +CONFIG_SOUND=y +CONFIG_I2S=y +CONFIG_SOUND_RT5677=y +CONFIG_I2S_TI=y +CONFIG_SOUND_TLV320AIC3106=y CONFIG_SOC_DEVICE=y CONFIG_SOC_DEVICE_TI_K3=y CONFIG_SOC_TI=y

Add function macro for_each_set_bit_from(), an extension to the already existing function macro for_each_set_bit() to iterate through set bits of a variable from a given bit index as required by the MCASP Driver.
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com --- include/linux/bitmap.h | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/include/linux/bitmap.h b/include/linux/bitmap.h index 0a8503af9f..da3aa428b6 100644 --- a/include/linux/bitmap.h +++ b/include/linux/bitmap.h @@ -159,6 +159,11 @@ static inline unsigned long find_first_bit(const unsigned long *addr, unsigned l (bit) < (size); \ (bit) = find_next_bit((addr), (size), (bit) + 1))
+#define for_each_set_bit_from(_bit, _addr, _size) \ + for ((_bit) = find_next_bit((_addr), (_size), (_bit)); \ + (_bit) < (_size); \ + (_bit) = find_next_bit((_addr), (_size), (_bit) + 1)) + static inline unsigned long bitmap_find_next_zero_area(unsigned long *map, unsigned long size,

On Mon, Jul 08, 2024 at 01:22:07PM +0530, Scaria Kochidanadu wrote:
Add function macro for_each_set_bit_from(), an extension to the already existing function macro for_each_set_bit() to iterate through set bits of a variable from a given bit index as required by the MCASP Driver.
Signed-off-by: Scaria Kochidanadu s-kochidanadu@ti.com
include/linux/bitmap.h | 5 +++++ 1 file changed, 5 insertions(+)
Please note the Linux Kernel commit (or tag, really) you're backporting this function from and emphasize that, not what you're using it for, thanks.
participants (3)
-
Nishanth Menon
-
Scaria Kochidanadu
-
Tom Rini