diff --git a/arch/arm64/boot/dts/rockchip/rk3308.dtsi b/arch/arm64/boot/dts/rockchip/rk3308.dtsi index 2b2ca4db2d26..e0ad7edd6960 100644 --- a/arch/arm64/boot/dts/rockchip/rk3308.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3308.dtsi @@ -1047,6 +1047,7 @@ rockchip,cru = <&cru>; rockchip,grf = <&grf>; rockchip,mclk-calibrate; + i2s-lrck-gpio = <&gpio2 RK_PA7 GPIO_ACTIVE_HIGH>; /* i2s_8ch_0_lrcktx */ pinctrl-names = "default"; pinctrl-0 = <&i2s_8ch_0_sclktx &i2s_8ch_0_sclkrx @@ -1084,6 +1085,7 @@ rockchip,grf = <&grf>; rockchip,mclk-calibrate; rockchip,io-multiplex; + i2s-lrck-gpio = <&gpio1 RK_PA5 GPIO_ACTIVE_HIGH>; /* i2s_8ch_1_m0_lrcktx */ status = "disabled"; }; diff --git a/arch/arm64/boot/dts/rockchip/rk356x.dtsi b/arch/arm64/boot/dts/rockchip/rk356x.dtsi index b726970d1b9f..8a8e239fe299 100644 --- a/arch/arm64/boot/dts/rockchip/rk356x.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk356x.dtsi @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -2629,6 +2630,7 @@ reset-names = "tx-m", "rx-m"; rockchip,cru = <&cru>; rockchip,grf = <&grf>; + i2s-lrck-gpio = <&gpio1 RK_PA5 GPIO_ACTIVE_HIGH>; /* i2s1m0_lrcktx */ pinctrl-names = "default"; pinctrl-0 = <&i2s1m0_sclktx &i2s1m0_sclkrx &i2s1m0_lrcktx &i2s1m0_lrckrx @@ -2651,6 +2653,7 @@ rockchip,cru = <&cru>; rockchip,grf = <&grf>; rockchip,trcm-sync-tx-only; + i2s-lrck-gpio = <&gpio2 RK_PC3 GPIO_ACTIVE_HIGH>; /* i2s2m0_lrcktx */ pinctrl-names = "default"; pinctrl-0 = <&i2s2m0_sclktx &i2s2m0_lrcktx @@ -2674,6 +2677,7 @@ rockchip,cru = <&cru>; rockchip,grf = <&grf>; rockchip,trcm-sync-tx-only; + i2s-lrck-gpio = <&gpio3 RK_PA4 GPIO_ACTIVE_HIGH>; /* i2s3m0_lrck */ #sound-dai-cells = <0>; pinctrl-names = "default"; pinctrl-0 = <&i2s3m0_sclk diff --git a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi index 81bb417def49..3f62c58056cc 100644 --- a/arch/arm64/boot/dts/rockchip/rk3588s.dtsi +++ b/arch/arm64/boot/dts/rockchip/rk3588s.dtsi @@ -4,9 +4,11 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -5747,6 +5749,7 @@ resets = <&cru SRST_M_I2S0_8CH_TX>, <&cru SRST_M_I2S0_8CH_RX>; reset-names = "tx-m", "rx-m"; rockchip,trcm-sync-tx-only; + i2s-lrck-gpio = <&gpio1 RK_PC5 GPIO_ACTIVE_HIGH>; /* i2s0_lrck */ pinctrl-names = "default", "idle", "clk"; pinctrl-0 = <&i2s0_sdi0 &i2s0_sdi1 @@ -5772,6 +5775,7 @@ resets = <&cru SRST_M_I2S1_8CH_TX>, <&cru SRST_M_I2S1_8CH_RX>; reset-names = "tx-m", "rx-m"; rockchip,trcm-sync-tx-only; + i2s-lrck-gpio = <&gpio4 RK_PA2 GPIO_ACTIVE_HIGH>; /* i2s1m0_lrck */ pinctrl-names = "default"; pinctrl-0 = <&i2s1m0_lrck &i2s1m0_sclk diff --git a/drivers/mtd/nand/spi/macronix.c b/drivers/mtd/nand/spi/macronix.c index 32a04b5f950d..003eb402c7ef 100644 --- a/drivers/mtd/nand/spi/macronix.c +++ b/drivers/mtd/nand/spi/macronix.c @@ -112,9 +112,10 @@ static int mx35lf1ge4ab_ecc_get_status(struct spinand_device *spinand, * in order to avoid forcing the wear-leveling layer to move * data around if it's not necessary. */ - if (mx35lf1ge4ab_get_eccsr(spinand, &eccsr)) + if (mx35lf1ge4ab_get_eccsr(spinand, spinand->scratchbuf)) return nanddev_get_ecc_conf(nand)->strength; + eccsr = *spinand->scratchbuf; if (WARN_ON(eccsr > nanddev_get_ecc_conf(nand)->strength || !eccsr)) return nanddev_get_ecc_conf(nand)->strength; diff --git a/sound/soc/rockchip/Kconfig b/sound/soc/rockchip/Kconfig index e1b10483ae6f..81a1c69dc81e 100644 --- a/sound/soc/rockchip/Kconfig +++ b/sound/soc/rockchip/Kconfig @@ -31,6 +31,7 @@ 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 + select SND_SOC_ROCKCHIP_TRCM 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 @@ -94,6 +95,12 @@ config SND_SOC_ROCKCHIP_SPDIFRX Say Y or M if you want to add support for SPDIFRX driver for Rockchip SPDIF receiver device. +config SND_SOC_ROCKCHIP_TRCM + tristate "Rockchip TRCM Pcm Driver" + depends on SND_SOC_ROCKCHIP + help + Say Y or M if you want to add support for TRCM driver. + config SND_SOC_ROCKCHIP_VAD tristate "Rockchip Voice Activity Detection Driver" depends on HAVE_CLK && SND_SOC_ROCKCHIP diff --git a/sound/soc/rockchip/Makefile b/sound/soc/rockchip/Makefile index ca8dfb54841c..6571779e1849 100644 --- a/sound/soc/rockchip/Makefile +++ b/sound/soc/rockchip/Makefile @@ -10,6 +10,7 @@ snd-soc-rockchip-pdm-objs := rockchip_pdm.o snd-soc-rockchip-sai-objs := rockchip_sai.o snd-soc-rockchip-spdif-objs := rockchip_spdif.o snd-soc-rockchip-spdifrx-objs := rockchip_spdifrx.o +snd-soc-rockchip-trcm-objs := rockchip_trcm.o snd-soc-rockchip-vad-objs := rockchip_vad.o ifdef CONFIG_THUMB2_KERNEL snd-soc-rockchip-vad-$(CONFIG_THUMB2_KERNEL) += vad_preprocess_thumb.o @@ -28,6 +29,7 @@ obj-$(CONFIG_SND_SOC_ROCKCHIP_PDM) += snd-soc-rockchip-pdm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SAI) += snd-soc-rockchip-sai.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIF) += snd-soc-rockchip-spdif.o obj-$(CONFIG_SND_SOC_ROCKCHIP_SPDIFRX) += snd-soc-rockchip-spdifrx.o +obj-$(CONFIG_SND_SOC_ROCKCHIP_TRCM) += snd-soc-rockchip-trcm.o obj-$(CONFIG_SND_SOC_ROCKCHIP_VAD) += snd-soc-rockchip-vad.o snd-soc-rockchip-hdmi-objs := rockchip_hdmi.o diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.c b/sound/soc/rockchip/rockchip_i2s_tdm.c index 6e5cfe729ddc..65baeed24202 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.c +++ b/sound/soc/rockchip/rockchip_i2s_tdm.c @@ -25,6 +25,7 @@ #include "rockchip_i2s_tdm.h" #include "rockchip_dlp_pcm.h" #include "rockchip_utils.h" +#include "rockchip_trcm.h" #define DRV_NAME "rockchip-i2s-tdm" @@ -50,8 +51,6 @@ * */ -#define CLK_MAX_COUNT 1000 -#define NSAMPLES 4 #define XFER_EN 0x3 #define XFER_DIS 0x0 #define CKR_V(m, r, t) ((m - 1) << 16 | (r - 1) << 8 | (t - 1) << 0) @@ -60,6 +59,8 @@ #define I2S_XCR_IBM_LSJM I2S_TXCR_IBM_LSJM #endif +#define CLK_MAX_COUNT 1000 +#define NSAMPLES 4 #define DEFAULT_MCLK_FS 256 #define DEFAULT_FS 48000 #define CH_GRP_MAX 4 /* The max channel 8 / 2 */ @@ -83,6 +84,8 @@ struct txrx_config { u32 rxonly; }; +struct rk_i2s_tdm_dev; + struct rk_i2s_soc_data { u32 softrst_offset; u32 grf_reg_offset; @@ -90,6 +93,7 @@ struct rk_i2s_soc_data { int config_count; const struct txrx_config *configs; int (*init)(struct device *dev, u32 addr); + void (*src_clk_ctrl)(struct rk_i2s_tdm_dev *i2s_tdm, bool en); }; struct rk_i2s_tdm_dev { @@ -117,15 +121,15 @@ struct rk_i2s_tdm_dev { struct snd_dmaengine_dai_dma_data playback_dma_data; struct snd_pcm_substream *substreams[SNDRV_PCM_STREAM_LAST + 1]; unsigned int wait_time[SNDRV_PCM_STREAM_LAST + 1]; + struct snd_soc_component *pcm_comp; struct reset_control *tx_reset; struct reset_control *rx_reset; struct pinctrl *pinctrl; struct pinctrl_state *clk_state; const struct rk_i2s_soc_data *soc_data; #ifdef HAVE_SYNC_RESET + int id; void __iomem *cru_base; - int tx_reset_id; - int rx_reset_id; #endif bool is_master_mode; bool io_multiplex; @@ -133,6 +137,7 @@ struct rk_i2s_tdm_dev { bool tdm_mode; bool tdm_fsync_half_frame; bool is_dma_active[SNDRV_PCM_STREAM_LAST + 1]; + bool dma_guard_initialized; unsigned int mclk_rx_freq; unsigned int mclk_tx_freq; unsigned int mclk_root0_freq; @@ -151,9 +156,9 @@ struct rk_i2s_tdm_dev { bool has_playback; bool has_capture; struct snd_soc_dai_driver *dai; + struct gpio_desc *i2s_lrck_gpio; #ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES struct snd_soc_dai *clk_src_dai; - struct gpio_desc *i2s_lrck_gpio; struct gpio_desc *tdm_fsync_gpio; unsigned int tx_lanes; unsigned int rx_lanes; @@ -254,76 +259,6 @@ 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); - - pinctrl_pm_select_idle_state(dev); - - return 0; -} - -static int rockchip_i2s_tdm_pinctrl_select_clk_state(struct device *dev) -{ - struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); - - if (IS_ERR_OR_NULL(i2s_tdm->pinctrl) || !i2s_tdm->clk_state) - return 0; - - pinctrl_select_state(i2s_tdm->pinctrl, i2s_tdm->clk_state); - - 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; - - /* - * pinctrl default state is invoked by ASoC framework, so, - * we just handle clk state here if DT assigned. - */ - if (i2s_tdm->is_master_mode) - rockchip_i2s_tdm_pinctrl_select_clk_state(dev); - - 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; - - /* - * should be placed after regcache sync done to back - * to the slave mode and then enable clk state. - */ - if (!i2s_tdm->is_master_mode) - rockchip_i2s_tdm_pinctrl_select_clk_state(dev); - - 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); @@ -354,148 +289,146 @@ static inline bool is_dma_active(struct rk_i2s_tdm_dev *i2s_tdm, int stream) } #ifdef HAVE_SYNC_RESET -#if defined(CONFIG_ARM) && !defined(writeq) -static inline void __raw_writeq(u64 val, volatile void __iomem *addr) +static void rockchip_i2s_tdm_src_clk_ctrl(struct rk_i2s_tdm_dev *i2s_tdm, bool en, + unsigned int gate_reg, unsigned int gate_val, + unsigned int sel_reg) { - asm volatile("strd %0, %H0, [%1]" : : "r" (val), "r" (addr)); + int val = readl(i2s_tdm->cru_base + sel_reg); + + if (!gate_reg || !sel_reg) + return; + + if (IS_I2S_CLK_SRC_MCLKIN(val) && en) + writel(I2S_CLK_SRC_MCLKIN, i2s_tdm->cru_base + sel_reg); + + writel(gate_val, i2s_tdm->cru_base + gate_reg); + + if (IS_I2S_CLK_SRC_MCLKIN(val) && !en) + writel(I2S_CLK_SRC_PLL, i2s_tdm->cru_base + sel_reg); +} + +static void rockchip_i2s_tdm_px30_src_clk_ctrl(struct rk_i2s_tdm_dev *i2s_tdm, bool en) +{ + unsigned int gate_reg = 0, gate_val = 0, sel_reg = 0; + + switch (i2s_tdm->clk_trcm) { + case I2S_CKR_TRCM_TXONLY: + sel_reg = PX30_CLKSEL_CON28_I2S0_TX; + gate_reg = PX30_CLKGATE_CON9; + gate_val = en ? PX30_CLKGATE_CON9_I2S0_TX_PLL_EN : + PX30_CLKGATE_CON9_I2S0_TX_PLL_DIS; + break; + case I2S_CKR_TRCM_RXONLY: + sel_reg = PX30_CLKSEL_CON58_I2S0_RX; + gate_reg = PX30_CLKGATE_CON17; + gate_val = en ? PX30_CLKGATE_CON17_I2S0_RX_PLL_EN : + PX30_CLKGATE_CON17_I2S0_RX_PLL_DIS; + break; + } + + rockchip_i2s_tdm_src_clk_ctrl(i2s_tdm, en, gate_reg, gate_val, sel_reg); +} + +static void rockchip_i2s_tdm_rk1808_src_clk_ctrl(struct rk_i2s_tdm_dev *i2s_tdm, bool en) +{ + unsigned int gate_reg = 0, gate_val = 0, sel_reg = 0; + + switch (i2s_tdm->clk_trcm) { + case I2S_CKR_TRCM_TXONLY: + sel_reg = RK1808_CLKSEL_CON32_I2S0_TX; + gate_reg = RK1808_CLKGATE_CON17; + gate_val = en ? RK1808_CLKGATE_CON17_I2S0_TX_PLL_EN : + RK1808_CLKGATE_CON17_I2S0_TX_PLL_DIS; + break; + case I2S_CKR_TRCM_RXONLY: + sel_reg = RK1808_CLKSEL_CON34_I2S0_RX; + gate_reg = RK1808_CLKGATE_CON18; + gate_val = en ? RK1808_CLKGATE_CON18_I2S0_RX_PLL_EN : + RK1808_CLKGATE_CON18_I2S0_RX_PLL_DIS; + break; + } + + rockchip_i2s_tdm_src_clk_ctrl(i2s_tdm, en, gate_reg, gate_val, sel_reg); +} + +static void rockchip_i2s_tdm_rk3308_src_clk_ctrl(struct rk_i2s_tdm_dev *i2s_tdm, bool en) +{ + unsigned int gate_reg = 0, gate_val = 0, sel_reg = 0; + + /* I2S_8CH_2 used for internal and TRCM-none mode default */ + switch (i2s_tdm->id) { + case 0: + switch (i2s_tdm->clk_trcm) { + case I2S_CKR_TRCM_TXONLY: + sel_reg = RK3308_CLKSEL_CON52_I2S0_TX; + gate_reg = RK3308_CLKGATE_CON10; + gate_val = en ? RK3308_CLKGATE_CON10_I2S0_TX_PLL_EN : + RK3308_CLKGATE_CON10_I2S0_TX_PLL_DIS; + break; + case I2S_CKR_TRCM_RXONLY: + sel_reg = RK3308_CLKSEL_CON54_I2S0_RX; + gate_reg = RK3308_CLKGATE_CON11; + gate_val = en ? RK3308_CLKGATE_CON11_I2S0_RX_PLL_EN : + RK3308_CLKGATE_CON11_I2S0_RX_PLL_DIS; + break; + } + break; + case 1: + switch (i2s_tdm->clk_trcm) { + case I2S_CKR_TRCM_TXONLY: + sel_reg = RK3308_CLKSEL_CON56_I2S1_TX; + gate_reg = RK3308_CLKGATE_CON11; + gate_val = en ? RK3308_CLKGATE_CON11_I2S1_TX_PLL_EN : + RK3308_CLKGATE_CON11_I2S1_TX_PLL_DIS; + break; + case I2S_CKR_TRCM_RXONLY: + sel_reg = RK3308_CLKSEL_CON58_I2S1_RX; + gate_reg = RK3308_CLKGATE_CON11; + gate_val = en ? RK3308_CLKGATE_CON11_I2S1_RX_PLL_EN : + RK3308_CLKGATE_CON11_I2S1_RX_PLL_DIS; + break; + } + break; + } + + rockchip_i2s_tdm_src_clk_ctrl(i2s_tdm, en, gate_reg, gate_val, sel_reg); } -#define writeq(v,c) ({ __iowmb(); __raw_writeq((__force u64) cpu_to_le64(v), c); }) -#endif static void rockchip_i2s_tdm_reset_assert(struct rk_i2s_tdm_dev *i2s_tdm) { - int tx_bank, rx_bank, tx_offset, rx_offset, tx_id, rx_id; - void __iomem *cru_reset, *addr; - unsigned long flags; - u64 val; - if (!i2s_tdm->cru_base || !i2s_tdm->soc_data || !i2s_tdm->is_master_mode) return; - tx_id = i2s_tdm->tx_reset_id; - rx_id = i2s_tdm->rx_reset_id; - if (tx_id < 0 || rx_id < 0) + if (IS_ERR_OR_NULL(i2s_tdm->tx_reset) || IS_ERR_OR_NULL(i2s_tdm->rx_reset)) return; - tx_bank = tx_id / 16; - tx_offset = tx_id % 16; - rx_bank = rx_id / 16; - rx_offset = rx_id % 16; + reset_control_assert(i2s_tdm->tx_reset); + reset_control_assert(i2s_tdm->rx_reset); - 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); - - cru_reset = i2s_tdm->cru_base + i2s_tdm->soc_data->softrst_offset; - - switch (abs(tx_bank - rx_bank)) { - case 0: - writel(BIT(tx_offset) | BIT(rx_offset) | - (BIT(tx_offset) << 16) | (BIT(rx_offset) << 16), - cru_reset + (tx_bank * 4)); - break; - case 1: - if (tx_bank < rx_bank) { - val = BIT(rx_offset) | (BIT(rx_offset) << 16); - val <<= 32; - val |= BIT(tx_offset) | (BIT(tx_offset) << 16); - addr = cru_reset + (tx_bank * 4); - } else { - val = BIT(tx_offset) | (BIT(tx_offset) << 16); - val <<= 32; - val |= BIT(rx_offset) | (BIT(rx_offset) << 16); - addr = cru_reset + (rx_bank * 4); - } - - if (IS_ALIGNED((uintptr_t)addr, 8)) { - writeq(val, addr); - break; - } - fallthrough; - default: - 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); - break; - } /* delay for reset assert done */ udelay(10); } static void rockchip_i2s_tdm_reset_deassert(struct rk_i2s_tdm_dev *i2s_tdm) { - int tx_bank, rx_bank, tx_offset, rx_offset, tx_id, rx_id; - void __iomem *cru_reset, *addr; - unsigned long flags; - u64 val; - if (!i2s_tdm->cru_base || !i2s_tdm->soc_data || !i2s_tdm->is_master_mode) return; - tx_id = i2s_tdm->tx_reset_id; - rx_id = i2s_tdm->rx_reset_id; - if (tx_id < 0 || rx_id < 0) + if (IS_ERR_OR_NULL(i2s_tdm->tx_reset) || IS_ERR_OR_NULL(i2s_tdm->rx_reset)) return; - tx_bank = tx_id / 16; - tx_offset = tx_id % 16; - rx_bank = rx_id / 16; - rx_offset = rx_id % 16; + if (i2s_tdm->soc_data && i2s_tdm->soc_data->src_clk_ctrl) + i2s_tdm->soc_data->src_clk_ctrl(i2s_tdm, 0); - 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); + reset_control_deassert(i2s_tdm->tx_reset); + reset_control_deassert(i2s_tdm->rx_reset); - cru_reset = i2s_tdm->cru_base + i2s_tdm->soc_data->softrst_offset; + if (i2s_tdm->soc_data && i2s_tdm->soc_data->src_clk_ctrl) + i2s_tdm->soc_data->src_clk_ctrl(i2s_tdm, 1); - switch (abs(tx_bank - rx_bank)) { - case 0: - writel((BIT(tx_offset) << 16) | (BIT(rx_offset) << 16), - cru_reset + (tx_bank * 4)); - break; - case 1: - if (tx_bank < rx_bank) { - val = (BIT(rx_offset) << 16); - val <<= 32; - val |= (BIT(tx_offset) << 16); - addr = cru_reset + (tx_bank * 4); - } else { - val = (BIT(tx_offset) << 16); - val <<= 32; - val |= (BIT(rx_offset) << 16); - addr = cru_reset + (rx_bank * 4); - } - - if (IS_ALIGNED((uintptr_t)addr, 8)) { - writeq(val, addr); - break; - } - fallthrough; - default: - 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); - break; - } /* delay for reset deassert done */ udelay(10); } - -/* - * make sure both tx and rx are reset at the same time for sync lrck - * when clk_trcm > 0 - */ -static void rockchip_i2s_tdm_sync_reset(struct rk_i2s_tdm_dev *i2s_tdm) -{ - rockchip_i2s_tdm_reset_assert(i2s_tdm); - rockchip_i2s_tdm_reset_deassert(i2s_tdm); -} #else static inline void rockchip_i2s_tdm_reset_assert(struct rk_i2s_tdm_dev *i2s_tdm) { @@ -503,44 +436,35 @@ static inline void rockchip_i2s_tdm_reset_assert(struct rk_i2s_tdm_dev *i2s_tdm) static inline void rockchip_i2s_tdm_reset_deassert(struct rk_i2s_tdm_dev *i2s_tdm) { } -static inline void rockchip_i2s_tdm_sync_reset(struct rk_i2s_tdm_dev *i2s_tdm) -{ -} #endif -static void rockchip_i2s_tdm_reset(struct reset_control *rc) +static void rockchip_i2s_tdm_reset(struct rk_i2s_tdm_dev *i2s_tdm, unsigned int clr) { - if (IS_ERR_OR_NULL(rc)) - return; + if ((clr & I2S_CLR_TXC) && !IS_ERR_OR_NULL(i2s_tdm->tx_reset)) { + reset_control_assert(i2s_tdm->tx_reset); + /* delay for reset assert done */ + udelay(10); + reset_control_deassert(i2s_tdm->tx_reset); + /* delay for reset deassert done */ + udelay(10); + } - reset_control_assert(rc); - /* delay for reset assert done */ - udelay(10); - reset_control_deassert(rc); - /* delay for reset deassert done */ - udelay(10); + if ((clr & I2S_CLR_RXC) && !IS_ERR_OR_NULL(i2s_tdm->rx_reset)) { + reset_control_assert(i2s_tdm->rx_reset); + /* delay for reset assert done */ + udelay(10); + reset_control_deassert(i2s_tdm->rx_reset); + /* delay for reset deassert done */ + udelay(10); + } } static int rockchip_i2s_tdm_clear(struct rk_i2s_tdm_dev *i2s_tdm, unsigned int clr) { - struct reset_control *rst = NULL; unsigned int val = 0; int ret = 0; - switch (clr) { - case I2S_CLR_TXC: - rst = i2s_tdm->tx_reset; - break; - case I2S_CLR_RXC: - rst = i2s_tdm->rx_reset; - break; - case I2S_CLR_TXC | I2S_CLR_RXC: - break; - default: - return -EINVAL; - } - regmap_update_bits(i2s_tdm->regmap, I2S_CLR, clr, clr); ret = regmap_read_poll_timeout_atomic(i2s_tdm->regmap, I2S_CLR, val, !(val & clr), 10, 100); @@ -578,10 +502,7 @@ static int rockchip_i2s_tdm_clear(struct rk_i2s_tdm_dev *i2s_tdm, return 0; reset: - if (i2s_tdm->clk_trcm) - rockchip_i2s_tdm_sync_reset(i2s_tdm); - else - rockchip_i2s_tdm_reset(rst); + rockchip_i2s_tdm_reset(i2s_tdm, clr); return 0; } @@ -659,6 +580,87 @@ static void rockchip_i2s_tdm_dma_ctrl(struct rk_i2s_tdm_dev *i2s_tdm, rockchip_i2s_tdm_fifo_xrun_detect(i2s_tdm, stream, 1); } +static inline int rockchip_i2s_tdm_clk_assert_h(const struct gpio_desc *desc) +{ + int cnt = CLK_MAX_COUNT; + + while (gpiod_get_raw_value(desc) && --cnt) + ; + + return cnt; +} + +static inline int rockchip_i2s_tdm_clk_assert_l(const struct gpio_desc *desc) +{ + int cnt = CLK_MAX_COUNT; + + while (!gpiod_get_raw_value(desc) && --cnt) + ; + + return cnt; +} + +static inline bool rockchip_i2s_tdm_clk_valid(struct rk_i2s_tdm_dev *i2s_tdm, + bool has_fsync) +{ + int dc_h = CLK_MAX_COUNT, dc_l = CLK_MAX_COUNT; + + /* + * TBD: optimize debounce and get value + * + * debounce at least one cycle found, otherwise, the clk ref maybe + * not on the fly. + */ + + /* check HIGH-Level */ + dc_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + if (!dc_h) + return false; + + /* check LOW-Level */ + dc_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + if (!dc_l) + return false; + +#ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES + if (!has_fsync) + return true; + + /* check HIGH-Level */ + dc_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->tdm_fsync_gpio); + if (!dc_h) + return false; + + /* check LOW-Level */ + dc_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->tdm_fsync_gpio); + if (!dc_l) + return false; +#endif + + return true; +} + +static void __maybe_unused rockchip_i2s_tdm_gpio_clk_meas(struct rk_i2s_tdm_dev *i2s_tdm, + const struct gpio_desc *desc, + const char *name) +{ + int h[NSAMPLES], l[NSAMPLES], i; + + dev_dbg(i2s_tdm->dev, "%s:\n", name); + + if (!rockchip_i2s_tdm_clk_valid(i2s_tdm, 1)) + return; + + for (i = 0; i < NSAMPLES; i++) { + h[i] = rockchip_i2s_tdm_clk_assert_h(desc); + l[i] = rockchip_i2s_tdm_clk_assert_l(desc); + } + + for (i = 0; i < NSAMPLES; i++) + dev_dbg(i2s_tdm->dev, "H[%d]: %2d, L[%d]: %2d\n", + i, CLK_MAX_COUNT - h[i], i, CLK_MAX_COUNT - l[i]); +} + #ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES static const char * const tx_lanes_text[] = { "Auto", "SDOx1", "SDOx2", "SDOx3", "SDOx4" }; static const char * const rx_lanes_text[] = { "Auto", "SDIx1", "SDIx2", "SDIx3", "SDIx4" }; @@ -776,81 +778,6 @@ static int rockchip_i2s_tdm_multi_lanes_set_clk(struct snd_pcm_substream *substr return 0; } -static inline int tdm_multi_lanes_clk_assert_h(const struct gpio_desc *desc) -{ - int cnt = CLK_MAX_COUNT; - - while (gpiod_get_raw_value(desc) && --cnt) - ; - - return cnt; -} - -static inline int tdm_multi_lanes_clk_assert_l(const struct gpio_desc *desc) -{ - int cnt = CLK_MAX_COUNT; - - while (!gpiod_get_raw_value(desc) && --cnt) - ; - - return cnt; -} - -static inline bool rockchip_i2s_tdm_clk_valid(struct rk_i2s_tdm_dev *i2s_tdm) -{ - int dc_h = CLK_MAX_COUNT, dc_l = CLK_MAX_COUNT; - - /* - * TBD: optimize debounce and get value - * - * debounce at least one cycle found, otherwise, the clk ref maybe - * not on the fly. - */ - - /* check HIGH-Level */ - dc_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); - if (!dc_h) - return false; - - /* check LOW-Level */ - dc_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); - if (!dc_l) - return false; - - /* check HIGH-Level */ - dc_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->tdm_fsync_gpio); - if (!dc_h) - return false; - - /* check LOW-Level */ - dc_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->tdm_fsync_gpio); - if (!dc_l) - return false; - - return true; -} - -static void __maybe_unused rockchip_i2s_tdm_gpio_clk_meas(struct rk_i2s_tdm_dev *i2s_tdm, - const struct gpio_desc *desc, - const char *name) -{ - int h[NSAMPLES], l[NSAMPLES], i; - - dev_dbg(i2s_tdm->dev, "%s:\n", name); - - if (!rockchip_i2s_tdm_clk_valid(i2s_tdm)) - return; - - for (i = 0; i < NSAMPLES; i++) { - h[i] = tdm_multi_lanes_clk_assert_h(desc); - l[i] = tdm_multi_lanes_clk_assert_l(desc); - } - - for (i = 0; i < NSAMPLES; i++) - dev_dbg(i2s_tdm->dev, "H[%d]: %2d, L[%d]: %2d\n", - i, CLK_MAX_COUNT - h[i], i, CLK_MAX_COUNT - l[i]); -} - static int rockchip_i2s_tdm_multi_lanes_start(struct rk_i2s_tdm_dev *i2s_tdm, int stream) { unsigned int tdm_h = 0, tdm_l = 0, i2s_h = 0, i2s_l = 0; @@ -878,7 +805,7 @@ static int rockchip_i2s_tdm_multi_lanes_start(struct rk_i2s_tdm_dev *i2s_tdm, in local_irq_save(flags); - if (!rockchip_i2s_tdm_clk_valid(i2s_tdm)) { + if (!rockchip_i2s_tdm_clk_valid(i2s_tdm, 1)) { local_irq_restore(flags); dev_err(i2s_tdm->dev, "Invalid LRCK / FSYNC measured by ref IO\n"); return -EINVAL; @@ -886,36 +813,36 @@ static int rockchip_i2s_tdm_multi_lanes_start(struct rk_i2s_tdm_dev *i2s_tdm, in switch (fmt) { case I2S_XCR_IBM_NORMAL: - tdm_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->tdm_fsync_gpio); - tdm_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->tdm_fsync_gpio); + tdm_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->tdm_fsync_gpio); + tdm_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->tdm_fsync_gpio); if (i2s_tdm->lrck_ratio == 8) { - tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); } - i2s_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + i2s_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); if (stream == SNDRV_PCM_STREAM_CAPTURE) - i2s_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + i2s_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); break; case I2S_XCR_IBM_LSJM: - tdm_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->tdm_fsync_gpio); - tdm_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->tdm_fsync_gpio); + tdm_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->tdm_fsync_gpio); + tdm_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->tdm_fsync_gpio); if (i2s_tdm->lrck_ratio == 8) { - tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); - tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); } - tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); - i2s_l = tdm_multi_lanes_clk_assert_l(i2s_tdm->i2s_lrck_gpio); - i2s_h = tdm_multi_lanes_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + i2s_l = rockchip_i2s_tdm_clk_assert_l(i2s_tdm->i2s_lrck_gpio); + i2s_h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); break; default: local_irq_restore(flags); @@ -935,9 +862,16 @@ static int rockchip_i2s_tdm_multi_lanes_start(struct rk_i2s_tdm_dev *i2s_tdm, in static int rockchip_i2s_tdm_multi_lanes_parse(struct rk_i2s_tdm_dev *i2s_tdm) { struct device_node *clk_src_node = NULL; + enum gpiod_flags gpiod_flags; unsigned int val; int ret; + i2s_tdm->is_tdm_multi_lanes = + device_property_read_bool(i2s_tdm->dev, "rockchip,tdm-multi-lanes"); + + if (!i2s_tdm->is_tdm_multi_lanes) + return 0; + i2s_tdm->tx_lanes = 1; i2s_tdm->rx_lanes = 1; @@ -951,22 +885,27 @@ static int rockchip_i2s_tdm_multi_lanes_parse(struct rk_i2s_tdm_dev *i2s_tdm) i2s_tdm->rx_lanes = val; } - i2s_tdm->i2s_lrck_gpio = devm_gpiod_get_optional(i2s_tdm->dev, "i2s-lrck", GPIOD_IN); - if (IS_ERR(i2s_tdm->i2s_lrck_gpio)) { - ret = PTR_ERR(i2s_tdm->i2s_lrck_gpio); - dev_err(i2s_tdm->dev, "Failed to get i2s_lrck_gpio %d\n", ret); - return ret; - } - - i2s_tdm->tdm_fsync_gpio = devm_gpiod_get_optional(i2s_tdm->dev, "tdm-fsync", GPIOD_IN); + /* It's optional, required when use soc clk src, such as: i2s2_2ch */ + clk_src_node = of_parse_phandle(i2s_tdm->dev->of_node, "rockchip,clk-src", 0); + gpiod_flags = clk_src_node ? GPIOD_ASIS : GPIOD_IN; + /* + * Two situation for 'tdm-fsync': + * + * A. when the pin is a generic gpio as the ref signal pin which is drived from + * external. should use flag GPIOD_IN to reclaim as GPIO_IN function. + * + * B. when the pin is the same pin from the 'clk-src' on the same SoC, we can + * use the 'clk-src' fsync out signal as the 'tdm-fsync' to query status. + * in this case, should use flag GPIOD_ASIS not to reclaim it as GPIO. + */ + i2s_tdm->tdm_fsync_gpio = devm_gpiod_get_optional(i2s_tdm->dev, "tdm-fsync", + gpiod_flags); if (IS_ERR(i2s_tdm->tdm_fsync_gpio)) { ret = PTR_ERR(i2s_tdm->tdm_fsync_gpio); dev_err(i2s_tdm->dev, "Failed to get tdm_fsync_gpio %d\n", ret); return ret; } - /* It's optional, required when use soc clk src, such as: i2s2_2ch */ - clk_src_node = of_parse_phandle(i2s_tdm->dev->of_node, "rockchip,clk-src", 0); if (clk_src_node) { i2s_tdm->clk_src_dai = rockchip_i2s_tdm_find_dai(clk_src_node); if (!i2s_tdm->clk_src_dai) @@ -985,6 +924,47 @@ static int rockchip_i2s_tdm_multi_lanes_parse(struct rk_i2s_tdm_dev *i2s_tdm) } #endif +static int rockchip_i2s_tdm_slave_one_frame_start(struct rk_i2s_tdm_dev *i2s_tdm, + int stream) +{ + unsigned int msk, val, h; + unsigned long flags; + bool sof; + + sof = i2s_tdm->tdm_mode && !i2s_tdm->is_master_mode && + !i2s_tdm->tdm_fsync_half_frame; + + if (!sof) + return -ENOSYS; + + if (!i2s_tdm->i2s_lrck_gpio) { + dev_err(i2s_tdm->dev, "SOF: should assign 'i2s-lrck-gpio' the pin used in DT\n"); + return -EINVAL; + } + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + msk = I2S_XFER_TXS_MASK; + val = I2S_XFER_TXS_START; + } else { + msk = I2S_XFER_RXS_MASK; + val = I2S_XFER_RXS_START; + } + + local_irq_save(flags); + if (!rockchip_i2s_tdm_clk_valid(i2s_tdm, 0)) { + local_irq_restore(flags); + dev_err(i2s_tdm->dev, "SOF: invalid LRCK, please check 'i2s-lrck-gpio' in DT\n"); + return -EINVAL; + } + h = rockchip_i2s_tdm_clk_assert_h(i2s_tdm->i2s_lrck_gpio); + regmap_update_bits(i2s_tdm->regmap, I2S_XFER, msk, val); + local_irq_restore(flags); + + dev_dbg(i2s_tdm->dev, "STREAM[%d]: TDM-H: %d\n", stream, CLK_MAX_COUNT - h); + + return 0; +} + static void rockchip_i2s_tdm_xfer_start(struct rk_i2s_tdm_dev *i2s_tdm, int stream) { @@ -994,6 +974,9 @@ static void rockchip_i2s_tdm_xfer_start(struct rk_i2s_tdm_dev *i2s_tdm, return; } #endif + if (rockchip_i2s_tdm_slave_one_frame_start(i2s_tdm, stream) != -ENOSYS) + return; + if (i2s_tdm->clk_trcm) { rockchip_i2s_tdm_reset_assert(i2s_tdm); regmap_update_bits(i2s_tdm->regmap, I2S_XFER, @@ -1043,23 +1026,38 @@ static void rockchip_i2s_tdm_xfer_stop(struct rk_i2s_tdm_dev *i2s_tdm, rockchip_i2s_tdm_clear(i2s_tdm, clr); } -static void rockchip_i2s_tdm_xfer_trcm_start(struct rk_i2s_tdm_dev *i2s_tdm) +static void rockchip_i2s_tdm_xfer_trcm_start(struct rk_i2s_tdm_dev *i2s_tdm, + int stream) { + int bstream = SNDRV_PCM_STREAM_LAST - stream; unsigned long flags; + u32 val, en; spin_lock_irqsave(&i2s_tdm->lock, flags); - if (++i2s_tdm->refcount == 1) + if (++i2s_tdm->refcount == 1) { + if (i2s_tdm->dma_guard_initialized) { + regmap_read(i2s_tdm->regmap, I2S_DMACR, &val); + en = I2S_DMACR_RDE(1) | I2S_DMACR_TDE(1); + if ((val & en) != en) { + dmaengine_trcm_dma_guard_ctrl(i2s_tdm->pcm_comp, bstream, 1); + rockchip_i2s_tdm_dma_ctrl(i2s_tdm, bstream, 1); + } + } rockchip_i2s_tdm_xfer_start(i2s_tdm, 0); + } spin_unlock_irqrestore(&i2s_tdm->lock, flags); } -static void rockchip_i2s_tdm_xfer_trcm_stop(struct rk_i2s_tdm_dev *i2s_tdm) +static void rockchip_i2s_tdm_xfer_trcm_stop(struct rk_i2s_tdm_dev *i2s_tdm, + int stream) { unsigned long flags; spin_lock_irqsave(&i2s_tdm->lock, flags); if (--i2s_tdm->refcount == 0) rockchip_i2s_tdm_xfer_stop(i2s_tdm, 0, false); + if (i2s_tdm->dma_guard_initialized) + rockchip_i2s_tdm_dma_ctrl(i2s_tdm, stream, 1); spin_unlock_irqrestore(&i2s_tdm->lock, flags); } @@ -1069,6 +1067,9 @@ static void rockchip_i2s_tdm_trcm_pause(struct snd_pcm_substream *substream, int stream = substream->stream; int bstream = SNDRV_PCM_STREAM_LAST - stream; + if (i2s_tdm->pcm_comp) + dmaengine_trcm_dma_guard_ctrl(i2s_tdm->pcm_comp, stream, 0); + /* store the current state, prepare for resume if necessary */ i2s_tdm->is_dma_active[bstream] = is_dma_active(i2s_tdm, bstream); @@ -1081,14 +1082,17 @@ static void rockchip_i2s_tdm_trcm_pause(struct snd_pcm_substream *substream, static void rockchip_i2s_tdm_trcm_resume(struct snd_pcm_substream *substream, struct rk_i2s_tdm_dev *i2s_tdm) { + int stream = substream->stream; int bstream = SNDRV_PCM_STREAM_LAST - substream->stream; - /* - * just resume bstream, because current stream will be - * startup in the trigger-cmd-START - */ + if (i2s_tdm->pcm_comp) { + dmaengine_trcm_dma_guard_ctrl(i2s_tdm->pcm_comp, stream, 1); + rockchip_i2s_tdm_dma_ctrl(i2s_tdm, stream, 1); + } + if (i2s_tdm->is_dma_active[bstream]) rockchip_i2s_tdm_dma_ctrl(i2s_tdm, bstream, 1); + rockchip_i2s_tdm_xfer_start(i2s_tdm, bstream); } @@ -1108,7 +1112,7 @@ static void rockchip_i2s_tdm_start(struct rk_i2s_tdm_dev *i2s_tdm, int stream) rockchip_i2s_tdm_dma_ctrl(i2s_tdm, stream, 1); if (i2s_tdm->clk_trcm) - rockchip_i2s_tdm_xfer_trcm_start(i2s_tdm); + rockchip_i2s_tdm_xfer_trcm_start(i2s_tdm, stream); else rockchip_i2s_tdm_xfer_start(i2s_tdm, stream); } @@ -1118,7 +1122,7 @@ static void rockchip_i2s_tdm_stop(struct rk_i2s_tdm_dev *i2s_tdm, int stream) rockchip_i2s_tdm_dma_ctrl(i2s_tdm, stream, 0); if (i2s_tdm->clk_trcm) - rockchip_i2s_tdm_xfer_trcm_stop(i2s_tdm); + rockchip_i2s_tdm_xfer_trcm_stop(i2s_tdm, stream); else rockchip_i2s_tdm_xfer_stop(i2s_tdm, stream, false); } @@ -1601,14 +1605,20 @@ static bool is_params_dirty(struct snd_pcm_substream *substream, } static int rockchip_i2s_tdm_params_trcm(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, 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); + struct snd_soc_component *comp = i2s_tdm->pcm_comp; unsigned long flags; + /* Prepare params changes for trcm dma guard resume */ + if (comp && comp->driver->hw_params) + comp->driver->hw_params(comp, substream, params); + spin_lock_irqsave(&i2s_tdm->lock, flags); if (i2s_tdm->refcount) rockchip_i2s_tdm_trcm_pause(substream, i2s_tdm); @@ -1633,6 +1643,9 @@ static int rockchip_i2s_tdm_params_trcm(struct snd_pcm_substream *substream, rockchip_i2s_tdm_trcm_resume(substream, i2s_tdm); spin_unlock_irqrestore(&i2s_tdm->lock, flags); + if (comp && !i2s_tdm->dma_guard_initialized) + i2s_tdm->dma_guard_initialized = true; + return 0; } @@ -1853,7 +1866,7 @@ static int rockchip_i2s_tdm_hw_params(struct snd_pcm_substream *substream, return 0; if (i2s_tdm->clk_trcm) - rockchip_i2s_tdm_params_trcm(substream, dai, div_bclk, div_lrck, val); + rockchip_i2s_tdm_params_trcm(substream, params, dai, div_bclk, div_lrck, val); else rockchip_i2s_tdm_params(substream, dai, div_bclk, div_lrck, val); @@ -2423,6 +2436,9 @@ static const struct rk_i2s_soc_data px30_i2s_soc_data = { .configs = px30_txrx_config, .config_count = ARRAY_SIZE(px30_txrx_config), .init = common_soc_init, +#ifdef HAVE_SYNC_RESET + .src_clk_ctrl = rockchip_i2s_tdm_px30_src_clk_ctrl, +#endif }; static const struct rk_i2s_soc_data rk1808_i2s_soc_data = { @@ -2430,6 +2446,9 @@ static const struct rk_i2s_soc_data rk1808_i2s_soc_data = { .configs = rk1808_txrx_config, .config_count = ARRAY_SIZE(rk1808_txrx_config), .init = common_soc_init, +#ifdef HAVE_SYNC_RESET + .src_clk_ctrl = rockchip_i2s_tdm_rk1808_src_clk_ctrl, +#endif }; static const struct rk_i2s_soc_data rk3308_i2s_soc_data = { @@ -2439,6 +2458,9 @@ static const struct rk_i2s_soc_data rk3308_i2s_soc_data = { .configs = rk3308_txrx_config, .config_count = ARRAY_SIZE(rk3308_txrx_config), .init = common_soc_init, +#ifdef HAVE_SYNC_RESET + .src_clk_ctrl = rockchip_i2s_tdm_rk3308_src_clk_ctrl, +#endif }; static const struct rk_i2s_soc_data rk3568_i2s_soc_data = { @@ -2480,26 +2502,6 @@ static const struct of_device_id rockchip_i2s_tdm_match[] = { {}, }; -#ifdef HAVE_SYNC_RESET -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]; -} -#endif - static const struct snd_soc_dai_driver i2s_tdm_dai = { .probe = rockchip_i2s_tdm_dai_probe, .ops = &rockchip_i2s_tdm_dai_ops, @@ -2801,7 +2803,7 @@ static int rockchip_i2s_tdm_keep_clk_always_on(struct rk_i2s_tdm_dev *i2s_tdm) I2S_CKR_RSD(div_lrck) | I2S_CKR_TSD(div_lrck)); if (i2s_tdm->clk_trcm) - rockchip_i2s_tdm_xfer_trcm_start(i2s_tdm); + rockchip_i2s_tdm_xfer_trcm_start(i2s_tdm, SNDRV_PCM_STREAM_PLAYBACK); else rockchip_i2s_tdm_xfer_start(i2s_tdm, SNDRV_PCM_STREAM_PLAYBACK); @@ -2815,6 +2817,8 @@ static int rockchip_i2s_tdm_keep_clk_always_on(struct rk_i2s_tdm_dev *i2s_tdm) static int rockchip_i2s_tdm_register_platform(struct device *dev) { + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + struct snd_soc_component *comp; int ret = 0; if (device_property_read_bool(dev, "rockchip,no-dmaengine")) { @@ -2829,6 +2833,25 @@ static int rockchip_i2s_tdm_register_platform(struct device *dev) return ret; } + if (i2s_tdm->clk_trcm) { + ret = devm_snd_dmaengine_trcm_register(dev); + if (ret) { + dev_err(dev, "Could not register TRCM PCM\n"); + return ret; + } + + comp = snd_soc_lookup_component(i2s_tdm->dev, + SND_DMAENGINE_TRCM_DRV_NAME); + if (!comp) { + dev_err(dev, "Could not find TRCM PCM\n"); + ret = -ENODEV; + } + + i2s_tdm->pcm_comp = comp; + + return ret; + } + ret = devm_snd_dmaengine_pcm_register(dev, NULL, 0); if (ret) dev_err(dev, "Could not register PCM\n"); @@ -2836,6 +2859,96 @@ static int rockchip_i2s_tdm_register_platform(struct device *dev) return ret; } +static int __maybe_unused i2s_tdm_runtime_suspend(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + if (i2s_tdm->pcm_comp && i2s_tdm->clk_trcm) { + rockchip_i2s_tdm_dma_ctrl(i2s_tdm, 0, 0); + rockchip_i2s_tdm_dma_ctrl(i2s_tdm, 1, 0); + dmaengine_trcm_dma_guard_ctrl(i2s_tdm->pcm_comp, 0, 0); + dmaengine_trcm_dma_guard_ctrl(i2s_tdm->pcm_comp, 1, 0); + } + + regcache_cache_only(i2s_tdm->regmap, true); + i2s_tdm_disable_unprepare_mclk(i2s_tdm); + + clk_disable_unprepare(i2s_tdm->hclk); + + pinctrl_pm_select_idle_state(dev); + + return 0; +} + +static int rockchip_i2s_tdm_pinctrl_select_clk_state(struct device *dev) +{ + struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(dev); + + if (IS_ERR_OR_NULL(i2s_tdm->pinctrl) || !i2s_tdm->clk_state) + return 0; + + pinctrl_select_state(i2s_tdm->pinctrl, i2s_tdm->clk_state); + + 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; + + /* + * pinctrl default state is invoked by ASoC framework, so, + * we just handle clk state here if DT assigned. + */ + if (i2s_tdm->is_master_mode) + rockchip_i2s_tdm_pinctrl_select_clk_state(dev); + + 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; + + /* + * should be placed after regcache sync done to back + * to the slave mode and then enable clk state. + */ + if (!i2s_tdm->is_master_mode) + rockchip_i2s_tdm_pinctrl_select_clk_state(dev); + + return 0; + +err_regcache: + i2s_tdm_disable_unprepare_mclk(i2s_tdm); +err_mclk: + clk_disable_unprepare(i2s_tdm->hclk); +err_hclk: + return ret; +} + +static void __maybe_unused rockchip_i2s_tdm_unmap(struct rk_i2s_tdm_dev *i2s_tdm) +{ +#ifdef HAVE_SYNC_RESET + if (i2s_tdm->cru_base) + iounmap(i2s_tdm->cru_base); +#endif + +#ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES + if (i2s_tdm->clk_src_base) + iounmap(i2s_tdm->clk_src_base); +#endif +} + static int rockchip_i2s_tdm_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; @@ -2855,6 +2968,19 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) i2s_tdm->dev = &pdev->dev; i2s_tdm->lrck_ratio = 1; + /* + * Should use flag GPIOD_ASIS not to reclaim LRCK pin as GPIO function, + * because we use the same PIN and just read EXT_PORT value which show + * the pin status. + */ + i2s_tdm->i2s_lrck_gpio = devm_gpiod_get_optional(i2s_tdm->dev, "i2s-lrck", + GPIOD_ASIS); + if (IS_ERR(i2s_tdm->i2s_lrck_gpio)) { + ret = PTR_ERR(i2s_tdm->i2s_lrck_gpio); + dev_err(i2s_tdm->dev, "Failed to get i2s_lrck_gpio %d\n", ret); + return ret; + } + of_id = of_match_device(rockchip_i2s_tdm_match, &pdev->dev); if (!of_id) return -EINVAL; @@ -2931,24 +3057,6 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) } } -#ifdef HAVE_SYNC_RESET - sync = of_device_is_compatible(node, "rockchip,px30-i2s-tdm") || - of_device_is_compatible(node, "rockchip,rk1808-i2s-tdm") || - of_device_is_compatible(node, "rockchip,rk3308-i2s-tdm"); - - if (i2s_tdm->clk_trcm && sync) { - struct device_node *cru_node; - - cru_node = of_parse_phandle(node, "rockchip,cru", 0); - i2s_tdm->cru_base = of_iomap(cru_node, 0); - if (!i2s_tdm->cru_base) - 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"); - } -#endif - i2s_tdm->io_multiplex = of_property_read_bool(node, "rockchip,io-multiplex"); @@ -2992,33 +3100,26 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) } } - 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) { - ret = dev_err_probe(i2s_tdm->dev, ret, - "Failed to enable one or more mclks\n"); + 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); goto err_disable_hclk; } + 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); + goto err_disable_hclk; + } + + dev_set_drvdata(&pdev->dev, i2s_tdm); + 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); @@ -3036,16 +3137,6 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) if (i2s_tdm->soc_data && i2s_tdm->soc_data->init) i2s_tdm->soc_data->init(&pdev->dev, res->start); -#ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES - i2s_tdm->is_tdm_multi_lanes = - device_property_read_bool(i2s_tdm->dev, "rockchip,tdm-multi-lanes"); - - if (i2s_tdm->is_tdm_multi_lanes) { - ret = rockchip_i2s_tdm_multi_lanes_parse(i2s_tdm); - if (ret) - goto err_disable_hclk; - } -#endif /* * CLK_ALWAYS_ON should be placed after all registers write done, * because this situation will enable XFER bit which will make @@ -3057,6 +3148,31 @@ static int rockchip_i2s_tdm_probe(struct platform_device *pdev) goto err_disable_hclk; } +#ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES + ret = rockchip_i2s_tdm_multi_lanes_parse(i2s_tdm); + if (ret) + goto err_unmap; +#endif + +#ifdef HAVE_SYNC_RESET + sync = of_device_is_compatible(node, "rockchip,px30-i2s-tdm") || + of_device_is_compatible(node, "rockchip,rk1808-i2s-tdm") || + of_device_is_compatible(node, "rockchip,rk3308-i2s-tdm"); + + if (i2s_tdm->clk_trcm && sync) { + struct device_node *cru_node; + + cru_node = of_parse_phandle(node, "rockchip,cru", 0); + i2s_tdm->cru_base = of_iomap(cru_node, 0); + if (!i2s_tdm->cru_base) { + ret = -ENOENT; + goto err_unmap; + } + + i2s_tdm->id = (res->start >> 16) & GENMASK(3, 0); + } +#endif + /* * MUST: after pm_runtime_enable step, any register R/W * should be wrapped with pm_runtime_get_sync/put. @@ -3093,6 +3209,11 @@ err_suspend: i2s_tdm_runtime_suspend(&pdev->dev); pm_runtime_disable(&pdev->dev); +#if defined(HAVE_SYNC_RESET) || defined(CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES) +err_unmap: + rockchip_i2s_tdm_unmap(i2s_tdm); +#endif + err_disable_hclk: clk_disable_unprepare(i2s_tdm->hclk); @@ -3101,11 +3222,8 @@ err_disable_hclk: static int rockchip_i2s_tdm_remove(struct platform_device *pdev) { -#ifdef CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES - struct rk_i2s_tdm_dev *i2s_tdm = dev_get_drvdata(&pdev->dev); - - if (i2s_tdm->clk_src_base) - iounmap(i2s_tdm->clk_src_base); +#if defined(HAVE_SYNC_RESET) || defined(CONFIG_SND_SOC_ROCKCHIP_I2S_TDM_MULTI_LANES) + rockchip_i2s_tdm_unmap(dev_get_drvdata(&pdev->dev)); #endif if (!pm_runtime_status_suspended(&pdev->dev)) i2s_tdm_runtime_suspend(&pdev->dev); diff --git a/sound/soc/rockchip/rockchip_i2s_tdm.h b/sound/soc/rockchip/rockchip_i2s_tdm.h index 9b9f5fa390ee..c1c20c716270 100644 --- a/sound/soc/rockchip/rockchip_i2s_tdm.h +++ b/sound/soc/rockchip/rockchip_i2s_tdm.h @@ -320,6 +320,24 @@ enum { #define HIWORD_UPDATE(v, h, l) (((v) << (l)) | (GENMASK((h), (l)) << 16)) +/* I2Sx CLK SRC Mux Common Define */ +#define I2S_CLK_SRC(v) (((v) & GENMASK(11, 10)) >> 10) +#define I2S_CLK_SRC_MCLKIN HIWORD_UPDATE(2, 11, 10) +#define I2S_CLK_SRC_PLL HIWORD_UPDATE(0, 11, 10) +#define IS_I2S_CLK_SRC_MCLKIN(v) (I2S_CLK_SRC(v) == 2) + +/* PX30 CRU CONFIGS */ +#define PX30_CLKSEL_CON28_I2S0_TX 0x170 +#define PX30_CLKSEL_CON58_I2S0_RX 0x1e8 + +#define PX30_CLKGATE_CON9 0x224 +#define PX30_CLKGATE_CON9_I2S0_TX_PLL_DIS HIWORD_UPDATE(1, 12, 12) +#define PX30_CLKGATE_CON9_I2S0_TX_PLL_EN HIWORD_UPDATE(0, 12, 12) + +#define PX30_CLKGATE_CON17 0x244 +#define PX30_CLKGATE_CON17_I2S0_RX_PLL_DIS HIWORD_UPDATE(1, 0, 0) +#define PX30_CLKGATE_CON17_I2S0_RX_PLL_EN HIWORD_UPDATE(0, 0, 0) + /* 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) @@ -332,6 +350,18 @@ enum { #define PX30_I2S0_CLK_RXONLY \ (PX30_I2S0_MCLK_OUT_SRC_FROM_RX | PX30_I2S0_CLK_IN_SRC_FROM_RX) +/* RK1808 CRU CONFIGS */ +#define RK1808_CLKSEL_CON32_I2S0_TX 0x180 +#define RK1808_CLKSEL_CON34_I2S0_RX 0x188 + +#define RK1808_CLKGATE_CON17 0x274 +#define RK1808_CLKGATE_CON17_I2S0_TX_PLL_DIS HIWORD_UPDATE(1, 12, 12) +#define RK1808_CLKGATE_CON17_I2S0_TX_PLL_EN HIWORD_UPDATE(0, 12, 12) + +#define RK1808_CLKGATE_CON18 0x278 +#define RK1808_CLKGATE_CON18_I2S0_RX_PLL_DIS HIWORD_UPDATE(1, 0, 0) +#define RK1808_CLKGATE_CON18_I2S0_RX_PLL_EN HIWORD_UPDATE(0, 0, 0) + /* 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) @@ -344,6 +374,26 @@ enum { #define RK1808_I2S0_CLK_RXONLY \ (RK1808_I2S0_MCLK_OUT_SRC_FROM_RX | RK1808_I2S0_CLK_IN_SRC_FROM_RX) +/* RK3308 CRU CONFIGS */ +#define RK3308_CLKSEL_CON52_I2S0_TX 0x1d0 +#define RK3308_CLKSEL_CON54_I2S0_RX 0x1d8 +#define RK3308_CLKSEL_CON56_I2S1_TX 0x1e0 +#define RK3308_CLKSEL_CON58_I2S1_RX 0x1e8 + +#define RK3308_CLKGATE_CON10 0x328 +#define RK3308_CLKGATE_CON10_I2S0_TX_PLL_DIS HIWORD_UPDATE(1, 12, 12) +#define RK3308_CLKGATE_CON10_I2S0_TX_PLL_EN HIWORD_UPDATE(0, 12, 12) + +#define RK3308_CLKGATE_CON11 0x32c +#define RK3308_CLKGATE_CON11_I2S0_RX_PLL_DIS HIWORD_UPDATE(1, 0, 0) +#define RK3308_CLKGATE_CON11_I2S0_RX_PLL_EN HIWORD_UPDATE(0, 0, 0) + +#define RK3308_CLKGATE_CON11_I2S1_TX_PLL_DIS HIWORD_UPDATE(1, 4, 4) +#define RK3308_CLKGATE_CON11_I2S1_TX_PLL_EN HIWORD_UPDATE(0, 4, 4) + +#define RK3308_CLKGATE_CON11_I2S1_RX_PLL_DIS HIWORD_UPDATE(1, 8, 8) +#define RK3308_CLKGATE_CON11_I2S1_RX_PLL_EN HIWORD_UPDATE(0, 8, 8) + /* 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) diff --git a/sound/soc/rockchip/rockchip_trcm.c b/sound/soc/rockchip/rockchip_trcm.c new file mode 100644 index 000000000000..8865ccdeaeb0 --- /dev/null +++ b/sound/soc/rockchip/rockchip_trcm.c @@ -0,0 +1,578 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Rockchip TRCM Pcm Driver + * + * Copyright (c) 2023 Rockchip Electronics Co. Ltd. + * Author: Sugar Zhang + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rockchip_trcm.h" + +#define DMA_GUARD_BUFFER_SIZE 64 + +static unsigned int prealloc_buffer_size_kbytes = 512; +module_param(prealloc_buffer_size_kbytes, uint, 0444); +MODULE_PARM_DESC(prealloc_buffer_size_kbytes, "Preallocate DMA buffer size (KB)."); + +struct dmaengine_dma_guard { + dma_addr_t dma_addr; + unsigned char *dma_area; +}; + +struct dmaengine_trcm { + struct device *dev; + struct dma_chan *chan[SNDRV_PCM_STREAM_LAST + 1]; + struct dmaengine_dma_guard guard[SNDRV_PCM_STREAM_LAST + 1]; + struct snd_soc_component component; +}; + +struct dmaengine_trcm_runtime_data { + struct dmaengine_trcm *parent; + struct dma_chan *dma_chan; + dma_cookie_t cookie; + + unsigned int frame_bytes; + unsigned int channels; + int stream; +}; + +static inline ssize_t trcm_channels_to_bytes(struct dmaengine_trcm_runtime_data *prtd, + int channels) +{ + return (prtd->frame_bytes / prtd->channels) * channels; +} + +static inline ssize_t trcm_frames_to_bytes(struct dmaengine_trcm_runtime_data *prtd, + snd_pcm_sframes_t size) +{ + return size * prtd->frame_bytes; +} + +static inline snd_pcm_sframes_t trcm_bytes_to_frames(struct dmaengine_trcm_runtime_data *prtd, + ssize_t size) +{ + return size / prtd->frame_bytes; +} + +static inline struct dmaengine_trcm *soc_component_to_trcm(struct snd_soc_component *p) +{ + return container_of(p, struct dmaengine_trcm, component); +} + +static inline struct dmaengine_trcm_runtime_data *substream_to_prtd( + const struct snd_pcm_substream *substream) +{ + if (!substream->runtime) + return NULL; + + return substream->runtime->private_data; +} + +static struct dma_chan *snd_dmaengine_trcm_get_chan(struct snd_pcm_substream *substream) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + + return prtd->dma_chan; +} + +static struct device *dmaengine_dma_dev(struct dmaengine_trcm *trcm, + struct snd_pcm_substream *substream) +{ + if (!trcm->chan[substream->stream]) + return NULL; + + return trcm->chan[substream->stream]->device->dev; +} + +static int dmaengine_trcm_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_chan *chan = snd_dmaengine_trcm_get_chan(substream); + struct dma_slave_config slave_config; + int ret; + + memset(&slave_config, 0, sizeof(slave_config)); + + ret = snd_dmaengine_pcm_prepare_slave_config(substream, params, &slave_config); + if (ret) + return ret; + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) + return ret; + + prtd->frame_bytes = snd_pcm_format_size(params_format(params), + params_channels(params)); + prtd->channels = params_channels(params); + + return 0; +} + +static int +dmaengine_pcm_set_runtime_hwparams(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct dmaengine_trcm *trcm = soc_component_to_trcm(component); + struct device *dma_dev = dmaengine_dma_dev(trcm, substream); + struct dma_chan *chan = trcm->chan[substream->stream]; + struct snd_dmaengine_dai_dma_data *dma_data; + struct snd_pcm_hardware hw; + + if (rtd->dai_link->num_cpus > 1) { + dev_err(rtd->dev, + "%s doesn't support Multi CPU yet\n", __func__); + return -EINVAL; + } + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), substream); + + memset(&hw, 0, sizeof(hw)); + hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED; + hw.periods_min = 2; + hw.periods_max = UINT_MAX; + hw.period_bytes_min = 256; + hw.period_bytes_max = dma_get_max_seg_size(dma_dev); + hw.buffer_bytes_max = SIZE_MAX; + hw.fifo_size = dma_data->fifo_size; + + snd_dmaengine_pcm_refine_runtime_hwparams(substream, + dma_data, + &hw, + chan); + + return snd_soc_set_runtime_hwparams(substream, &hw); +} + +static int dmaengine_trcm_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct dmaengine_trcm *trcm = soc_component_to_trcm(component); + struct dma_chan *chan = trcm->chan[substream->stream]; + struct dmaengine_trcm_runtime_data *prtd; + int ret; + + if (!chan) + return -ENXIO; + + ret = dmaengine_pcm_set_runtime_hwparams(component, substream); + if (ret) + return ret; + + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + prtd->parent = trcm; + prtd->stream = substream->stream; + prtd->dma_chan = chan; + + substream->runtime->private_data = prtd; + + return 0; +} + +static int dmaengine_trcm_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + + dmaengine_synchronize(prtd->dma_chan); + + kfree(prtd); + + return 0; +} + +static snd_pcm_uframes_t dmaengine_trcm_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_tx_state state; + unsigned int buf_size; + unsigned int pos = 0; + + dmaengine_tx_status(prtd->dma_chan, prtd->cookie, &state); + buf_size = snd_pcm_lib_buffer_bytes(substream); + if (state.residue > 0 && state.residue <= buf_size) + pos = buf_size - state.residue; + + return trcm_bytes_to_frames(prtd, pos); +} + +static void dmaengine_trcm_dma_complete(void *arg) +{ + struct snd_pcm_substream *substream = arg; + + if (!substream->runtime) + return; + + snd_pcm_period_elapsed(substream); +} + +static int dmaengine_trcm_prepare_and_submit(struct snd_pcm_substream *substream) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + struct dma_chan *chan = prtd->dma_chan; + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + unsigned long flags = DMA_CTRL_ACK; + + direction = snd_pcm_substream_to_dma_direction(substream); + + if (!substream->runtime->no_period_wakeup) + flags |= DMA_PREP_INTERRUPT; + + desc = dmaengine_prep_dma_cyclic(chan, + substream->runtime->dma_addr, + snd_pcm_lib_buffer_bytes(substream), + snd_pcm_lib_period_bytes(substream), direction, flags); + + if (!desc) + return -ENOMEM; + + desc->callback = dmaengine_trcm_dma_complete; + desc->callback_param = substream; + prtd->cookie = dmaengine_submit(desc); + + return 0; +} + +int dmaengine_trcm_dma_guard_ctrl(struct snd_soc_component *component, + int stream, bool en) +{ + struct dmaengine_trcm *trcm = soc_component_to_trcm(component); + struct dmaengine_dma_guard *guard = &trcm->guard[stream]; + struct dma_chan *chan = trcm->chan[stream]; + struct dma_async_tx_descriptor *desc; + enum dma_transfer_direction direction; + + if (!chan) + return 0; + + if (!en) + return dmaengine_terminate_sync(chan); + + direction = stream ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + + desc = dmaengine_prep_dma_cyclic(chan, guard->dma_addr, + DMA_GUARD_BUFFER_SIZE, + DMA_GUARD_BUFFER_SIZE, + direction, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(component->dev, "Failed to get dma desc\n"); + return -ENOMEM; + } + + desc->callback = NULL; + desc->callback_param = NULL; + dmaengine_submit(desc); + dma_async_issue_pending(chan); + + return 0; +} +EXPORT_SYMBOL_GPL(dmaengine_trcm_dma_guard_ctrl); + +static int dmaengine_trcm_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + struct dmaengine_trcm_runtime_data *prtd = substream_to_prtd(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dmaengine_terminate_sync(prtd->dma_chan); + ret = dmaengine_trcm_prepare_and_submit(substream); + if (ret) + return ret; + dma_async_issue_pending(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + dmaengine_resume(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + if (runtime->info & SNDRV_PCM_INFO_PAUSE) + dmaengine_pause(prtd->dma_chan); + else + dmaengine_terminate_sync(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + dmaengine_pause(prtd->dma_chan); + break; + case SNDRV_PCM_TRIGGER_STOP: + dmaengine_terminate_sync(prtd->dma_chan); + dmaengine_trcm_dma_guard_ctrl(component, substream->stream, 1); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dmaengine_trcm_dma_guard_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct dmaengine_trcm *trcm = soc_component_to_trcm(component); + struct snd_dmaengine_dai_dma_data *dma_data; + struct snd_pcm_substream *substream; + struct dma_chan *chan; + struct dma_slave_config slave_config; + struct device *dev; + dma_addr_t dma_addr; + unsigned char *dma_area; + unsigned int i; + int ret; + + for_each_pcm_streams(i) { + substream = rtd->pcm->streams[i].substream; + dev = dmaengine_dma_dev(trcm, substream); + chan = trcm->chan[i]; + + dma_area = dma_alloc_coherent(dev, DMA_GUARD_BUFFER_SIZE, + &dma_addr, GFP_KERNEL); + if (!dma_area) + return -ENOMEM; + + memset(dma_area, 0x0, DMA_GUARD_BUFFER_SIZE); + + trcm->guard[i].dma_addr = dma_addr; + trcm->guard[i].dma_area = dma_area; + + memset(&slave_config, 0, sizeof(slave_config)); + + dma_data = snd_soc_dai_get_dma_data(asoc_rtd_to_cpu(rtd, 0), + substream); + snd_dmaengine_pcm_set_config_from_dai_data(substream, dma_data, + &slave_config); + + ret = dmaengine_slave_config(chan, &slave_config); + if (ret) + return ret; + } + + return 0; +} + +static int dmaengine_trcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct dmaengine_trcm *trcm = soc_component_to_trcm(component); + struct snd_pcm_substream *substream; + size_t prealloc_buffer_size; + size_t max_buffer_size; + unsigned int i; + int ret; + + prealloc_buffer_size = prealloc_buffer_size_kbytes * 1024; + max_buffer_size = SIZE_MAX; + + ret = dmaengine_trcm_dma_guard_new(component, rtd); + if (ret) + return ret; + + for_each_pcm_streams(i) { + substream = rtd->pcm->streams[i].substream; + if (!substream) + continue; + + if (!trcm->chan[i]) { + dev_err(component->dev, + "Missing dma channel for stream: %d\n", i); + return -EINVAL; + } + + snd_pcm_set_managed_buffer(substream, + SNDRV_DMA_TYPE_DEV_IRAM, + dmaengine_dma_dev(trcm, substream), + prealloc_buffer_size, + max_buffer_size); + + if (rtd->pcm->streams[i].pcm->name[0] == '\0') { + strscpy_pad(rtd->pcm->streams[i].pcm->name, + rtd->pcm->streams[i].pcm->id, + sizeof(rtd->pcm->streams[i].pcm->name)); + } + } + + return 0; +} + +static const struct snd_soc_component_driver dmaengine_trcm_component = { + .name = SND_DMAENGINE_TRCM_DRV_NAME, + .probe_order = SND_SOC_COMP_ORDER_LATE, + .open = dmaengine_trcm_open, + .close = dmaengine_trcm_close, + .hw_params = dmaengine_trcm_hw_params, + .trigger = dmaengine_trcm_trigger, + .pointer = dmaengine_trcm_pointer, + .pcm_construct = dmaengine_trcm_new, +}; + +static const char * const dmaengine_pcm_dma_channel_names[] = { + [SNDRV_PCM_STREAM_PLAYBACK] = "tx", + [SNDRV_PCM_STREAM_CAPTURE] = "rx", +}; + +static int dmaengine_pcm_request_chan_of(struct dmaengine_trcm *trcm, + struct device *dev, const struct snd_dmaengine_pcm_config *config) +{ + unsigned int i; + const char *name; + struct dma_chan *chan; + + for_each_pcm_streams(i) { + name = dmaengine_pcm_dma_channel_names[i]; + chan = dma_request_chan(dev, name); + if (IS_ERR(chan)) { + /* + * Only report probe deferral errors, channels + * might not be present for devices that + * support only TX or only RX. + */ + if (PTR_ERR(chan) == -EPROBE_DEFER) + return -EPROBE_DEFER; + trcm->chan[i] = NULL; + } else { + trcm->chan[i] = chan; + } + } + + return 0; +} + +static void dmaengine_pcm_release_chan(struct dmaengine_trcm *trcm) +{ + unsigned int i; + + for_each_pcm_streams(i) { + if (!trcm->chan[i]) + continue; + dma_release_channel(trcm->chan[i]); + } +} + +/** + * snd_dmaengine_trcm_register - Register a dmaengine based TRCM device + * @dev: The parent device for the TRCM device + */ +static int snd_dmaengine_trcm_register(struct device *dev) +{ + const struct snd_soc_component_driver *driver; + struct dmaengine_trcm *trcm; + int ret; + + trcm = kzalloc(sizeof(*trcm), GFP_KERNEL); + if (!trcm) + return -ENOMEM; + + trcm->dev = dev; + +#ifdef CONFIG_DEBUG_FS + trcm->component.debugfs_prefix = "dma"; +#endif + ret = dmaengine_pcm_request_chan_of(trcm, dev, NULL); + if (ret) + goto err_free_dma; + + driver = &dmaengine_trcm_component; + + ret = snd_soc_component_initialize(&trcm->component, driver, dev); + if (ret) + goto err_free_dma; + + ret = snd_soc_add_component(&trcm->component, NULL, 0); + if (ret) + goto err_free_dma; + + dev_info(dev, "Register PCM for TRCM mode\n"); + + return 0; + +err_free_dma: + dmaengine_pcm_release_chan(trcm); + kfree(trcm); + return ret; +} + +/** + * snd_dmaengine_trcm_unregister - Removes a dmaengine based TRCM device + * @dev: Parent device the TRCM was register with + * + * Removes a dmaengine based TRCM device previously registered with + * snd_dmaengine_trcm_register. + */ +static void snd_dmaengine_trcm_unregister(struct device *dev) +{ + struct snd_soc_component *component; + struct dmaengine_trcm *trcm; + + component = snd_soc_lookup_component(dev, SND_DMAENGINE_TRCM_DRV_NAME); + if (!component) + return; + + trcm = soc_component_to_trcm(component); + + snd_soc_unregister_component_by_driver(dev, component->driver); + dmaengine_pcm_release_chan(trcm); + kfree(trcm); +} + +static void devm_dmaengine_trcm_release(struct device *dev, void *res) +{ + snd_dmaengine_trcm_unregister(*(struct device **)res); +} + +/** + * devm_snd_dmaengine_trcm_register - resource managed dmaengine TRCM registration + * @dev: The parent device for the TRCM device + * + * Register a dmaengine based TRCM device with automatic unregistration when the + * device is unregistered. + */ +int devm_snd_dmaengine_trcm_register(struct device *dev) +{ + struct device **ptr; + int ret; + + ptr = devres_alloc(devm_dmaengine_trcm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = snd_dmaengine_trcm_register(dev); + if (ret == 0) { + *ptr = dev; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return ret; +} +EXPORT_SYMBOL_GPL(devm_snd_dmaengine_trcm_register); + +MODULE_LICENSE("GPL"); diff --git a/sound/soc/rockchip/rockchip_trcm.h b/sound/soc/rockchip/rockchip_trcm.h new file mode 100644 index 000000000000..6135164dc0b5 --- /dev/null +++ b/sound/soc/rockchip/rockchip_trcm.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Rockchip TRCM Pcm driver + * + * Copyright (C) 2023 Rockchip Electronics Co., Ltd + * Author: Sugar Zhang + * + */ + +#ifndef _ROCKCHIP_TRCM_H +#define _ROCKCHIP_TRCM_H + +#define SND_DMAENGINE_TRCM_DRV_NAME "snd_dmaengine_trcm" + +#if IS_REACHABLE(CONFIG_SND_SOC_ROCKCHIP_TRCM) +int dmaengine_trcm_dma_guard_ctrl(struct snd_soc_component *component, + int stream, bool en); +int devm_snd_dmaengine_trcm_register(struct device *dev); +#else +static inline int dmaengine_trcm_dma_guard_ctrl(struct snd_soc_component *component, + int stream, bool en) +{ + return -ENOSYS; +} + +static inline int devm_snd_dmaengine_trcm_register(struct device *dev) +{ + return -ENOSYS; +} +#endif + +#endif