diff mbox series

[2/2] clk: mmp2: Add audio clock controller driver

Message ID 20200511195534.1207927-3-lkundrak@v3.sk
State New
Headers show
Series None | expand

Commit Message

Lubomir Rintel May 11, 2020, 7:55 p.m. UTC
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@v3.sk>
---
 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 mbox series

Patch

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@v3.sk>
+ */
+
+#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@v3.sk>");
+MODULE_DESCRIPTION("Clock driver for MMP2 Audio subsystem");
+MODULE_LICENSE("GPL");