This is a driver for a block that generates master and bit clocks for the I2S interface. It's separate from the PMUs that generate clocks for the peripherals. Signed-off-by: Lubomir Rintel <lkundrak@xxxxx> --- drivers/clk/Kconfig | 6 + drivers/clk/mmp/Makefile | 1 + drivers/clk/mmp/clk-audio.c | 563 ++++++++++++++++++++++++++++++++++++ 3 files changed, 570 insertions(+) create mode 100644 drivers/clk/mmp/clk-audio.c diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index bcb257baed06..a28cf98ffe68 100644 --- a/drivers/clk/Kconfig +++ b/drivers/clk/Kconfig @@ -326,6 +326,12 @@ config COMMON_CLK_MMP2 help Support for Marvell MMP2 and MMP3 SoC clocks +config COMMON_CLK_MMP2_AUDIO + tristate "Clock driver for MMP2 Audio subsystem" + depends on COMMON_CLK_MMP2 || COMPILE_TEST + help + This driver supports clocks for Audio subsystem on MMP2 SoC. + config COMMON_CLK_BD718XX tristate "Clock driver for 32K clk gates on ROHM PMICs" depends on MFD_ROHM_BD718XX || MFD_ROHM_BD70528 || MFD_ROHM_BD71828 diff --git a/drivers/clk/mmp/Makefile b/drivers/clk/mmp/Makefile index 14dc8a8a9d08..9a4b79ff6572 100644 --- a/drivers/clk/mmp/Makefile +++ b/drivers/clk/mmp/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_RESET_CONTROLLER) += reset.o obj-$(CONFIG_MACH_MMP_DT) += clk-of-pxa168.o clk-of-pxa910.o obj-$(CONFIG_COMMON_CLK_MMP2) += clk-of-mmp2.o clk-pll.o +obj-$(CONFIG_COMMON_CLK_MMP2_AUDIO) += clk-audio.o obj-$(CONFIG_CPU_PXA168) += clk-pxa168.o obj-$(CONFIG_CPU_PXA910) += clk-pxa910.o diff --git a/drivers/clk/mmp/clk-audio.c b/drivers/clk/mmp/clk-audio.c new file mode 100644 index 000000000000..ee89b97dc09a --- /dev/null +++ b/drivers/clk/mmp/clk-audio.c @@ -0,0 +1,563 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * MMP Audio Clock Controller driver + * + * Copyright (C) 2020 Lubomir Rintel <lkundrak@xxxxx> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <dt-bindings/clock/marvell,mmp2-audio.h> + +enum { + SSPA_AUD_CTRL = 0x04, + SSPA_AUD_PLL_CTRL0 = 0x08, + SSPA_AUD_PLL_CTRL1 = 0x0c, +}; + +/* SSPA Audio Control Register */ +#define SSPA_AUD_CTRL_SYSCLK_SHIFT 0 +#define SSPA_AUD_CTRL_SSPA0_MUX_SHIFT 7 +#define SSPA_AUD_CTRL_SSPA0_SHIFT 8 +#define SSPA_AUD_CTRL_SSPA1_SHIFT 16 +#define SSPA_AUD_CTRL_SSPA1_MUX_SHIFT 23 +#define SSPA_AUD_CTRL_DIV_MASK 0x7e + +/* SSPA Audio PLL Control 0 Register */ +#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO_MASK (0x7 << 28) +#define SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(x) ((x) << 28) +#define SSPA_AUD_PLL_CTRL0_FRACT_MASK (0xfffff << 8) +#define SSPA_AUD_PLL_CTRL0_FRACT(x) ((x) << 8) +#define SSPA_AUD_PLL_CTRL0_ENA_DITHER (1 << 7) +#define SSPA_AUD_PLL_CTRL0_ICP_2UA (0 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_5UA (1 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_7UA (2 << 5) +#define SSPA_AUD_PLL_CTRL0_ICP_10UA (3 << 5) +#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK_MASK (0x3 << 3) +#define SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(x) ((x) << 3) +#define SSPA_AUD_PLL_CTRL0_DIV_MCLK_MASK (0x1 << 2) +#define SSPA_AUD_PLL_CTRL0_DIV_MCLK(x) ((x) << 2) +#define SSPA_AUD_PLL_CTRL0_PD_OVPROT_DIS (1 << 1) +#define SSPA_AUD_PLL_CTRL0_PU (1 << 0) + +/* SSPA Audio PLL Control 1 Register */ +#define SSPA_AUD_PLL_CTRL1_SEL_FAST_CLK (1 << 24) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_MASK (1 << 11) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL (1 << 11) +#define SSPA_AUD_PLL_CTRL1_CLK_SEL_VCXO (0 << 11) +#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN_MASK (0x7ff << 0) +#define SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(x) ((x) << 0) + +struct mmp_audio_mux { + struct clk_hw hw; + struct mmp2_audio_clk *priv; + u8 shift; + u8 flags; + int index; +}; + +#define to_mmp_audio_mux(_hw) container_of(_hw, struct mmp_audio_mux, hw) + +struct mmp_audio_div { + struct clk_hw hw; + struct mmp2_audio_clk *priv; + u8 shift; + int value; +}; + +#define to_mmp_audio_div(_hw) container_of(_hw, struct mmp_audio_div, hw) + +struct mmp_audio_pll { + struct clk_hw hw; + struct mmp2_audio_clk *priv; + u32 ctrl0; + u32 ctrl1; +}; + +#define to_mmp_audio_pll(_hw) container_of(_hw, struct mmp_audio_pll, hw) + +struct mmp2_audio_clk { + void __iomem *mmio_base; + + struct clk *audio_clk; + struct clk *vctcxo_clk; + struct clk *i2s0_clk; + struct clk *i2s1_clk; + + struct mmp_audio_pll audio_pll; + struct mmp_audio_mux sspa_mux; + struct mmp_audio_mux sspa1_mux; + struct mmp_audio_div sysclk; + struct mmp_audio_div sspa0; + struct mmp_audio_div sspa1; + + struct clk *clk_table[3]; + struct clk_onecell_data clk_data; + + spinlock_t lock; +}; + +static struct { + unsigned long parent_rate; + unsigned long freq_vco; + unsigned char mclk; + unsigned char fbcclk; + unsigned short fract; +} predivs[] = { + { 26000000, 135475200, 0, 0, 0x8a18 }, + { 26000000, 147456000, 0, 1, 0x0da1 }, + { 38400000, 135475200, 1, 2, 0x8208 }, + { 38400000, 147456000, 1, 3, 0xaaaa }, +}; + +static struct { + unsigned char divisor; + unsigned char modulo; + unsigned char pattern; +} postdivs[] = { + { 1, 3, 0, }, + { 2, 5, 0, }, + { 4, 0, 0, }, + { 6, 1, 1, }, + { 8, 1, 0, }, + { 9, 1, 2, }, + { 12, 2, 1, }, + { 16, 2, 0, }, + { 18, 2, 2, }, + { 24, 4, 1, }, + { 36, 4, 2, }, + { 48, 6, 1, }, + { 72, 6, 2, }, +}; + +static unsigned long mmp_audio_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp_audio_pll *pll = to_mmp_audio_pll(hw); + unsigned int prediv; + unsigned int postdiv; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != parent_rate) + continue; + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + unsigned long freq; + u32 val; + + val = SSPA_AUD_PLL_CTRL0_ENA_DITHER; + val |= SSPA_AUD_PLL_CTRL0_PU; + val |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo); + val |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract); + val |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk); + val |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk); + if (val != pll->ctrl0) + continue; + + val = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL; + val |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern); + if (val != pll->ctrl1) + continue; + + freq = predivs[prediv].freq_vco; + freq /= postdivs[postdiv].divisor; + return freq; + } + } + + return 0; +} + +static long mmp_audio_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned int prediv; + unsigned int postdiv; + long rounded = 0; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != *parent_rate) + continue; + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + long freq = predivs[prediv].freq_vco; + + freq /= postdivs[postdiv].divisor; + if (freq == rate) + return rate; + if (freq < rate) + continue; + if (rounded && freq > rounded) + continue; + rounded = freq; + } + } + + return rounded; +} + +static void mmp_audio_pll_write_rate(struct mmp_audio_pll *pll) +{ + struct mmp2_audio_clk *priv = pll->priv; + + __raw_writel(pll->ctrl0, priv->mmio_base + SSPA_AUD_PLL_CTRL0); + __raw_writel(pll->ctrl1, priv->mmio_base + SSPA_AUD_PLL_CTRL1); +} + +static int mmp_audio_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mmp_audio_pll *pll = to_mmp_audio_pll(hw); + struct mmp2_audio_clk *priv = pll->priv; + unsigned int prediv; + unsigned int postdiv; + + for (prediv = 0; prediv < ARRAY_SIZE(predivs); prediv++) { + if (predivs[prediv].parent_rate != parent_rate) + continue; + + for (postdiv = 0; postdiv < ARRAY_SIZE(postdivs); postdiv++) { + if (rate * postdivs[postdiv].divisor != predivs[prediv].freq_vco) + continue; + + pll->ctrl0 = SSPA_AUD_PLL_CTRL0_ENA_DITHER; + pll->ctrl0 |= SSPA_AUD_PLL_CTRL0_PU; + pll->ctrl0 |= SSPA_AUD_PLL_CTRL0_DIV_OCLK_MODULO(postdivs[postdiv].modulo); + pll->ctrl0 |= SSPA_AUD_PLL_CTRL0_FRACT(predivs[prediv].fract); + pll->ctrl0 |= SSPA_AUD_PLL_CTRL0_DIV_FBCCLK(predivs[prediv].fbcclk); + pll->ctrl0 |= SSPA_AUD_PLL_CTRL0_DIV_MCLK(predivs[prediv].mclk); + + pll->ctrl1 = SSPA_AUD_PLL_CTRL1_CLK_SEL_AUDIO_PLL; + pll->ctrl1 |= SSPA_AUD_PLL_CTRL1_DIV_OCLK_PATTERN(postdivs[postdiv].pattern); + + if (__clk_is_enabled(priv->audio_clk)) + mmp_audio_pll_write_rate(pll); + + return 0; + } + } + + return -ERANGE; +} + +static int mmp_audio_pll_enable(struct clk_hw *hw) +{ + struct mmp_audio_pll *pll = to_mmp_audio_pll(hw); + struct mmp2_audio_clk *priv = pll->priv; + + clk_prepare_enable(priv->audio_clk); + mmp_audio_pll_write_rate(pll); + return 0; +} + +static void mmp_audio_pll_disable(struct clk_hw *hw) +{ + struct mmp_audio_pll *pll = to_mmp_audio_pll(hw); + struct mmp2_audio_clk *priv = pll->priv; + + clk_disable_unprepare(priv->audio_clk); +} + +const struct clk_ops mmp_audio_pll_ops = { + .enable = mmp_audio_pll_enable, + .disable = mmp_audio_pll_disable, + .recalc_rate = mmp_audio_pll_recalc_rate, + .round_rate = mmp_audio_pll_round_rate, + .set_rate = mmp_audio_pll_set_rate, +}; + +static u8 mmp_audio_mux_get_parent(struct clk_hw *hw) +{ + struct mmp_audio_mux *mux = to_mmp_audio_mux(hw); + struct mmp2_audio_clk *priv = mux->priv; + u32 val; + + if (__clk_is_enabled(priv->audio_clk)) { + val = __raw_readl(priv->mmio_base + SSPA_AUD_CTRL); + val >>= mux->shift; + val &= 1; + } else { + val = 0; + } + mux->index = val; + + return mux->index; +} + +static void mmp_audio_mux_write_parent(struct mmp_audio_mux *mux) +{ + struct mmp2_audio_clk *priv = mux->priv; + unsigned long flags = 0; + u32 reg; + + spin_lock_irqsave(&priv->lock, flags); + reg = __raw_readl(priv->mmio_base + SSPA_AUD_CTRL); + reg &= ~(1 << mux->shift); + reg |= mux->index << mux->shift; + __raw_writel(reg, priv->mmio_base + SSPA_AUD_CTRL); + spin_unlock_irqrestore(&priv->lock, flags); +} + +static int mmp_audio_mux_set_parent(struct clk_hw *hw, u8 index) +{ + struct mmp_audio_mux *mux = to_mmp_audio_mux(hw); + struct mmp2_audio_clk *priv = mux->priv; + + mux->index = index; + if (__clk_is_enabled(priv->audio_clk)) + mmp_audio_mux_write_parent(mux); + + return 0; +} + +static int mmp_audio_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + return clk_mux_determine_rate_flags(hw, req, 0); +} + +static int mmp_audio_mux_enable(struct clk_hw *hw) +{ + struct mmp_audio_mux *mux = to_mmp_audio_mux(hw); + struct mmp2_audio_clk *priv = mux->priv; + + clk_prepare_enable(priv->audio_clk); + mmp_audio_mux_write_parent(mux); + return 0; +} + +static void mmp_audio_mux_disable(struct clk_hw *hw) +{ + struct mmp_audio_mux *mux = to_mmp_audio_mux(hw); + struct mmp2_audio_clk *priv = mux->priv; + + clk_disable_unprepare(priv->audio_clk); +} + +const struct clk_ops mmp_audio_mux_ops = { + .enable = mmp_audio_mux_enable, + .disable = mmp_audio_mux_disable, + + .get_parent = mmp_audio_mux_get_parent, + .set_parent = mmp_audio_mux_set_parent, + .determine_rate = mmp_audio_mux_determine_rate, +}; + +static unsigned long mmp_audio_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mmp_audio_div *div = to_mmp_audio_div(hw); + + if (div->value == 0) + return 0; + + return divider_recalc_rate(hw, parent_rate, div->value, NULL, + CLK_DIVIDER_ONE_BASED | + CLK_DIVIDER_ROUND_CLOSEST | + CLK_DIVIDER_ALLOW_ZERO, 6); +} + +static long mmp_audio_div_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *prate) +{ + return divider_round_rate(hw, rate, prate, NULL, 6, + CLK_DIVIDER_ONE_BASED | + CLK_DIVIDER_ROUND_CLOSEST | + CLK_DIVIDER_ALLOW_ZERO); +} + +static void mmp_audio_div_write_rate(struct mmp_audio_div *div, int enable) +{ + struct mmp2_audio_clk *priv = div->priv; + u32 val; + + clk_prepare_enable(priv->audio_clk); + val = __raw_readl(priv->mmio_base + SSPA_AUD_CTRL); + val &= ~(SSPA_AUD_CTRL_DIV_MASK << div->shift); + val |= (u32)div->value << (div->shift + 1); + val |= enable << div->shift; + __raw_writel(val, priv->mmio_base + SSPA_AUD_CTRL); + clk_disable_unprepare(priv->audio_clk); +} + +static int mmp_audio_div_enable(struct clk_hw *hw) +{ + struct mmp_audio_div *div = to_mmp_audio_div(hw); + struct mmp2_audio_clk *priv = div->priv; + + clk_prepare_enable(priv->audio_clk); + mmp_audio_div_write_rate(div, 1); + return 0; +} + +static void mmp_audio_div_disable(struct clk_hw *hw) +{ + struct mmp_audio_div *div = to_mmp_audio_div(hw); + struct mmp2_audio_clk *priv = div->priv; + unsigned long flags = 0; + u32 val; + + spin_lock_irqsave(&priv->lock, flags); + val = __raw_readl(priv->mmio_base + SSPA_AUD_CTRL); + val &= ~(1 << div->shift); + __raw_writel(val, priv->mmio_base + SSPA_AUD_CTRL); + spin_unlock_irqrestore(&priv->lock, flags); + clk_disable_unprepare(priv->audio_clk); +} + +static int mmp_audio_div_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mmp_audio_div *div = to_mmp_audio_div(hw); + struct mmp2_audio_clk *priv = div->priv; + int value; + + if (rate == 0) { + value = 0; + } else { + value = divider_get_val(rate, parent_rate, NULL, 6, + CLK_DIVIDER_ONE_BASED | + CLK_DIVIDER_ROUND_CLOSEST | + CLK_DIVIDER_ALLOW_ZERO); + if (value < 0) + return value; + } + + div->value = value; + if (__clk_is_enabled(priv->audio_clk)) + mmp_audio_div_write_rate(div, 0); + + return 0; +} + +const struct clk_ops mmp_audio_div_ops = { + .enable = mmp_audio_div_enable, + .disable = mmp_audio_div_disable, + + .recalc_rate = mmp_audio_div_recalc_rate, + .round_rate = mmp_audio_div_round_rate, + .set_rate = mmp_audio_div_set_rate, +}; + +static int mmp2_audio_clk_probe(struct platform_device *pdev) +{ + const struct clk_hw *sspa_mux_parents[2]; + const struct clk_hw *sspa1_mux_parents[2]; + struct mmp2_audio_clk *priv; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + spin_lock_init(&priv->lock); + + priv->mmio_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->mmio_base)) + return PTR_ERR(priv->mmio_base); + + priv->audio_clk = devm_clk_get(&pdev->dev, "audio"); + if (IS_ERR(priv->audio_clk)) + return PTR_ERR(priv->audio_clk); + + priv->i2s0_clk = devm_clk_get(&pdev->dev, "i2s0"); + if (IS_ERR(priv->i2s0_clk)) + return PTR_ERR(priv->i2s0_clk); + + priv->i2s1_clk = devm_clk_get(&pdev->dev, "i2s1"); + if (IS_ERR(priv->i2s1_clk)) + return PTR_ERR(priv->i2s1_clk); + + priv->vctcxo_clk = devm_clk_get(&pdev->dev, "vctcxo"); + if (IS_ERR(priv->vctcxo_clk)) + return PTR_ERR(priv->vctcxo_clk); + + priv->audio_pll.hw.init = + CLK_HW_INIT_HW("audio_pll", __clk_get_hw(priv->vctcxo_clk), + &mmp_audio_pll_ops, CLK_SET_RATE_PARENT); + priv->audio_pll.priv = priv; + ret = devm_clk_hw_register(&pdev->dev, &priv->audio_pll.hw); + if (ret) + return ret; + + sspa_mux_parents[0] = &priv->audio_pll.hw; + sspa_mux_parents[1] = __clk_get_hw(priv->i2s0_clk); + priv->sspa_mux.hw.init = + CLK_HW_INIT_PARENTS_HW("sspa_mux", sspa_mux_parents, + &mmp_audio_mux_ops, CLK_SET_RATE_PARENT); + priv->sspa_mux.priv = priv; + priv->sspa_mux.shift = SSPA_AUD_CTRL_SSPA0_MUX_SHIFT; + ret = devm_clk_hw_register(&pdev->dev, &priv->sspa_mux.hw); + if (ret) + return ret; + + priv->sysclk.hw.init = + CLK_HW_INIT_HW("sysclk", &priv->sspa_mux.hw, + &mmp_audio_div_ops, CLK_SET_RATE_PARENT); + priv->sysclk.priv = priv; + priv->sysclk.shift = SSPA_AUD_CTRL_SYSCLK_SHIFT; + ret = devm_clk_hw_register(&pdev->dev, &priv->sysclk.hw); + if (ret) + return ret; + + priv->sspa0.hw.init = + CLK_HW_INIT_HW("sspa0", &priv->sspa_mux.hw, + &mmp_audio_div_ops, 0); + priv->sspa0.priv = priv; + priv->sspa0.shift = SSPA_AUD_CTRL_SSPA0_SHIFT; + ret = devm_clk_hw_register(&pdev->dev, &priv->sspa0.hw); + if (ret) + return ret; + + sspa1_mux_parents[0] = &priv->audio_pll.hw; + sspa1_mux_parents[1] = __clk_get_hw(priv->i2s1_clk); + priv->sspa1_mux.hw.init = + CLK_HW_INIT_PARENTS_HW("sspa1_mux", sspa1_mux_parents, + &mmp_audio_mux_ops, CLK_SET_RATE_PARENT); + priv->sspa1_mux.priv = priv; + priv->sspa1_mux.shift = SSPA_AUD_CTRL_SSPA1_MUX_SHIFT; + ret = devm_clk_hw_register(&pdev->dev, &priv->sspa1_mux.hw); + if (ret) + return ret; + + priv->sspa1.hw.init = + CLK_HW_INIT_HW("sspa1", &priv->sspa1_mux.hw, + &mmp_audio_div_ops, 0); + priv->sspa1.priv = priv; + priv->sspa1.shift = SSPA_AUD_CTRL_SSPA1_SHIFT; + ret = devm_clk_hw_register(&pdev->dev, &priv->sspa1.hw); + if (ret) + return ret; + + priv->clk_table[MMP2_CLK_AUDIO_SYSCLK] = priv->sysclk.hw.clk; + priv->clk_table[MMP2_CLK_AUDIO_SSPA0] = priv->sspa0.hw.clk; + priv->clk_table[MMP2_CLK_AUDIO_SSPA1] = priv->sspa1.hw.clk; + priv->clk_data.clks = priv->clk_table; + priv->clk_data.clk_num = ARRAY_SIZE(priv->clk_table); + + return of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get, + &priv->clk_data); +} + +static const struct of_device_id mmp2_audio_clk_of_match[] = { + { .compatible = "marvell,mmp2-audio-clock" }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mmp2_audio_clk_of_match); + +static struct platform_driver mmp2_audio_clk_driver = { + .driver = { + .name = "mmp2-audio-clock", + .of_match_table = of_match_ptr(mmp2_audio_clk_of_match), + }, + .probe = mmp2_audio_clk_probe, +}; + +module_platform_driver(mmp2_audio_clk_driver); + +MODULE_AUTHOR("Lubomir Rintel <lkundrak@xxxxx>"); +MODULE_DESCRIPTION("Clock driver for MMP2 Audio subsystem"); +MODULE_LICENSE("GPL"); -- 2.26.2