[PATCH v5 0/4] Rockchip I2S/TDM controller
Hello,
this is version 5 of the I2S/TDM driver patchset. A big thanks to everyone who has provided their valuable feedback so far.
Changes in v5: driver: - change comment style of the first comment to C++ style - make refcount non-atomic, as it's only ever used inside a spinlock - use newer SND_SOC_DAIFMT_CB* defines - change ternary statements to if/else conditions - make _clk_compensation_put return 1 if clock changed - implement set_bclk_ratio callback - always set half frame sync mode in TDM mode - automatically enable mclk-calibrate mode when the clocks for it are specified in the device tree bindings: - add Reviewed-by: Rob Herring - drop rockchip,frame-width property (done by set_bclk_ratio) - drop rockchip,fsync-half-frame property - drop rockchip,mclk-calibrate property dts: - drop empty codec block from Quartz64 device tree
Changes in v4: driver: - factor TDE/RDE enable/disable into their own inlined functions - add an RDE disable in a location where it looks like it was forgotten (rxctrl else), judging by corresponding TDE code - remove parentheses around CLK_PPM_MIN/MAX values - wording + titlecasing in the clock compensation control - use if statement in precious_reg instead - refactor rockchip_i2s_io_multiplex to have the switch statements in a function call to make the function less unwieldy - get rid of IS_ERR checks around clk enable/disable calls where already checked before by the probe - reworded some error message strings - fix potential deadlock in txrxctrl found by Sugar Zhang using spin_lock_irqsave - fix potential deadlock in trcm_mode found by Sugar Zhang using spin_lock_irqsave - use devm_platform_get_and_ioremap_resource in probe - only set DMA things if controller has capture/playback ability. Did not move this into init_dai because I'd then need to pass in the res and probe it earlier in the function, and it's also used elsewhere in the probe function - use _get_optional_exclusive for reset controls, as some controllers only have capture or playback capability bindings: - remove status = "okay" since that's the default - change the path configs to be an enum - rename "foo" to "bus" - make resets optional as controller may lack either playback or capture capability, and therefore also doesn't have a reset. At least one reset is still required, because a controller with no playback and no capture is not very useful
Changes in v3: driver: - alphabetically sort includes - check pm_runtime_get_sync return value, act on it - remove unnecessary initialisers in set_fmt - use udelay(15) in retry code: 10 retries * 15 = 150, so at worst we wait the full i2s register access delay - fix some weird returns to return directly - use __maybe_unused instead of #ifdef CONFIG_PM_SLEEP, also put __maybe_unused on the runtime callbacks - use (foo) instead of foo in header macros for precedence reasons - when using mclk-calibrate, also turn off/on those clocks during suspend and resume operations - remove mclk_tx and mclk_rx reenablement code in remove - move hclk enablement further down the probe, and disable it on probe failure - make reset controls mandatory, since the bindings state this too - use _exclusive for getting the reset controls - change reset assert/deassert delays to both be 10 usec (thank you Sugar Zhang!) - properly prepare and enable all mclks in probe, especially before calling clk_get_rate on them - if registering PCM fails, also use the cleanup error path instead of returning directly - bring back playback and capture only but in the way Sugar Zhang suggested it: set those modes depending on dma-names - rework clock enablement in general. Probe now always enables these, instead of relying on the pm resume thing - add myself to MAINTAINERS for this driver dt bindings: - fix a description still mentioning clk-trcm in the schema - document rockchip,io-multiplex, a property that describes the hardware as having multiplexed I2S GPIOs so direction needs to be changed dynamically - document rockchip,mclk-calibrate, which allows specifying different clocks for the two sample rate bases and switch between them as needed - dma-names now doesn't have a set order and items can be absent to indicate that the controller doesn't support this mode - add myself to MAINTAINERS for these bindings
Changes in v2: - remove ad-hoc writeq and needless (and broken) optimisation in reset assert/deassert. This wouldn't have worked on Big Endian, and would've been pointless on any other platform, as the overhead for saving one write was comparatively big - fix various checkpatch issues - get rid of leftover clk-trcm in schema - set status = "okay" in example in schema instead of "disabled" - change dma-names so rx is first, adjust device trees as necessary - properly reference uint32-array for rx-route and tx-route instead of uint32 - replace trcm-sync with two boolean properties, adjust DT changes accordingly and also get rid of the header file - get rid of rockchip,no-dmaengine. This was only needed for some downstream driver and shouldn't be in the DT - get rid of rockchip,capture-only/playback-only. Rationale being that I have no way to test whether they're needed, and unconditionally setting channels_min to 0 breaks everything - change hclk description in "clocks"
Nicolas Frattaroli (4): ASoC: rockchip: add support for i2s-tdm controller ASoC: dt-bindings: rockchip: add i2s-tdm bindings arm64: dts: rockchip: add i2s1 on rk356x arm64: dts: rockchip: add analog audio on Quartz64
.../bindings/sound/rockchip,i2s-tdm.yaml | 198 ++ MAINTAINERS | 7 + .../boot/dts/rockchip/rk3566-quartz64-a.dts | 31 +- arch/arm64/boot/dts/rockchip/rk356x.dtsi | 26 + sound/soc/rockchip/Kconfig | 11 + sound/soc/rockchip/Makefile | 2 + sound/soc/rockchip/rockchip_i2s_tdm.c | 1848 +++++++++++++++++ sound/soc/rockchip/rockchip_i2s_tdm.h | 398 ++++ 8 files changed, 2520 insertions(+), 1 deletion(-) create mode 100644 Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml create mode 100644 sound/soc/rockchip/rockchip_i2s_tdm.c create mode 100644 sound/soc/rockchip/rockchip_i2s_tdm.h
This commit adds support for the rockchip i2s-tdm controller, which enables audio output on the following rockchip SoCs: - px30 - rk1808 - rk3308 - rk3566 - rk3568 - rv1126
This is a cleaned up version of the downstream vendor kernel's driver. It can be enabled through the SND_SOC_ROCKCHIP_I2S_TDM configuration option.
Signed-off-by: Nicolas Frattaroli frattaroli.nicolas@gmail.com --- MAINTAINERS | 6 + sound/soc/rockchip/Kconfig | 11 + sound/soc/rockchip/Makefile | 2 + sound/soc/rockchip/rockchip_i2s_tdm.c | 1848 +++++++++++++++++++++++++ sound/soc/rockchip/rockchip_i2s_tdm.h | 398 ++++++ 5 files changed, 2265 insertions(+) create mode 100644 sound/soc/rockchip/rockchip_i2s_tdm.c create mode 100644 sound/soc/rockchip/rockchip_i2s_tdm.h
diff --git a/MAINTAINERS b/MAINTAINERS index f58dad1a1922..106d448e660d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16115,6 +16115,12 @@ F: Documentation/ABI/*/sysfs-driver-hid-roccat* F: drivers/hid/hid-roccat* F: include/linux/hid-roccat*
+ROCKCHIP I2S TDM DRIVER +M: Nicolas Frattaroli frattaroli.nicolas@gmail.com +L: linux-rockchip@lists.infradead.org +S: Maintained +F: sound/soc/rockchip/rockchip_i2s_tdm.* + ROCKCHIP ISP V1 DRIVER M: Helen Koike helen.koike@collabora.com M: Dafna Hirschfeld dafna.hirschfeld@collabora.com diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index 053097b73e28..42f76bc0fb02 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -16,6 +16,17 @@ config SND_SOC_ROCKCHIP_I2S Rockchip I2S device. The device supports upto maximum of 8 channels each for play and record.
+config SND_SOC_ROCKCHIP_I2S_TDM + tristate "Rockchip I2S/TDM Device Driver" + depends on HAVE_CLK && SND_SOC_ROCKCHIP + select SND_SOC_GENERIC_DMAENGINE_PCM + help + Say Y or M if you want to add support for the I2S/TDM driver for + Rockchip I2S/TDM devices, found in Rockchip SoCs. These devices + interface between the AHB bus and the I2S bus, and support up to a + maximum of 8 channels each for playback and recording. + + config SND_SOC_ROCKCHIP_PDM tristate "Rockchip PDM Controller Driver" depends on HAVE_CLK && SND_SOC_ROCKCHIP diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index 65e814d46006..b10f5e7b136d 100644 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -1,11 +1,13 @@ # SPDX-License-Identifier: GPL-2.0 # ROCKCHIP Platform Support snd-soc-rockchip-i2s-objs := rockchip_i2s.o +snd-soc-rockchip-i2s-tdm-objs := rockchip_i2s_tdm.o snd-soc-rockchip-pcm-objs := rockchip_pcm.o snd-soc-rockchip-pdm-objs := rockchip_pdm.o snd-soc-rockchip-spdif-objs := rockchip_spdif.o
obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S) += snd-soc-rockchip-i2s.o snd-soc-rockchip-pcm.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_I2S_TDM) += snd-soc-rockchip-i2s-tdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o
diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c new file mode 100644 index 000000000000..b08b15071993 --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -0,0 +1,1848 @@ +// SPDX-License-Identifier: GPL-2.0-only +// ALSA SoC Audio Layer - Rockchip I2S/TDM Controller driver + +// Copyright (c) 2018 Rockchip Electronics Co. Ltd. +// Author: Sugar Zhang sugar.zhang@rock-chips.com +// Author: Nicolas Frattaroli frattaroli.nicolas@gmail.com + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/spinlock.h> +#include <sound/dmaengine_pcm.h> +#include <sound/pcm_params.h> + +#include "rockchip_i2s_tdm.h" + +#define DRV_NAME "rockchip-i2s-tdm" + +#define DEFAULT_MCLK_FS 256 +#define CH_GRP_MAX 4 /* The max channel 8 / 2 */ +#define MULTIPLEX_CH_MAX 10 +#define CLK_PPM_MIN -1000 +#define CLK_PPM_MAX 1000 + +#define TRCM_TXRX 0 +#define TRCM_TX 1 +#define TRCM_RX 2 + +struct txrx_config { + u32 addr; + u32 reg; + u32 txonly; + u32 rxonly; +}; + +struct rk_i2s_soc_data { + u32 softrst_offset; + u32 grf_reg_offset; + u32 grf_shift; + int config_count; + const struct txrx_config *configs; + int (*init)(struct device *dev, u32 addr); +}; + +struct rk_i2s_tdm_dev { + struct device *dev; + struct clk *hclk; + struct clk *mclk_tx; + struct clk *mclk_rx; + /* The mclk_tx_src is parent of mclk_tx */ + struct clk *mclk_tx_src; + /* The mclk_rx_src is parent of mclk_rx */ + struct clk *mclk_rx_src; + /* + * The mclk_root0 and mclk_root1 are root parent and supplies for + * the different FS. + * + * e.g: + * mclk_root0 is VPLL0, used for FS=48000Hz + * mclk_root1 is VPLL1, used for FS=44100Hz + */ + struct clk *mclk_root0; + struct clk *mclk_root1; + struct regmap *regmap; + struct regmap *grf; + struct snd_dmaengine_dai_dma_data capture_dma_data; + struct snd_dmaengine_dai_dma_data playback_dma_data; + struct reset_control *tx_reset; + struct reset_control *rx_reset; + struct rk_i2s_soc_data *soc_data; + void __iomem *cru_base; + bool is_master_mode; + bool io_multiplex; + bool mclk_calibrate; + bool tdm_mode; + unsigned int mclk_rx_freq; + unsigned int mclk_tx_freq; + unsigned int mclk_root0_freq; + unsigned int mclk_root1_freq; + unsigned int mclk_root0_initial_freq; + unsigned int mclk_root1_initial_freq; + unsigned int frame_width; + unsigned int clk_trcm; + unsigned int i2s_sdis[CH_GRP_MAX]; + unsigned int i2s_sdos[CH_GRP_MAX]; + int clk_ppm; + int tx_reset_id; + int rx_reset_id; + int refcount; + spinlock_t lock; /* xfer lock */ + bool has_playback; + bool has_capture; +}; + +static int to_ch_num(unsigned int val) +{ + switch (val) { + case I2S_CHN_4: + return 4; + case I2S_CHN_6: + return 6; + case I2S_CHN_8: + return 8; + default: + return 2; + } +} + +static void i2s_tdm_disable_unprepare_mclk(struct rk_i2s_tdm_dev *i2s_tdm) +{ + clk_disable_unprepare(i2s_tdm->mclk_tx); + clk_disable_unprepare(i2s_tdm->mclk_rx); + if (i2s_tdm->mclk_calibrate) { + clk_disable_unprepare(i2s_tdm->mclk_tx_src); + clk_disable_unprepare(i2s_tdm->mclk_rx_src); + clk_disable_unprepare(i2s_tdm->mclk_root0); + clk_disable_unprepare(i2s_tdm->mclk_root1); + } +} + +/** + * i2s_tdm_prepare_enable_mclk - prepare to enable all mclks, disable them on + * failure. + * @i2s_tdm: rk_i2s_tdm_dev struct + * + * This function attempts to enable all mclk clocks, but cleans up after + * itself on failure. Guarantees to balance its calls. + * + * Returns success (0) or negative errno. + */ +static int i2s_tdm_prepare_enable_mclk(struct rk_i2s_tdm_dev *i2s_tdm) +{ + int ret = 0; + + ret = clk_prepare_enable(i2s_tdm->mclk_tx); + if (ret) + goto err_mclk_tx; + ret = clk_prepare_enable(i2s_tdm->mclk_rx); + if (ret) + goto err_mclk_rx; + if (i2s_tdm->mclk_calibrate) { + ret = clk_prepare_enable(i2s_tdm->mclk_tx_src); + if (ret) + goto err_mclk_rx; + ret = clk_prepare_enable(i2s_tdm->mclk_rx_src); + if (ret) + goto err_mclk_rx_src; + ret = clk_prepare_enable(i2s_tdm->mclk_root0); + if (ret) + goto err_mclk_root0; + ret = clk_prepare_enable(i2s_tdm->mclk_root1); + if (ret) + goto err_mclk_root1; + } + + return 0; + +err_mclk_root1: + clk_disable_unprepare(i2s_tdm->mclk_root0); +err_mclk_root0: + clk_disable_unprepare(i2s_tdm->mclk_rx_src); +err_mclk_rx_src: + clk_disable_unprepare(i2s_tdm->mclk_tx_src); +err_mclk_rx: + clk_disable_unprepare(i2s_tdm->mclk_tx); +err_mclk_tx: + return ret; +} + +static int __maybe_unused i2s_tdm_runtime_suspend(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + regcache_cache_only(i2s_tdm->regmap, true); + i2s_tdm_disable_unprepare_mclk(i2s_tdm); + + clk_disable_unprepare(i2s_tdm->hclk); + + return 0; +} + +static int __maybe_unused i2s_tdm_runtime_resume(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(i2s_tdm->hclk); + if (ret) + goto err_hclk; + + ret = i2s_tdm_prepare_enable_mclk(i2s_tdm); + if (ret) + goto err_mclk; + + regcache_cache_only(i2s_tdm->regmap, false); + regcache_mark_dirty(i2s_tdm->regmap); + + ret = regcache_sync(i2s_tdm->regmap); + if (ret) + goto err_regcache; + + return 0; + +err_regcache: + i2s_tdm_disable_unprepare_mclk(i2s_tdm); +err_mclk: + clk_disable_unprepare(i2s_tdm->hclk); +err_hclk: + return ret; +} + +static inline struct rk_i2s_tdm_dev *to_info(struct snd_soc_dai *dai) +{ + return snd_soc_dai_get_drvdata(dai); +} + +static void rockchip_snd_xfer_reset_assert(struct rk_i2s_tdm_dev *i2s_tdm, + int tx_bank, int tx_offset, + int rx_bank, int rx_offset) +{ + void __iomem *cru_reset; + unsigned long flags; + + cru_reset = i2s_tdm->cru_base + i2s_tdm->soc_data->softrst_offset; + + if (tx_bank == rx_bank) { + writel(BIT(tx_offset) | BIT(rx_offset) | + (BIT(tx_offset) << 16) | (BIT(rx_offset) << 16), + cru_reset + (tx_bank * 4)); + } else { + local_irq_save(flags); + writel(BIT(tx_offset) | (BIT(tx_offset) << 16), + cru_reset + (tx_bank * 4)); + writel(BIT(rx_offset) | (BIT(rx_offset) << 16), + cru_reset + (rx_bank * 4)); + local_irq_restore(flags); + } +} + +static void rockchip_snd_xfer_reset_deassert(struct rk_i2s_tdm_dev *i2s_tdm, + int tx_bank, int tx_offset, + int rx_bank, int rx_offset) +{ + void __iomem *cru_reset; + unsigned long flags; + + cru_reset = i2s_tdm->cru_base + i2s_tdm->soc_data->softrst_offset; + + if (tx_bank == rx_bank) { + writel((BIT(tx_offset) << 16) | (BIT(rx_offset) << 16), + cru_reset + (tx_bank * 4)); + } else { + local_irq_save(flags); + writel((BIT(tx_offset) << 16), + cru_reset + (tx_bank * 4)); + writel((BIT(rx_offset) << 16), + cru_reset + (rx_bank * 4)); + local_irq_restore(flags); + } +} + +/* + * Makes sure that both tx and rx are reset at the same time to sync lrck + * when clk_trcm > 0. + */ +static void rockchip_snd_xfer_sync_reset(struct rk_i2s_tdm_dev *i2s_tdm) +{ + int tx_id, rx_id; + int tx_bank, rx_bank, tx_offset, rx_offset; + + if (!i2s_tdm->cru_base || !i2s_tdm->soc_data) + return; + + tx_id = i2s_tdm->tx_reset_id; + rx_id = i2s_tdm->rx_reset_id; + if (tx_id < 0 || rx_id < 0) + return; + + tx_bank = tx_id / 16; + tx_offset = tx_id % 16; + rx_bank = rx_id / 16; + rx_offset = rx_id % 16; + dev_dbg(i2s_tdm->dev, + "tx_bank: %d, rx_bank: %d, tx_offset: %d, rx_offset: %d\n", + tx_bank, rx_bank, tx_offset, rx_offset); + + rockchip_snd_xfer_reset_assert(i2s_tdm, tx_bank, tx_offset, + rx_bank, rx_offset); + + udelay(10); + + rockchip_snd_xfer_reset_deassert(i2s_tdm, tx_bank, tx_offset, + rx_bank, rx_offset); + udelay(10); +} + +static void rockchip_snd_reset(struct reset_control *rc) +{ + reset_control_assert(rc); + udelay(10); + reset_control_deassert(rc); + udelay(10); +} + +static void rockchip_snd_xfer_clear(struct rk_i2s_tdm_dev *i2s_tdm, + unsigned int clr) +{ + unsigned int xfer_mask = 0; + unsigned int xfer_val = 0; + unsigned int val; + int retry = 10; + bool tx = clr & I2S_CLR_TXC; + bool rx = clr & I2S_CLR_RXC; + + if (!(rx || tx)) + return; + + if (tx) { + xfer_mask = I2S_XFER_TXS_START; + xfer_val = I2S_XFER_TXS_STOP; + } + if (rx) { + xfer_mask |= I2S_XFER_RXS_START; + xfer_val |= I2S_XFER_RXS_STOP; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, xfer_mask, xfer_val); + udelay(150); + regmap_update_bits(i2s_tdm->regmap, I2S_CLR, clr, clr); + + regmap_read(i2s_tdm->regmap, I2S_CLR, &val); + /* Wait on the clear operation to finish */ + while (val) { + udelay(15); + regmap_read(i2s_tdm->regmap, I2S_CLR, &val); + retry--; + if (!retry) { + dev_warn(i2s_tdm->dev, "clear failed, reset %s%s\n", + tx ? "tx" : "", rx ? "rx" : ""); + if (rx && tx) + rockchip_snd_xfer_sync_reset(i2s_tdm); + else if (tx) + rockchip_snd_reset(i2s_tdm->tx_reset); + else if (rx) + rockchip_snd_reset(i2s_tdm->rx_reset); + break; + } + } +} + +static inline void rockchip_enable_tde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_TDE_ENABLE, + I2S_DMACR_TDE_ENABLE); +} + +static inline void rockchip_disable_tde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_TDE_ENABLE, + I2S_DMACR_TDE_DISABLE); +} + +static inline void rockchip_enable_rde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_RDE_ENABLE, + I2S_DMACR_RDE_ENABLE); +} + +static inline void rockchip_disable_rde(struct regmap *regmap) +{ + regmap_update_bits(regmap, I2S_DMACR, I2S_DMACR_RDE_ENABLE, + I2S_DMACR_RDE_DISABLE); +} + +/* only used when clk_trcm > 0 */ +static void rockchip_snd_txrxctrl(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, int on) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + unsigned long flags; + + spin_lock_irqsave(&i2s_tdm->lock, flags); + if (on) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_enable_tde(i2s_tdm->regmap); + else + rockchip_enable_rde(i2s_tdm->regmap); + + if (++i2s_tdm->refcount == 1) { + rockchip_snd_xfer_sync_reset(i2s_tdm); + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); + } + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_disable_tde(i2s_tdm->regmap); + else + rockchip_disable_rde(i2s_tdm->regmap); + + if (--i2s_tdm->refcount) { + rockchip_snd_xfer_clear(i2s_tdm, + I2S_CLR_TXC | I2S_CLR_RXC); + } + } + spin_unlock_irqrestore(&i2s_tdm->lock, flags); +} + +static void rockchip_snd_txctrl(struct rk_i2s_tdm_dev *i2s_tdm, int on) +{ + if (on) { + rockchip_enable_tde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START, + I2S_XFER_TXS_START); + } else { + rockchip_disable_tde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_TXC); + } +} + +static void rockchip_snd_rxctrl(struct rk_i2s_tdm_dev *i2s_tdm, int on) +{ + if (on) { + rockchip_enable_rde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_RXS_START, + I2S_XFER_RXS_START); + } else { + rockchip_disable_rde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_RXC); + } +} + +static int rockchip_i2s_tdm_set_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(cpu_dai); + unsigned int mask, val, tdm_val, txcr_val, rxcr_val; + int ret; + bool is_tdm = i2s_tdm->tdm_mode; + + ret = pm_runtime_get_sync(cpu_dai->dev); + if (ret < 0 && ret != -EACCES) { + pm_runtime_put_noidle(cpu_dai->dev); + return ret; + } + + mask = I2S_CKR_MSS_MASK; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBC_CFC: + val = I2S_CKR_MSS_MASTER; + i2s_tdm->is_master_mode = true; + break; + case SND_SOC_DAIFMT_CBP_CFP: + val = I2S_CKR_MSS_SLAVE; + i2s_tdm->is_master_mode = false; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val); + + mask = I2S_CKR_CKP_MASK | I2S_CKR_TLP_MASK | I2S_CKR_RLP_MASK; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + val = I2S_CKR_CKP_NORMAL | + I2S_CKR_TLP_NORMAL | + I2S_CKR_RLP_NORMAL; + break; + case SND_SOC_DAIFMT_NB_IF: + val = I2S_CKR_CKP_NORMAL | + I2S_CKR_TLP_INVERTED | + I2S_CKR_RLP_INVERTED; + break; + case SND_SOC_DAIFMT_IB_NF: + val = I2S_CKR_CKP_INVERTED | + I2S_CKR_TLP_NORMAL | + I2S_CKR_RLP_NORMAL; + break; + case SND_SOC_DAIFMT_IB_IF: + val = I2S_CKR_CKP_INVERTED | + I2S_CKR_TLP_INVERTED | + I2S_CKR_RLP_INVERTED; + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, mask, val); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + txcr_val = I2S_TXCR_IBM_RSJM; + rxcr_val = I2S_RXCR_IBM_RSJM; + break; + case SND_SOC_DAIFMT_LEFT_J: + txcr_val = I2S_TXCR_IBM_LSJM; + rxcr_val = I2S_RXCR_IBM_LSJM; + break; + case SND_SOC_DAIFMT_I2S: + txcr_val = I2S_TXCR_IBM_NORMAL; + rxcr_val = I2S_RXCR_IBM_NORMAL; + break; + case SND_SOC_DAIFMT_DSP_A: /* PCM no delay mode */ + txcr_val = I2S_TXCR_TFS_PCM; + rxcr_val = I2S_RXCR_TFS_PCM; + break; + case SND_SOC_DAIFMT_DSP_B: /* PCM delay 1 mode */ + txcr_val = I2S_TXCR_TFS_PCM | I2S_TXCR_PBM_MODE(1); + rxcr_val = I2S_RXCR_TFS_PCM | I2S_RXCR_PBM_MODE(1); + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + mask = I2S_TXCR_IBM_MASK | I2S_TXCR_TFS_MASK | I2S_TXCR_PBM_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, mask, txcr_val); + + mask = I2S_RXCR_IBM_MASK | I2S_RXCR_TFS_MASK | I2S_RXCR_PBM_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, mask, rxcr_val); + + if (is_tdm) { + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(2); + break; + case SND_SOC_DAIFMT_LEFT_J: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(1); + break; + case SND_SOC_DAIFMT_I2S: + val = I2S_TXCR_TFS_TDM_I2S; + tdm_val = TDM_SHIFT_CTRL(0); + break; + case SND_SOC_DAIFMT_DSP_A: + val = I2S_TXCR_TFS_TDM_PCM; + tdm_val = TDM_SHIFT_CTRL(0); + break; + case SND_SOC_DAIFMT_DSP_B: + val = I2S_TXCR_TFS_TDM_PCM; + tdm_val = TDM_SHIFT_CTRL(2); + break; + default: + ret = -EINVAL; + goto err_pm_put; + } + + tdm_val |= TDM_FSYNC_WIDTH_SEL1(1); + tdm_val |= TDM_FSYNC_WIDTH_HALF_FRAME; + + mask = I2S_TXCR_TFS_MASK; + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, mask, val); + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, mask, val); + + mask = TDM_FSYNC_WIDTH_SEL1_MSK | TDM_FSYNC_WIDTH_SEL0_MSK | + TDM_SHIFT_CTRL_MSK; + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_TXCR, + mask, tdm_val); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_RXCR, + mask, tdm_val); + } + +err_pm_put: + pm_runtime_put(cpu_dai->dev); + + return ret; +} + +static void rockchip_i2s_tdm_xfer_pause(struct snd_pcm_substream *substream, + struct rk_i2s_tdm_dev *i2s_tdm) +{ + int stream; + + stream = SNDRV_PCM_STREAM_LAST - substream->stream; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_disable_tde(i2s_tdm->regmap); + else + rockchip_disable_rde(i2s_tdm->regmap); + + rockchip_snd_xfer_clear(i2s_tdm, I2S_CLR_TXC | I2S_CLR_RXC); +} + +static void rockchip_i2s_tdm_xfer_resume(struct snd_pcm_substream *substream, + struct rk_i2s_tdm_dev *i2s_tdm) +{ + int stream; + + stream = SNDRV_PCM_STREAM_LAST - substream->stream; + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + rockchip_enable_tde(i2s_tdm->regmap); + else + rockchip_enable_rde(i2s_tdm->regmap); + + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START, + I2S_XFER_TXS_START | + I2S_XFER_RXS_START); +} + +static int rockchip_i2s_tdm_clk_set_rate(struct rk_i2s_tdm_dev *i2s_tdm, + struct clk *clk, unsigned long rate, + int ppm) +{ + unsigned long rate_target; + int delta, ret; + + if (ppm == i2s_tdm->clk_ppm) + return 0; + + if (ppm < 0) + delta = -1; + else + delta = 1; + + delta *= (int)div64_u64((u64)rate * (u64)abs(ppm) + 500000, + 1000000); + + rate_target = rate + delta; + + if (!rate_target) + return -EINVAL; + + ret = clk_set_rate(clk, rate_target); + if (ret) + return ret; + + i2s_tdm->clk_ppm = ppm; + + return ret; +} + +static int rockchip_i2s_tdm_calibrate_mclk(struct rk_i2s_tdm_dev *i2s_tdm, + struct snd_pcm_substream *substream, + unsigned int lrck_freq) +{ + struct clk *mclk_root; + struct clk *mclk_parent; + unsigned int mclk_root_freq; + unsigned int mclk_root_initial_freq; + unsigned int mclk_parent_freq; + unsigned int div, delta; + u64 ppm; + int ret; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mclk_parent = i2s_tdm->mclk_tx_src; + else + mclk_parent = i2s_tdm->mclk_rx_src; + + switch (lrck_freq) { + case 8000: + case 16000: + case 24000: + case 32000: + case 48000: + case 64000: + case 96000: + case 192000: + mclk_root = i2s_tdm->mclk_root0; + mclk_root_freq = i2s_tdm->mclk_root0_freq; + mclk_root_initial_freq = i2s_tdm->mclk_root0_initial_freq; + mclk_parent_freq = DEFAULT_MCLK_FS * 192000; + break; + case 11025: + case 22050: + case 44100: + case 88200: + case 176400: + mclk_root = i2s_tdm->mclk_root1; + mclk_root_freq = i2s_tdm->mclk_root1_freq; + mclk_root_initial_freq = i2s_tdm->mclk_root1_initial_freq; + mclk_parent_freq = DEFAULT_MCLK_FS * 176400; + break; + default: + dev_err(i2s_tdm->dev, "Invalid LRCK frequency: %u Hz\n", + lrck_freq); + return -EINVAL; + } + + ret = clk_set_parent(mclk_parent, mclk_root); + if (ret) + return ret; + + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, mclk_root, + mclk_root_freq, 0); + if (ret) + return ret; + + delta = abs(mclk_root_freq % mclk_parent_freq - mclk_parent_freq); + ppm = div64_u64((uint64_t)delta * 1000000, (uint64_t)mclk_root_freq); + + if (ppm) { + div = DIV_ROUND_CLOSEST(mclk_root_initial_freq, mclk_parent_freq); + if (!div) + return -EINVAL; + + mclk_root_freq = mclk_parent_freq * round_up(div, 2); + + ret = clk_set_rate(mclk_root, mclk_root_freq); + if (ret) + return ret; + + i2s_tdm->mclk_root0_freq = clk_get_rate(i2s_tdm->mclk_root0); + i2s_tdm->mclk_root1_freq = clk_get_rate(i2s_tdm->mclk_root1); + } + + return clk_set_rate(mclk_parent, mclk_parent_freq); +} + +static int rockchip_i2s_tdm_set_mclk(struct rk_i2s_tdm_dev *i2s_tdm, + struct snd_pcm_substream *substream, + struct clk **mclk) +{ + unsigned int mclk_freq; + int ret; + + if (i2s_tdm->clk_trcm) { + if (i2s_tdm->mclk_tx_freq != i2s_tdm->mclk_rx_freq) { + dev_err(i2s_tdm->dev, + "clk_trcm, tx: %d and rx: %d should be the same\n", + i2s_tdm->mclk_tx_freq, + i2s_tdm->mclk_rx_freq); + return -EINVAL; + } + + ret = clk_set_rate(i2s_tdm->mclk_tx, i2s_tdm->mclk_tx_freq); + if (ret) + return ret; + + ret = clk_set_rate(i2s_tdm->mclk_rx, i2s_tdm->mclk_rx_freq); + if (ret) + return ret; + + /* mclk_rx is also ok. */ + *mclk = i2s_tdm->mclk_tx; + } else { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + *mclk = i2s_tdm->mclk_tx; + mclk_freq = i2s_tdm->mclk_tx_freq; + } else { + *mclk = i2s_tdm->mclk_rx; + mclk_freq = i2s_tdm->mclk_rx_freq; + } + + ret = clk_set_rate(*mclk, mclk_freq); + if (ret) + return ret; + } + + return 0; +} + +static int rockchip_i2s_ch_to_io(unsigned int ch, bool substream_capture) +{ + if (substream_capture) { + switch (ch) { + case I2S_CHN_4: + return I2S_IO_6CH_OUT_4CH_IN; + case I2S_CHN_6: + return I2S_IO_4CH_OUT_6CH_IN; + case I2S_CHN_8: + return I2S_IO_2CH_OUT_8CH_IN; + default: + return I2S_IO_8CH_OUT_2CH_IN; + } + } else { + switch (ch) { + case I2S_CHN_4: + return I2S_IO_4CH_OUT_6CH_IN; + case I2S_CHN_6: + return I2S_IO_6CH_OUT_4CH_IN; + case I2S_CHN_8: + return I2S_IO_8CH_OUT_2CH_IN; + default: + return I2S_IO_2CH_OUT_8CH_IN; + } + } +} + +static int rockchip_i2s_io_multiplex(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + int usable_chs = MULTIPLEX_CH_MAX; + unsigned int val = 0; + + if (!i2s_tdm->io_multiplex) + return 0; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + struct snd_pcm_str *playback_str = + &substream->pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]; + + if (playback_str->substream_opened) { + regmap_read(i2s_tdm->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + usable_chs = MULTIPLEX_CH_MAX - to_ch_num(val); + } + + regmap_read(i2s_tdm->regmap, I2S_RXCR, &val); + val &= I2S_RXCR_CSR_MASK; + + if (to_ch_num(val) > usable_chs) { + dev_err(i2s_tdm->dev, + "Capture channels (%d) > usable channels (%d)\n", + to_ch_num(val), usable_chs); + return -EINVAL; + } + + rockchip_i2s_ch_to_io(val, true); + } else { + struct snd_pcm_str *capture_str = + &substream->pcm->streams[SNDRV_PCM_STREAM_CAPTURE]; + + if (capture_str->substream_opened) { + regmap_read(i2s_tdm->regmap, I2S_RXCR, &val); + val &= I2S_RXCR_CSR_MASK; + usable_chs = MULTIPLEX_CH_MAX - to_ch_num(val); + } + + regmap_read(i2s_tdm->regmap, I2S_TXCR, &val); + val &= I2S_TXCR_CSR_MASK; + + if (to_ch_num(val) > usable_chs) { + dev_err(i2s_tdm->dev, + "Playback channels (%d) > usable channels (%d)\n", + to_ch_num(val), usable_chs); + return -EINVAL; + } + + rockchip_i2s_ch_to_io(val, false); + } + + val <<= i2s_tdm->soc_data->grf_shift; + val |= (I2S_IO_DIRECTION_MASK << i2s_tdm->soc_data->grf_shift) << 16; + regmap_write(i2s_tdm->grf, i2s_tdm->soc_data->grf_reg_offset, val); + + return 0; +} + +static int rockchip_i2s_trcm_mode(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai, + unsigned int div_bclk, + unsigned int div_lrck, + unsigned int fmt) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + unsigned long flags; + + if (!i2s_tdm->clk_trcm) + return 0; + + spin_lock_irqsave(&i2s_tdm->lock, flags); + if (i2s_tdm->refcount) + rockchip_i2s_tdm_xfer_pause(substream, i2s_tdm); + + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_TXM_MASK | I2S_CLKDIV_RXM_MASK, + I2S_CLKDIV_TXM(div_bclk) | I2S_CLKDIV_RXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_TSD_MASK | I2S_CKR_RSD_MASK, + I2S_CKR_TSD(div_lrck) | I2S_CKR_RSD(div_lrck)); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, + fmt); + else + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK | I2S_RXCR_CSR_MASK, + fmt); + + if (i2s_tdm->refcount) + rockchip_i2s_tdm_xfer_resume(substream, i2s_tdm); + spin_unlock_irqrestore(&i2s_tdm->lock, flags); + + return 0; +} + +static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + struct clk *mclk; + int ret = 0; + unsigned int val = 0; + unsigned int mclk_rate, bclk_rate, div_bclk = 4, div_lrck = 64; + + if (i2s_tdm->is_master_mode) { + if (i2s_tdm->mclk_calibrate) + rockchip_i2s_tdm_calibrate_mclk(i2s_tdm, substream, + params_rate(params)); + + ret = rockchip_i2s_tdm_set_mclk(i2s_tdm, substream, &mclk); + if (ret) + return ret; + + mclk_rate = clk_get_rate(mclk); + bclk_rate = i2s_tdm->frame_width * params_rate(params); + if (!bclk_rate) + return -EINVAL; + + div_bclk = DIV_ROUND_CLOSEST(mclk_rate, bclk_rate); + div_lrck = bclk_rate / params_rate(params); + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + val |= I2S_TXCR_VDW(8); + break; + case SNDRV_PCM_FORMAT_S16_LE: + val |= I2S_TXCR_VDW(16); + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= I2S_TXCR_VDW(20); + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= I2S_TXCR_VDW(24); + break; + case SNDRV_PCM_FORMAT_S32_LE: + val |= I2S_TXCR_VDW(32); + break; + default: + return -EINVAL; + } + + switch (params_channels(params)) { + case 8: + val |= I2S_CHN_8; + break; + case 6: + val |= I2S_CHN_6; + break; + case 4: + val |= I2S_CHN_4; + break; + case 2: + val |= I2S_CHN_2; + break; + default: + return -EINVAL; + } + + if (i2s_tdm->clk_trcm) { + rockchip_i2s_trcm_mode(substream, dai, div_bclk, div_lrck, val); + } else if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_TXM_MASK, + I2S_CLKDIV_TXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_TSD_MASK, + I2S_CKR_TSD(div_lrck)); + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_VDW_MASK | I2S_TXCR_CSR_MASK, + val); + } else { + regmap_update_bits(i2s_tdm->regmap, I2S_CLKDIV, + I2S_CLKDIV_RXM_MASK, + I2S_CLKDIV_RXM(div_bclk)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, + I2S_CKR_RSD_MASK, + I2S_CKR_RSD(div_lrck)); + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_VDW_MASK | I2S_RXCR_CSR_MASK, + val); + } + + return rockchip_i2s_io_multiplex(substream, dai); +} + +static int rockchip_i2s_tdm_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (i2s_tdm->clk_trcm) + rockchip_snd_txrxctrl(substream, dai, 1); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s_tdm, 1); + else + rockchip_snd_txctrl(i2s_tdm, 1); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (i2s_tdm->clk_trcm) + rockchip_snd_txrxctrl(substream, dai, 0); + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + rockchip_snd_rxctrl(i2s_tdm, 0); + else + rockchip_snd_txctrl(i2s_tdm, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rockchip_i2s_tdm_set_sysclk(struct snd_soc_dai *cpu_dai, int stream, + unsigned int freq, int dir) +{ + struct rk_i2s_tdm_dev *i2s_tdm = to_info(cpu_dai); + + /* Put set mclk rate into rockchip_i2s_tdm_set_mclk() */ + if (i2s_tdm->clk_trcm) { + i2s_tdm->mclk_tx_freq = freq; + i2s_tdm->mclk_rx_freq = freq; + } else { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + i2s_tdm->mclk_tx_freq = freq; + else + i2s_tdm->mclk_rx_freq = freq; + } + + dev_dbg(i2s_tdm->dev, "The target mclk_%s freq is: %d\n", + stream ? "rx" : "tx", freq); + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = 1; + uinfo->value.integer.min = CLK_PPM_MIN; + uinfo->value.integer.max = CLK_PPM_MAX; + uinfo->value.integer.step = 1; + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + ucontrol->value.integer.value[0] = i2s_tdm->clk_ppm; + + return 0; +} + +static int rockchip_i2s_tdm_clk_compensation_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dai *dai = snd_kcontrol_chip(kcontrol); + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + int ret = 0, ppm = 0; + int changed = 0; + unsigned long old_rate; + + if (ucontrol->value.integer.value[0] < CLK_PPM_MIN || + ucontrol->value.integer.value[0] > CLK_PPM_MAX) + return -EINVAL; + + ppm = ucontrol->value.integer.value[0]; + + old_rate = clk_get_rate(i2s_tdm->mclk_root0); + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root0, + i2s_tdm->mclk_root0_freq, ppm); + if (ret) + return ret; + if (old_rate != clk_get_rate(i2s_tdm->mclk_root0)) + changed = 1; + + if (clk_is_match(i2s_tdm->mclk_root0, i2s_tdm->mclk_root1)) + return changed; + + old_rate = clk_get_rate(i2s_tdm->mclk_root1); + ret = rockchip_i2s_tdm_clk_set_rate(i2s_tdm, i2s_tdm->mclk_root1, + i2s_tdm->mclk_root1_freq, ppm); + if (ret) + return ret; + if (old_rate != clk_get_rate(i2s_tdm->mclk_root1)) + changed = 1; + + return changed; +} + +static struct snd_kcontrol_new rockchip_i2s_tdm_compensation_control = { + .iface = SNDRV_CTL_ELEM_IFACE_PCM, + .name = "PCM Clock Compensation in PPM", + .info = rockchip_i2s_tdm_clk_compensation_info, + .get = rockchip_i2s_tdm_clk_compensation_get, + .put = rockchip_i2s_tdm_clk_compensation_put, +}; + +static int rockchip_i2s_tdm_dai_probe(struct snd_soc_dai *dai) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + if (i2s_tdm->has_capture) + dai->capture_dma_data = &i2s_tdm->capture_dma_data; + if (i2s_tdm->has_playback) + dai->playback_dma_data = &i2s_tdm->playback_dma_data; + + if (i2s_tdm->mclk_calibrate) + snd_soc_add_dai_controls(dai, &rockchip_i2s_tdm_compensation_control, 1); + + return 0; +} + +static int rockchip_dai_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, unsigned int rx_mask, + int slots, int slot_width) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + unsigned int mask, val; + + i2s_tdm->tdm_mode = true; + i2s_tdm->frame_width = slots * slot_width; + mask = TDM_SLOT_BIT_WIDTH_MSK | TDM_FRAME_WIDTH_MSK; + val = TDM_SLOT_BIT_WIDTH(slot_width) | + TDM_FRAME_WIDTH(slots * slot_width); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_TXCR, + mask, val); + regmap_update_bits(i2s_tdm->regmap, I2S_TDM_RXCR, + mask, val); + + return 0; +} + +static int rockchip_i2s_tdm_set_bclk_ratio(struct snd_soc_dai *dai, + unsigned int ratio) +{ + struct rk_i2s_tdm_dev *i2s_tdm = snd_soc_dai_get_drvdata(dai); + + if (ratio < 32 || ratio > 512 || ratio % 2 == 1) + return -EINVAL; + + i2s_tdm->frame_width = ratio; + + return 0; +} + +static const struct snd_soc_dai_ops rockchip_i2s_tdm_dai_ops = { + .hw_params = rockchip_i2s_tdm_hw_params, + .set_bclk_ratio = rockchip_i2s_tdm_set_bclk_ratio, + .set_sysclk = rockchip_i2s_tdm_set_sysclk, + .set_fmt = rockchip_i2s_tdm_set_fmt, + .set_tdm_slot = rockchip_dai_tdm_slot, + .trigger = rockchip_i2s_tdm_trigger, +}; + +static const struct snd_soc_component_driver rockchip_i2s_tdm_component = { + .name = DRV_NAME, +}; + +static bool rockchip_i2s_tdm_wr_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + case I2S_TDM_TXCR: + case I2S_TDM_RXCR: + case I2S_CLKDIV: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_rd_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXCR: + case I2S_RXCR: + case I2S_CKR: + case I2S_DMACR: + case I2S_INTCR: + case I2S_XFER: + case I2S_CLR: + case I2S_TXDR: + case I2S_RXDR: + case I2S_TXFIFOLR: + case I2S_INTSR: + case I2S_RXFIFOLR: + case I2S_TDM_TXCR: + case I2S_TDM_RXCR: + case I2S_CLKDIV: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case I2S_TXFIFOLR: + case I2S_INTSR: + case I2S_CLR: + case I2S_TXDR: + case I2S_RXDR: + case I2S_RXFIFOLR: + return true; + default: + return false; + } +} + +static bool rockchip_i2s_tdm_precious_reg(struct device *dev, unsigned int reg) +{ + if (reg == I2S_RXDR) + return true; + return false; +} + +static const struct reg_default rockchip_i2s_tdm_reg_defaults[] = { + {0x00, 0x7200000f}, + {0x04, 0x01c8000f}, + {0x08, 0x00001f1f}, + {0x10, 0x001f0000}, + {0x14, 0x01f00000}, + {0x30, 0x00003eff}, + {0x34, 0x00003eff}, + {0x38, 0x00000707}, +}; + +static const struct regmap_config rockchip_i2s_tdm_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = I2S_CLKDIV, + .reg_defaults = rockchip_i2s_tdm_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(rockchip_i2s_tdm_reg_defaults), + .writeable_reg = rockchip_i2s_tdm_wr_reg, + .readable_reg = rockchip_i2s_tdm_rd_reg, + .volatile_reg = rockchip_i2s_tdm_volatile_reg, + .precious_reg = rockchip_i2s_tdm_precious_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int common_soc_init(struct device *dev, u32 addr) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + const struct txrx_config *configs = i2s_tdm->soc_data->configs; + u32 reg = 0, val = 0, trcm = i2s_tdm->clk_trcm; + int i; + + if (trcm == TRCM_TXRX) + return 0; + + for (i = 0; i < i2s_tdm->soc_data->config_count; i++) { + if (addr != configs[i].addr) + continue; + reg = configs[i].reg; + if (trcm == TRCM_TX) + val = configs[i].txonly; + else + val = configs[i].rxonly; + + if (reg) + regmap_write(i2s_tdm->grf, reg, val); + } + + return 0; +} + +static const struct txrx_config px30_txrx_config[] = { + { 0xff060000, 0x184, PX30_I2S0_CLK_TXONLY, PX30_I2S0_CLK_RXONLY }, +}; + +static const struct txrx_config rk1808_txrx_config[] = { + { 0xff7e0000, 0x190, RK1808_I2S0_CLK_TXONLY, RK1808_I2S0_CLK_RXONLY }, +}; + +static const struct txrx_config rk3308_txrx_config[] = { + { 0xff300000, 0x308, RK3308_I2S0_CLK_TXONLY, RK3308_I2S0_CLK_RXONLY }, + { 0xff310000, 0x308, RK3308_I2S1_CLK_TXONLY, RK3308_I2S1_CLK_RXONLY }, +}; + +static const struct txrx_config rk3568_txrx_config[] = { + { 0xfe410000, 0x504, RK3568_I2S1_CLK_TXONLY, RK3568_I2S1_CLK_RXONLY }, + { 0xfe410000, 0x508, RK3568_I2S1_MCLK_TX_OE, RK3568_I2S1_MCLK_RX_OE }, + { 0xfe420000, 0x508, RK3568_I2S2_MCLK_OE, RK3568_I2S2_MCLK_OE }, + { 0xfe430000, 0x504, RK3568_I2S3_CLK_TXONLY, RK3568_I2S3_CLK_RXONLY }, + { 0xfe430000, 0x508, RK3568_I2S3_MCLK_TXONLY, RK3568_I2S3_MCLK_RXONLY }, + { 0xfe430000, 0x508, RK3568_I2S3_MCLK_OE, RK3568_I2S3_MCLK_OE }, +}; + +static const struct txrx_config rv1126_txrx_config[] = { + { 0xff800000, 0x10260, RV1126_I2S0_CLK_TXONLY, RV1126_I2S0_CLK_RXONLY }, +}; + +static struct rk_i2s_soc_data px30_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = px30_txrx_config, + .config_count = ARRAY_SIZE(px30_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk1808_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = rk1808_txrx_config, + .config_count = ARRAY_SIZE(rk1808_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk3308_i2s_soc_data = { + .softrst_offset = 0x0400, + .grf_reg_offset = 0x0308, + .grf_shift = 5, + .configs = rk3308_txrx_config, + .config_count = ARRAY_SIZE(rk3308_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rk3568_i2s_soc_data = { + .softrst_offset = 0x0400, + .configs = rk3568_txrx_config, + .config_count = ARRAY_SIZE(rk3568_txrx_config), + .init = common_soc_init, +}; + +static struct rk_i2s_soc_data rv1126_i2s_soc_data = { + .softrst_offset = 0x0300, + .configs = rv1126_txrx_config, + .config_count = ARRAY_SIZE(rv1126_txrx_config), + .init = common_soc_init, +}; + +static const struct of_device_id rockchip_i2s_tdm_match[] = { + { .compatible = "rockchip,px30-i2s-tdm", .data = &px30_i2s_soc_data }, + { .compatible = "rockchip,rk1808-i2s-tdm", .data = &rk1808_i2s_soc_data }, + { .compatible = "rockchip,rk3308-i2s-tdm", .data = &rk3308_i2s_soc_data }, + { .compatible = "rockchip,rk3568-i2s-tdm", .data = &rk3568_i2s_soc_data }, + { .compatible = "rockchip,rv1126-i2s-tdm", .data = &rv1126_i2s_soc_data }, + {}, +}; + +static int of_i2s_resetid_get(struct device_node *node, + const char *id) +{ + struct of_phandle_args args; + int index = 0; + int ret; + + if (id) + index = of_property_match_string(node, + "reset-names", id); + ret = of_parse_phandle_with_args(node, "resets", "#reset-cells", + index, &args); + if (ret) + return ret; + + return args.args[0]; +} + +static struct snd_soc_dai_driver i2s_tdm_dai = { + .probe = rockchip_i2s_tdm_dai_probe, + .playback = { + .stream_name = "Playback", + }, + .capture = { + .stream_name = "Capture", + }, + .ops = &rockchip_i2s_tdm_dai_ops, +}; + +static void rockchip_i2s_tdm_init_dai(struct rk_i2s_tdm_dev *i2s_tdm) +{ + struct property *dma_names; + const char *dma_name; + u64 formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE | + SNDRV_PCM_FMTBIT_S32_LE); + struct device_node *node = i2s_tdm->dev->of_node; + + of_property_for_each_string(node, "dma-names", dma_names, dma_name) { + if (!strcmp(dma_name, "tx")) + i2s_tdm->has_playback = true; + if (!strcmp(dma_name, "rx")) + i2s_tdm->has_capture = true; + } + + if (i2s_tdm->has_playback) { + i2s_tdm_dai.playback.channels_min = 2; + i2s_tdm_dai.playback.channels_max = 8; + i2s_tdm_dai.playback.rates = SNDRV_PCM_RATE_8000_192000; + i2s_tdm_dai.playback.formats = formats; + } + + if (i2s_tdm->has_capture) { + i2s_tdm_dai.capture.channels_min = 2; + i2s_tdm_dai.capture.channels_max = 8; + i2s_tdm_dai.capture.rates = SNDRV_PCM_RATE_8000_192000; + i2s_tdm_dai.capture.formats = formats; + } +} + +static int rockchip_i2s_tdm_path_check(struct rk_i2s_tdm_dev *i2s_tdm, + int num, + bool is_rx_path) +{ + unsigned int *i2s_data; + int i, j; + + if (is_rx_path) + i2s_data = i2s_tdm->i2s_sdis; + else + i2s_data = i2s_tdm->i2s_sdos; + + for (i = 0; i < num; i++) { + if (i2s_data[i] > CH_GRP_MAX - 1) { + dev_err(i2s_tdm->dev, + "%s path i2s_data[%d]: %d is too high, max is: %d\n", + is_rx_path ? "RX" : "TX", + i, i2s_data[i], CH_GRP_MAX); + return -EINVAL; + } + + for (j = 0; j < num; j++) { + if (i == j) + continue; + + if (i2s_data[i] == i2s_data[j]) { + dev_err(i2s_tdm->dev, + "%s path invalid routed i2s_data: [%d]%d == [%d]%d\n", + is_rx_path ? "RX" : "TX", + i, i2s_data[i], + j, i2s_data[j]); + return -EINVAL; + } + } + } + + return 0; +} + +static void rockchip_i2s_tdm_tx_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num) +{ + int idx; + + for (idx = 0; idx < num; idx++) { + regmap_update_bits(i2s_tdm->regmap, I2S_TXCR, + I2S_TXCR_PATH_MASK(idx), + I2S_TXCR_PATH(idx, i2s_tdm->i2s_sdos[idx])); + } +} + +static void rockchip_i2s_tdm_rx_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num) +{ + int idx; + + for (idx = 0; idx < num; idx++) { + regmap_update_bits(i2s_tdm->regmap, I2S_RXCR, + I2S_RXCR_PATH_MASK(idx), + I2S_RXCR_PATH(idx, i2s_tdm->i2s_sdis[idx])); + } +} + +static void rockchip_i2s_tdm_path_config(struct rk_i2s_tdm_dev *i2s_tdm, + int num, bool is_rx_path) +{ + if (is_rx_path) + rockchip_i2s_tdm_rx_path_config(i2s_tdm, num); + else + rockchip_i2s_tdm_tx_path_config(i2s_tdm, num); +} + +static int rockchip_i2s_tdm_get_calibrate_mclks(struct rk_i2s_tdm_dev *i2s_tdm) +{ + int num_mclks = 0; + + i2s_tdm->mclk_tx_src = devm_clk_get(i2s_tdm->dev, "mclk_tx_src"); + if (!IS_ERR(i2s_tdm->mclk_tx_src)) + num_mclks++; + + i2s_tdm->mclk_rx_src = devm_clk_get(i2s_tdm->dev, "mclk_rx_src"); + if (!IS_ERR(i2s_tdm->mclk_rx_src)) + num_mclks++; + + i2s_tdm->mclk_root0 = devm_clk_get(i2s_tdm->dev, "mclk_root0"); + if (!IS_ERR(i2s_tdm->mclk_root0)) + num_mclks++; + + i2s_tdm->mclk_root1 = devm_clk_get(i2s_tdm->dev, "mclk_root1"); + if (!IS_ERR(i2s_tdm->mclk_root1)) + num_mclks++; + + if (num_mclks < 4 && num_mclks != 0) + return -ENOENT; + + if (num_mclks == 4) + i2s_tdm->mclk_calibrate = 1; + + return 0; +} + +static int rockchip_i2s_tdm_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np, + bool is_rx_path) +{ + char *i2s_tx_path_prop = "rockchip,i2s-tx-route"; + char *i2s_rx_path_prop = "rockchip,i2s-rx-route"; + char *i2s_path_prop; + unsigned int *i2s_data; + int num, ret = 0; + + if (is_rx_path) { + i2s_path_prop = i2s_rx_path_prop; + i2s_data = i2s_tdm->i2s_sdis; + } else { + i2s_path_prop = i2s_tx_path_prop; + i2s_data = i2s_tdm->i2s_sdos; + } + + num = of_count_phandle_with_args(np, i2s_path_prop, NULL); + if (num < 0) { + if (num != -ENOENT) { + dev_err(i2s_tdm->dev, + "Failed to read '%s' num: %d\n", + i2s_path_prop, num); + ret = num; + } + return ret; + } else if (num != CH_GRP_MAX) { + dev_err(i2s_tdm->dev, + "The num: %d should be: %d\n", num, CH_GRP_MAX); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, i2s_path_prop, + i2s_data, num); + if (ret < 0) { + dev_err(i2s_tdm->dev, + "Failed to read '%s': %d\n", + i2s_path_prop, ret); + return ret; + } + + ret = rockchip_i2s_tdm_path_check(i2s_tdm, num, is_rx_path); + if (ret < 0) { + dev_err(i2s_tdm->dev, + "Failed to check i2s data bus: %d\n", ret); + return ret; + } + + rockchip_i2s_tdm_path_config(i2s_tdm, num, is_rx_path); + + return 0; +} + +static int rockchip_i2s_tdm_tx_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np) +{ + return rockchip_i2s_tdm_path_prepare(i2s_tdm, np, 0); +} + +static int rockchip_i2s_tdm_rx_path_prepare(struct rk_i2s_tdm_dev *i2s_tdm, + struct device_node *np) +{ + return rockchip_i2s_tdm_path_prepare(i2s_tdm, np, 1); +} + +static int rockchip_i2s_tdm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct device_node *cru_node; + const struct of_device_id *of_id; + struct rk_i2s_tdm_dev *i2s_tdm; + struct resource *res; + void __iomem *regs; + int ret; + + i2s_tdm = devm_kzalloc(&pdev->dev, sizeof(*i2s_tdm), GFP_KERNEL); + if (!i2s_tdm) + return -ENOMEM; + + i2s_tdm->dev = &pdev->dev; + + of_id = of_match_device(rockchip_i2s_tdm_match, &pdev->dev); + if (!of_id || !of_id->data) + return -EINVAL; + + spin_lock_init(&i2s_tdm->lock); + i2s_tdm->soc_data = (struct rk_i2s_soc_data *)of_id->data; + + rockchip_i2s_tdm_init_dai(i2s_tdm); + + i2s_tdm->frame_width = 64; + + i2s_tdm->clk_trcm = TRCM_TXRX; + if (of_property_read_bool(node, "rockchip,trcm-sync-tx-only")) + i2s_tdm->clk_trcm = TRCM_TX; + if (of_property_read_bool(node, "rockchip,trcm-sync-rx-only")) { + if (i2s_tdm->clk_trcm) { + dev_err(i2s_tdm->dev, "invalid trcm-sync configuration\n"); + return -EINVAL; + } + i2s_tdm->clk_trcm = TRCM_RX; + } + if (i2s_tdm->clk_trcm != TRCM_TXRX) + i2s_tdm_dai.symmetric_rate = 1; + + i2s_tdm->grf = syscon_regmap_lookup_by_phandle(node, "rockchip,grf"); + if (IS_ERR(i2s_tdm->grf)) + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->grf), + "Error in rockchip,grf\n"); + + if (i2s_tdm->clk_trcm != TRCM_TXRX) { + cru_node = of_parse_phandle(node, "rockchip,cru", 0); + i2s_tdm->cru_base = of_iomap(cru_node, 0); + of_node_put(cru_node); + if (!i2s_tdm->cru_base) { + dev_err(i2s_tdm->dev, + "Missing or unsupported rockchip,cru node\n"); + return -ENOENT; + } + + i2s_tdm->tx_reset_id = of_i2s_resetid_get(node, "tx-m"); + i2s_tdm->rx_reset_id = of_i2s_resetid_get(node, "rx-m"); + } + + i2s_tdm->tx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "tx-m"); + if (IS_ERR(i2s_tdm->tx_reset)) { + ret = PTR_ERR(i2s_tdm->tx_reset); + return dev_err_probe(i2s_tdm->dev, ret, + "Error in tx-m reset control\n"); + } + + i2s_tdm->rx_reset = devm_reset_control_get_optional_exclusive(&pdev->dev, + "rx-m"); + if (IS_ERR(i2s_tdm->rx_reset)) { + ret = PTR_ERR(i2s_tdm->rx_reset); + return dev_err_probe(i2s_tdm->dev, ret, + "Error in rx-m reset control\n"); + } + + i2s_tdm->hclk = devm_clk_get(&pdev->dev, "hclk"); + if (IS_ERR(i2s_tdm->hclk)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->hclk), + "Failed to get clock hclk\n"); + } + + i2s_tdm->mclk_tx = devm_clk_get(&pdev->dev, "mclk_tx"); + if (IS_ERR(i2s_tdm->mclk_tx)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->mclk_tx), + "Failed to get clock mclk_tx\n"); + } + + i2s_tdm->mclk_rx = devm_clk_get(&pdev->dev, "mclk_rx"); + if (IS_ERR(i2s_tdm->mclk_rx)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->mclk_rx), + "Failed to get clock mclk_rx\n"); + } + + i2s_tdm->io_multiplex = + of_property_read_bool(node, "rockchip,io-multiplex"); + + ret = rockchip_i2s_tdm_get_calibrate_mclks(i2s_tdm); + if (ret) + return dev_err_probe(i2s_tdm->dev, ret, + "mclk-calibrate clocks missing"); + + regs = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(regs)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(regs), + "Failed to get resource IORESOURCE_MEM\n"); + } + + i2s_tdm->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &rockchip_i2s_tdm_regmap_config); + if (IS_ERR(i2s_tdm->regmap)) { + return dev_err_probe(i2s_tdm->dev, PTR_ERR(i2s_tdm->regmap), + "Failed to initialise regmap\n"); + } + + if (i2s_tdm->has_playback) { + i2s_tdm->playback_dma_data.addr = res->start + I2S_TXDR; + i2s_tdm->playback_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s_tdm->playback_dma_data.maxburst = 8; + } + + if (i2s_tdm->has_capture) { + i2s_tdm->capture_dma_data.addr = res->start + I2S_RXDR; + i2s_tdm->capture_dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + i2s_tdm->capture_dma_data.maxburst = 8; + } + + ret = rockchip_i2s_tdm_tx_path_prepare(i2s_tdm, node); + if (ret < 0) { + dev_err(&pdev->dev, "I2S TX path prepare failed: %d\n", ret); + return ret; + } + + ret = rockchip_i2s_tdm_rx_path_prepare(i2s_tdm, node); + if (ret < 0) { + dev_err(&pdev->dev, "I2S RX path prepare failed: %d\n", ret); + return ret; + } + + dev_set_drvdata(&pdev->dev, i2s_tdm); + + ret = clk_prepare_enable(i2s_tdm->hclk); + if (ret) { + return dev_err_probe(i2s_tdm->dev, ret, + "Failed to enable clock hclk\n"); + } + + ret = i2s_tdm_prepare_enable_mclk(i2s_tdm); + if (ret) { + return dev_err_probe(i2s_tdm->dev, ret, + "Failed to enable one or more mclks\n"); + goto err_disable_hclk; + } + + if (i2s_tdm->mclk_calibrate) { + i2s_tdm->mclk_root0_initial_freq = clk_get_rate(i2s_tdm->mclk_root0); + i2s_tdm->mclk_root1_initial_freq = clk_get_rate(i2s_tdm->mclk_root1); + i2s_tdm->mclk_root0_freq = i2s_tdm->mclk_root0_initial_freq; + i2s_tdm->mclk_root1_freq = i2s_tdm->mclk_root1_initial_freq; + } + + pm_runtime_enable(&pdev->dev); + + regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, I2S_DMACR_TDL_MASK, + I2S_DMACR_TDL(16)); + regmap_update_bits(i2s_tdm->regmap, I2S_DMACR, I2S_DMACR_RDL_MASK, + I2S_DMACR_RDL(16)); + regmap_update_bits(i2s_tdm->regmap, I2S_CKR, I2S_CKR_TRCM_MASK, + i2s_tdm->clk_trcm << I2S_CKR_TRCM_SHIFT); + + if (i2s_tdm->soc_data && i2s_tdm->soc_data->init) + i2s_tdm->soc_data->init(&pdev->dev, res->start); + + ret = devm_snd_soc_register_component(&pdev->dev, + &rockchip_i2s_tdm_component, + &i2s_tdm_dai, 1); + + if (ret) { + dev_err(&pdev->dev, "Could not register DAI\n"); + goto err_suspend; + } + + ret = devm_snd_dmaengine_pcm_register(&pdev->dev, NULL, 0); + if (ret) { + dev_err(&pdev->dev, "Could not register PCM\n"); + goto err_suspend; + } + + return 0; + +err_suspend: + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_tdm_runtime_suspend(&pdev->dev); + pm_runtime_disable(&pdev->dev); + +err_disable_hclk: + clk_disable_unprepare(i2s_tdm->hclk); + + return ret; +} + +static int rockchip_i2s_tdm_remove(struct platform_device *pdev) +{ + if (!pm_runtime_status_suspended(&pdev->dev)) + i2s_tdm_runtime_suspend(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int __maybe_unused rockchip_i2s_tdm_suspend(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + regcache_mark_dirty(i2s_tdm->regmap); + + return 0; +} + +static int __maybe_unused rockchip_i2s_tdm_resume(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + return ret; + ret = regcache_sync(i2s_tdm->regmap); + pm_runtime_put(dev); + + return ret; +} + +static const struct dev_pm_ops rockchip_i2s_tdm_pm_ops = { + SET_RUNTIME_PM_OPS(i2s_tdm_runtime_suspend, i2s_tdm_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(rockchip_i2s_tdm_suspend, + rockchip_i2s_tdm_resume) +}; + +static struct platform_driver rockchip_i2s_tdm_driver = { + .probe = rockchip_i2s_tdm_probe, + .remove = rockchip_i2s_tdm_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(rockchip_i2s_tdm_match), + .pm = &rockchip_i2s_tdm_pm_ops, + }, +}; +module_platform_driver(rockchip_i2s_tdm_driver); + +MODULE_DESCRIPTION("ROCKCHIP I2S/TDM ASoC Interface"); +MODULE_AUTHOR("Sugar Zhang sugar.zhang@rock-chips.com"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, rockchip_i2s_tdm_match); diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.h b/sound/soc/rockchip/rockchip_i2s_tdm.h new file mode 100644 index 000000000000..0aa1c6da1e2c --- /dev/null +++ b/sound/soc/rockchip/rockchip_i2s_tdm.h @@ -0,0 +1,398 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * ALSA SoC Audio Layer - Rockchip I2S/TDM Controller driver + * + * Copyright (c) 2018 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang sugar.zhang@rock-chips.com + * + */ + +#ifndef _ROCKCHIP_I2S_TDM_H +#define _ROCKCHIP_I2S_TDM_H + +/* + * TXCR + * transmit operation control register + */ +#define I2S_TXCR_PATH_SHIFT(x) (23 + (x) * 2) +#define I2S_TXCR_PATH_MASK(x) (0x3 << I2S_TXCR_PATH_SHIFT(x)) +#define I2S_TXCR_PATH(x, v) ((v) << I2S_TXCR_PATH_SHIFT(x)) +#define I2S_TXCR_RCNT_SHIFT 17 +#define I2S_TXCR_RCNT_MASK (0x3f << I2S_TXCR_RCNT_SHIFT) +#define I2S_TXCR_CSR_SHIFT 15 +#define I2S_TXCR_CSR(x) ((x) << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_CSR_MASK (3 << I2S_TXCR_CSR_SHIFT) +#define I2S_TXCR_HWT BIT(14) +#define I2S_TXCR_SJM_SHIFT 12 +#define I2S_TXCR_SJM_R (0 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_SJM_L (1 << I2S_TXCR_SJM_SHIFT) +#define I2S_TXCR_FBM_SHIFT 11 +#define I2S_TXCR_FBM_MSB (0 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_FBM_LSB (1 << I2S_TXCR_FBM_SHIFT) +#define I2S_TXCR_IBM_SHIFT 9 +#define I2S_TXCR_IBM_NORMAL (0 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_LSJM (1 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_RSJM (2 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_IBM_MASK (3 << I2S_TXCR_IBM_SHIFT) +#define I2S_TXCR_PBM_SHIFT 7 +#define I2S_TXCR_PBM_MODE(x) ((x) << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_PBM_MASK (3 << I2S_TXCR_PBM_SHIFT) +#define I2S_TXCR_TFS_SHIFT 5 +#define I2S_TXCR_TFS_I2S (0 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_PCM (1 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_TDM_PCM (2 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_TDM_I2S (3 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_TFS_MASK (3 << I2S_TXCR_TFS_SHIFT) +#define I2S_TXCR_VDW_SHIFT 0 +#define I2S_TXCR_VDW(x) (((x) - 1) << I2S_TXCR_VDW_SHIFT) +#define I2S_TXCR_VDW_MASK (0x1f << I2S_TXCR_VDW_SHIFT) + +/* + * RXCR + * receive operation control register + */ +#define I2S_RXCR_PATH_SHIFT(x) (17 + (x) * 2) +#define I2S_RXCR_PATH_MASK(x) (0x3 << I2S_RXCR_PATH_SHIFT(x)) +#define I2S_RXCR_PATH(x, v) ((v) << I2S_RXCR_PATH_SHIFT(x)) +#define I2S_RXCR_CSR_SHIFT 15 +#define I2S_RXCR_CSR(x) ((x) << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_CSR_MASK (3 << I2S_RXCR_CSR_SHIFT) +#define I2S_RXCR_HWT BIT(14) +#define I2S_RXCR_SJM_SHIFT 12 +#define I2S_RXCR_SJM_R (0 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_SJM_L (1 << I2S_RXCR_SJM_SHIFT) +#define I2S_RXCR_FBM_SHIFT 11 +#define I2S_RXCR_FBM_MSB (0 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_FBM_LSB (1 << I2S_RXCR_FBM_SHIFT) +#define I2S_RXCR_IBM_SHIFT 9 +#define I2S_RXCR_IBM_NORMAL (0 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_LSJM (1 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_RSJM (2 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_IBM_MASK (3 << I2S_RXCR_IBM_SHIFT) +#define I2S_RXCR_PBM_SHIFT 7 +#define I2S_RXCR_PBM_MODE(x) ((x) << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_PBM_MASK (3 << I2S_RXCR_PBM_SHIFT) +#define I2S_RXCR_TFS_SHIFT 5 +#define I2S_RXCR_TFS_I2S (0 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_PCM (1 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_TDM_PCM (2 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_TDM_I2S (3 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_TFS_MASK (3 << I2S_RXCR_TFS_SHIFT) +#define I2S_RXCR_VDW_SHIFT 0 +#define I2S_RXCR_VDW(x) (((x) - 1) << I2S_RXCR_VDW_SHIFT) +#define I2S_RXCR_VDW_MASK (0x1f << I2S_RXCR_VDW_SHIFT) + +/* + * CKR + * clock generation register + */ +#define I2S_CKR_TRCM_SHIFT 28 +#define I2S_CKR_TRCM(x) ((x) << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXRX (0 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_TXONLY (1 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_RXONLY (2 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_TRCM_MASK (3 << I2S_CKR_TRCM_SHIFT) +#define I2S_CKR_MSS_SHIFT 27 +#define I2S_CKR_MSS_MASTER (0 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_SLAVE (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_MSS_MASK (1 << I2S_CKR_MSS_SHIFT) +#define I2S_CKR_CKP_SHIFT 26 +#define I2S_CKR_CKP_NORMAL (0 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_INVERTED (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_CKP_MASK (1 << I2S_CKR_CKP_SHIFT) +#define I2S_CKR_RLP_SHIFT 25 +#define I2S_CKR_RLP_NORMAL (0 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_INVERTED (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_RLP_MASK (1 << I2S_CKR_RLP_SHIFT) +#define I2S_CKR_TLP_SHIFT 24 +#define I2S_CKR_TLP_NORMAL (0 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_INVERTED (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_TLP_MASK (1 << I2S_CKR_TLP_SHIFT) +#define I2S_CKR_MDIV_SHIFT 16 +#define I2S_CKR_MDIV(x) (((x) - 1) << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_MDIV_MASK (0xff << I2S_CKR_MDIV_SHIFT) +#define I2S_CKR_RSD_SHIFT 8 +#define I2S_CKR_RSD(x) (((x) - 1) << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_RSD_MASK (0xff << I2S_CKR_RSD_SHIFT) +#define I2S_CKR_TSD_SHIFT 0 +#define I2S_CKR_TSD(x) (((x) - 1) << I2S_CKR_TSD_SHIFT) +#define I2S_CKR_TSD_MASK (0xff << I2S_CKR_TSD_SHIFT) + +/* + * FIFOLR + * FIFO level register + */ +#define I2S_FIFOLR_RFL_SHIFT 24 +#define I2S_FIFOLR_RFL_MASK (0x3f << I2S_FIFOLR_RFL_SHIFT) +#define I2S_FIFOLR_TFL3_SHIFT 18 +#define I2S_FIFOLR_TFL3_MASK (0x3f << I2S_FIFOLR_TFL3_SHIFT) +#define I2S_FIFOLR_TFL2_SHIFT 12 +#define I2S_FIFOLR_TFL2_MASK (0x3f << I2S_FIFOLR_TFL2_SHIFT) +#define I2S_FIFOLR_TFL1_SHIFT 6 +#define I2S_FIFOLR_TFL1_MASK (0x3f << I2S_FIFOLR_TFL1_SHIFT) +#define I2S_FIFOLR_TFL0_SHIFT 0 +#define I2S_FIFOLR_TFL0_MASK (0x3f << I2S_FIFOLR_TFL0_SHIFT) + +/* + * DMACR + * DMA control register + */ +#define I2S_DMACR_RDE_SHIFT 24 +#define I2S_DMACR_RDE_DISABLE (0 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDE_ENABLE (1 << I2S_DMACR_RDE_SHIFT) +#define I2S_DMACR_RDL_SHIFT 16 +#define I2S_DMACR_RDL(x) (((x) - 1) << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_RDL_MASK (0x1f << I2S_DMACR_RDL_SHIFT) +#define I2S_DMACR_TDE_SHIFT 8 +#define I2S_DMACR_TDE_DISABLE (0 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDE_ENABLE (1 << I2S_DMACR_TDE_SHIFT) +#define I2S_DMACR_TDL_SHIFT 0 +#define I2S_DMACR_TDL(x) ((x) << I2S_DMACR_TDL_SHIFT) +#define I2S_DMACR_TDL_MASK (0x1f << I2S_DMACR_TDL_SHIFT) + +/* + * INTCR + * interrupt control register + */ +#define I2S_INTCR_RFT_SHIFT 20 +#define I2S_INTCR_RFT(x) (((x) - 1) << I2S_INTCR_RFT_SHIFT) +#define I2S_INTCR_RXOIC BIT(18) +#define I2S_INTCR_RXOIE_SHIFT 17 +#define I2S_INTCR_RXOIE_DISABLE (0 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXOIE_ENABLE (1 << I2S_INTCR_RXOIE_SHIFT) +#define I2S_INTCR_RXFIE_SHIFT 16 +#define I2S_INTCR_RXFIE_DISABLE (0 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_RXFIE_ENABLE (1 << I2S_INTCR_RXFIE_SHIFT) +#define I2S_INTCR_TFT_SHIFT 4 +#define I2S_INTCR_TFT(x) (((x) - 1) << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TFT_MASK (0x1f << I2S_INTCR_TFT_SHIFT) +#define I2S_INTCR_TXUIC BIT(2) +#define I2S_INTCR_TXUIE_SHIFT 1 +#define I2S_INTCR_TXUIE_DISABLE (0 << I2S_INTCR_TXUIE_SHIFT) +#define I2S_INTCR_TXUIE_ENABLE (1 << I2S_INTCR_TXUIE_SHIFT) + +/* + * INTSR + * interrupt status register + */ +#define I2S_INTSR_TXEIE_SHIFT 0 +#define I2S_INTSR_TXEIE_DISABLE (0 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_TXEIE_ENABLE (1 << I2S_INTSR_TXEIE_SHIFT) +#define I2S_INTSR_RXOI_SHIFT 17 +#define I2S_INTSR_RXOI_INA (0 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXOI_ACT (1 << I2S_INTSR_RXOI_SHIFT) +#define I2S_INTSR_RXFI_SHIFT 16 +#define I2S_INTSR_RXFI_INA (0 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_RXFI_ACT (1 << I2S_INTSR_RXFI_SHIFT) +#define I2S_INTSR_TXUI_SHIFT 1 +#define I2S_INTSR_TXUI_INA (0 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXUI_ACT (1 << I2S_INTSR_TXUI_SHIFT) +#define I2S_INTSR_TXEI_SHIFT 0 +#define I2S_INTSR_TXEI_INA (0 << I2S_INTSR_TXEI_SHIFT) +#define I2S_INTSR_TXEI_ACT (1 << I2S_INTSR_TXEI_SHIFT) + +/* + * XFER + * Transfer start register + */ +#define I2S_XFER_RXS_SHIFT 1 +#define I2S_XFER_RXS_STOP (0 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_RXS_START (1 << I2S_XFER_RXS_SHIFT) +#define I2S_XFER_TXS_SHIFT 0 +#define I2S_XFER_TXS_STOP (0 << I2S_XFER_TXS_SHIFT) +#define I2S_XFER_TXS_START (1 << I2S_XFER_TXS_SHIFT) + +/* + * CLR + * clear SCLK domain logic register + */ +#define I2S_CLR_RXC BIT(1) +#define I2S_CLR_TXC BIT(0) + +/* + * TXDR + * Transimt FIFO data register, write only. + */ +#define I2S_TXDR_MASK (0xff) + +/* + * RXDR + * Receive FIFO data register, write only. + */ +#define I2S_RXDR_MASK (0xff) + +/* + * TDM_CTRL + * TDM ctrl register + */ +#define TDM_FSYNC_WIDTH_SEL1_MSK GENMASK(20, 18) +#define TDM_FSYNC_WIDTH_SEL1(x) (((x) - 1) << 18) +#define TDM_FSYNC_WIDTH_SEL0_MSK BIT(17) +#define TDM_FSYNC_WIDTH_HALF_FRAME 0 +#define TDM_FSYNC_WIDTH_ONE_FRAME BIT(17) +#define TDM_SHIFT_CTRL_MSK GENMASK(16, 14) +#define TDM_SHIFT_CTRL(x) ((x) << 14) +#define TDM_SLOT_BIT_WIDTH_MSK GENMASK(13, 9) +#define TDM_SLOT_BIT_WIDTH(x) (((x) - 1) << 9) +#define TDM_FRAME_WIDTH_MSK GENMASK(8, 0) +#define TDM_FRAME_WIDTH(x) (((x) - 1) << 0) + +/* + * CLKDIV + * Mclk div register + */ +#define I2S_CLKDIV_TXM_SHIFT 0 +#define I2S_CLKDIV_TXM(x) (((x) - 1) << I2S_CLKDIV_TXM_SHIFT) +#define I2S_CLKDIV_TXM_MASK (0xff << I2S_CLKDIV_TXM_SHIFT) +#define I2S_CLKDIV_RXM_SHIFT 8 +#define I2S_CLKDIV_RXM(x) (((x) - 1) << I2S_CLKDIV_RXM_SHIFT) +#define I2S_CLKDIV_RXM_MASK (0xff << I2S_CLKDIV_RXM_SHIFT) + +/* Clock divider id */ +enum { + ROCKCHIP_DIV_MCLK = 0, + ROCKCHIP_DIV_BCLK, +}; + +/* channel select */ +#define I2S_CSR_SHIFT 15 +#define I2S_CHN_2 (0 << I2S_CSR_SHIFT) +#define I2S_CHN_4 (1 << I2S_CSR_SHIFT) +#define I2S_CHN_6 (2 << I2S_CSR_SHIFT) +#define I2S_CHN_8 (3 << I2S_CSR_SHIFT) + +/* io direction cfg register */ +#define I2S_IO_DIRECTION_MASK (7) +#define I2S_IO_8CH_OUT_2CH_IN (7) +#define I2S_IO_6CH_OUT_4CH_IN (3) +#define I2S_IO_4CH_OUT_6CH_IN (1) +#define I2S_IO_2CH_OUT_8CH_IN (0) + +/* I2S REGS */ +#define I2S_TXCR (0x0000) +#define I2S_RXCR (0x0004) +#define I2S_CKR (0x0008) +#define I2S_TXFIFOLR (0x000c) +#define I2S_DMACR (0x0010) +#define I2S_INTCR (0x0014) +#define I2S_INTSR (0x0018) +#define I2S_XFER (0x001c) +#define I2S_CLR (0x0020) +#define I2S_TXDR (0x0024) +#define I2S_RXDR (0x0028) +#define I2S_RXFIFOLR (0x002c) +#define I2S_TDM_TXCR (0x0030) +#define I2S_TDM_RXCR (0x0034) +#define I2S_CLKDIV (0x0038) + +#define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16)) + +/* PX30 GRF CONFIGS */ +#define PX30_I2S0_CLK_IN_SRC_FROM_TX HIWORD_UPDATE(1, 13, 12) +#define PX30_I2S0_CLK_IN_SRC_FROM_RX HIWORD_UPDATE(2, 13, 12) +#define PX30_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 5, 5) +#define PX30_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 5, 5) + +#define PX30_I2S0_CLK_TXONLY \ + (PX30_I2S0_MCLK_OUT_SRC_FROM_TX | PX30_I2S0_CLK_IN_SRC_FROM_TX) + +#define PX30_I2S0_CLK_RXONLY \ + (PX30_I2S0_MCLK_OUT_SRC_FROM_RX | PX30_I2S0_CLK_IN_SRC_FROM_RX) + +/* RK1808 GRF CONFIGS */ +#define RK1808_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 2, 2) +#define RK1808_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 2, 2) +#define RK1808_I2S0_CLK_IN_SRC_FROM_TX HIWORD_UPDATE(1, 1, 0) +#define RK1808_I2S0_CLK_IN_SRC_FROM_RX HIWORD_UPDATE(2, 1, 0) + +#define RK1808_I2S0_CLK_TXONLY \ + (RK1808_I2S0_MCLK_OUT_SRC_FROM_TX | RK1808_I2S0_CLK_IN_SRC_FROM_TX) + +#define RK1808_I2S0_CLK_RXONLY \ + (RK1808_I2S0_MCLK_OUT_SRC_FROM_RX | RK1808_I2S0_CLK_IN_SRC_FROM_RX) + +/* RK3308 GRF CONFIGS */ +#define RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 10, 10) +#define RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 10, 10) +#define RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_TX HIWORD_UPDATE(1, 9, 9) +#define RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_RX HIWORD_UPDATE(0, 9, 9) +#define RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_RX HIWORD_UPDATE(1, 8, 8) +#define RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_TX HIWORD_UPDATE(0, 8, 8) +#define RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 2, 2) +#define RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 2, 2) +#define RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_TX HIWORD_UPDATE(1, 1, 1) +#define RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_RX HIWORD_UPDATE(0, 1, 1) +#define RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_RX HIWORD_UPDATE(1, 0, 0) +#define RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_TX HIWORD_UPDATE(0, 0, 0) + +#define RK3308_I2S0_CLK_TXONLY \ + (RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_TX | \ + RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_TX | \ + RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_TX) + +#define RK3308_I2S0_CLK_RXONLY \ + (RK3308_I2S0_8CH_MCLK_OUT_SRC_FROM_RX | \ + RK3308_I2S0_8CH_CLK_IN_RX_SRC_FROM_RX | \ + RK3308_I2S0_8CH_CLK_IN_TX_SRC_FROM_RX) + +#define RK3308_I2S1_CLK_TXONLY \ + (RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_TX | \ + RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_TX | \ + RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_TX) + +#define RK3308_I2S1_CLK_RXONLY \ + (RK3308_I2S1_8CH_MCLK_OUT_SRC_FROM_RX | \ + RK3308_I2S1_8CH_CLK_IN_RX_SRC_FROM_RX | \ + RK3308_I2S1_8CH_CLK_IN_TX_SRC_FROM_RX) + +/* RK3568 GRF CONFIGS */ +#define RK3568_I2S1_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 5, 5) +#define RK3568_I2S1_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 5, 5) + +#define RK3568_I2S1_CLK_TXONLY \ + RK3568_I2S1_MCLK_OUT_SRC_FROM_TX + +#define RK3568_I2S1_CLK_RXONLY \ + RK3568_I2S1_MCLK_OUT_SRC_FROM_RX + +#define RK3568_I2S3_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(1, 15, 15) +#define RK3568_I2S3_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(0, 15, 15) +#define RK3568_I2S3_SCLK_SRC_FROM_TX HIWORD_UPDATE(1, 7, 7) +#define RK3568_I2S3_SCLK_SRC_FROM_RX HIWORD_UPDATE(0, 7, 7) +#define RK3568_I2S3_LRCK_SRC_FROM_TX HIWORD_UPDATE(1, 6, 6) +#define RK3568_I2S3_LRCK_SRC_FROM_RX HIWORD_UPDATE(0, 6, 6) + +#define RK3568_I2S3_MCLK_TXONLY \ + RK3568_I2S3_MCLK_OUT_SRC_FROM_TX + +#define RK3568_I2S3_CLK_TXONLY \ + (RK3568_I2S3_SCLK_SRC_FROM_TX | \ + RK3568_I2S3_LRCK_SRC_FROM_TX) + +#define RK3568_I2S3_MCLK_RXONLY \ + RK3568_I2S3_MCLK_OUT_SRC_FROM_RX + +#define RK3568_I2S3_CLK_RXONLY \ + (RK3568_I2S3_SCLK_SRC_FROM_RX | \ + RK3568_I2S3_LRCK_SRC_FROM_RX) + +#define RK3568_I2S3_MCLK_IE HIWORD_UPDATE(0, 3, 3) +#define RK3568_I2S3_MCLK_OE HIWORD_UPDATE(1, 3, 3) +#define RK3568_I2S2_MCLK_IE HIWORD_UPDATE(0, 2, 2) +#define RK3568_I2S2_MCLK_OE HIWORD_UPDATE(1, 2, 2) +#define RK3568_I2S1_MCLK_TX_IE HIWORD_UPDATE(0, 1, 1) +#define RK3568_I2S1_MCLK_TX_OE HIWORD_UPDATE(1, 1, 1) +#define RK3568_I2S1_MCLK_RX_IE HIWORD_UPDATE(0, 0, 0) +#define RK3568_I2S1_MCLK_RX_OE HIWORD_UPDATE(1, 0, 0) + +/* RV1126 GRF CONFIGS */ +#define RV1126_I2S0_MCLK_OUT_SRC_FROM_TX HIWORD_UPDATE(0, 9, 9) +#define RV1126_I2S0_MCLK_OUT_SRC_FROM_RX HIWORD_UPDATE(1, 9, 9) + +#define RV1126_I2S0_CLK_TXONLY \ + RV1126_I2S0_MCLK_OUT_SRC_FROM_TX + +#define RV1126_I2S0_CLK_RXONLY \ + RV1126_I2S0_MCLK_OUT_SRC_FROM_RX + +#endif /* _ROCKCHIP_I2S_TDM_H */
This adds the YAML bindings for the Rockchip I2S/TDM audio driver.
Signed-off-by: Nicolas Frattaroli frattaroli.nicolas@gmail.com Reviewed-by: Rob Herring robh@kernel.org --- .../bindings/sound/rockchip,i2s-tdm.yaml | 198 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 199 insertions(+) create mode 100644 Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml
diff --git a/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml b/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml new file mode 100644 index 000000000000..dce8b4136ec6 --- /dev/null +++ b/Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/sound/rockchip,i2s-tdm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Rockchip I2S/TDM Controller + +description: + The Rockchip I2S/TDM Controller is a Time Division Multiplexed + audio interface found in various Rockchip SoCs, allowing up + to 8 channels of audio over a serial interface. + +maintainers: + - Nicolas Frattaroli frattaroli.nicolas@gmail.com + +properties: + compatible: + enum: + - rockchip,px30-i2s-tdm + - rockchip,rk1808-i2s-tdm + - rockchip,rk3308-i2s-tdm + - rockchip,rk3568-i2s-tdm + - rockchip,rv1126-i2s-tdm + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + dmas: + minItems: 1 + maxItems: 2 + + dma-names: + minItems: 1 + maxItems: 2 + items: + enum: + - rx + - tx + + clocks: + minItems: 3 + items: + - description: clock for TX + - description: clock for RX + - description: AHB clock driving the interface + - description: + Parent clock for mclk_tx (only required when using mclk-calibrate) + - description: + Parent clock for mclk_rx (only required when using mclk-calibrate) + - description: + Clock for sample rates that are an integer multiple of 8000 + (only required when using mclk-calibrate) + - description: + Clock for sample rates that are an integer multiple of 11025 + (only required when using mclk-calibrate) + + clock-names: + minItems: 3 + items: + - const: mclk_tx + - const: mclk_rx + - const: hclk + - const: mclk_tx_src + - const: mclk_rx_src + - const: mclk_root0 + - const: mclk_root1 + + resets: + minItems: 1 + maxItems: 2 + description: resets for the tx and rx directions + + reset-names: + minItems: 1 + maxItems: 2 + items: + enum: + - tx-m + - rx-m + + rockchip,cru: + $ref: /schemas/types.yaml#/definitions/phandle + description: + The phandle of the cru. + Required if neither trcm-sync-tx-only nor trcm-sync-rx-only are specified. + + rockchip,grf: + $ref: /schemas/types.yaml#/definitions/phandle + description: + The phandle of the syscon node for the GRF register. + + rockchip,trcm-sync-tx-only: + type: boolean + description: Use TX BCLK/LRCK for both TX and RX. + + rockchip,trcm-sync-rx-only: + type: boolean + description: Use RX BCLK/LRCK for both TX and RX. + + "#sound-dai-cells": + const: 0 + + rockchip,i2s-rx-route: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Defines the mapping of I2S RX sdis to I2S data bus lines. + By default, they are mapped one-to-one. + rockchip,i2s-rx-route = <3> would mean sdi3 is receiving from data0. + maxItems: 4 + items: + - enum: [0, 1, 2, 3] + + rockchip,i2s-tx-route: + $ref: /schemas/types.yaml#/definitions/uint32-array + description: + Defines the mapping of I2S TX sdos to I2S data bus lines. + By default, they are mapped one-to-one. + rockchip,i2s-tx-route = <3> would mean sdo3 is sending to data0. + maxItems: 4 + items: + - enum: [0, 1, 2, 3] + + rockchip,io-multiplex: + description: + Specify that the GPIO lines on the I2S bus are multiplexed such that + the direction (input/output) needs to be dynamically adjusted. + type: boolean + + +required: + - compatible + - reg + - interrupts + - dmas + - dma-names + - clocks + - clock-names + - resets + - reset-names + - rockchip,grf + - "#sound-dai-cells" + +allOf: + - if: + properties: + rockchip,trcm-sync-tx-only: false + rockchip,trcm-sync-rx-only: false + then: + required: + - rockchip,cru + +additionalProperties: false + +examples: + - | + #include <dt-bindings/clock/rk3568-cru.h> + #include <dt-bindings/interrupt-controller/arm-gic.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/pinctrl/rockchip.h> + + bus { + #address-cells = <2>; + #size-cells = <2>; + i2s@fe410000 { + compatible = "rockchip,rk3568-i2s-tdm"; + reg = <0x0 0xfe410000 0x0 0x1000>; + interrupts = <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&cru MCLK_I2S1_8CH_TX>, <&cru MCLK_I2S1_8CH_RX>, + <&cru HCLK_I2S1_8CH>; + clock-names = "mclk_tx", "mclk_rx", "hclk"; + dmas = <&dmac1 3>, <&dmac1 2>; + dma-names = "rx", "tx"; + resets = <&cru SRST_M_I2S1_8CH_TX>, <&cru SRST_M_I2S1_8CH_RX>; + reset-names = "tx-m", "rx-m"; + rockchip,trcm-sync-tx-only; + rockchip,cru = <&cru>; + rockchip,grf = <&grf>; + #sound-dai-cells = <0>; + pinctrl-names = "default"; + pinctrl-0 = + <&i2s1m0_sclktx + &i2s1m0_sclkrx + &i2s1m0_lrcktx + &i2s1m0_lrckrx + &i2s1m0_sdi0 + &i2s1m0_sdi1 + &i2s1m0_sdi2 + &i2s1m0_sdi3 + &i2s1m0_sdo0 + &i2s1m0_sdo1 + &i2s1m0_sdo2 + &i2s1m0_sdo3>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 106d448e660d..e2cc0357e2b7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -16119,6 +16119,7 @@ ROCKCHIP I2S TDM DRIVER M: Nicolas Frattaroli frattaroli.nicolas@gmail.com L: linux-rockchip@lists.infradead.org S: Maintained +F: Documentation/devicetree/bindings/sound/rockchip,i2s-tdm.yaml F: sound/soc/rockchip/rockchip_i2s_tdm.*
ROCKCHIP ISP V1 DRIVER
On Fri, 1 Oct 2021 19:15:27 +0200, Nicolas Frattaroli wrote:
this is version 5 of the I2S/TDM driver patchset. A big thanks to everyone who has provided their valuable feedback so far.
Changes in v5: driver:
- change comment style of the first comment to C++ style
- make refcount non-atomic, as it's only ever used inside a spinlock
- use newer SND_SOC_DAIFMT_CB* defines
- change ternary statements to if/else conditions
- make _clk_compensation_put return 1 if clock changed
- implement set_bclk_ratio callback
- always set half frame sync mode in TDM mode
- automatically enable mclk-calibrate mode when the clocks for it are specified in the device tree
bindings:
- add Reviewed-by: Rob Herring
- drop rockchip,frame-width property (done by set_bclk_ratio)
- drop rockchip,fsync-half-frame property
- drop rockchip,mclk-calibrate property
dts:
- drop empty codec block from Quartz64 device tree
[...]
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git for-next
Thanks!
[1/4] ASoC: rockchip: add support for i2s-tdm controller commit: 081068fd641403994f0505e6b91e021d3925f348 [2/4] ASoC: dt-bindings: rockchip: add i2s-tdm bindings commit: 510f1c133aedcf69847786c14681e7f7bf4db778
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
participants (2)
-
Mark Brown
-
Nicolas Frattaroli