diff --git a/drivers/clk/meson/Makefile b/drivers/clk/meson/Makefile
index 74ec070b0974..1574e3f0de5c 100644
--- a/drivers/clk/meson/Makefile
+++ b/drivers/clk/meson/Makefile
@@ -4,7 +4,7 @@
 
 obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-mpll.o clk-audio-divider.o
 obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-phase.o
-obj-$(CONFIG_COMMON_CLK_AMLOGIC_AUDIO)	+= clk-triphase.o
+obj-$(CONFIG_COMMON_CLK_AMLOGIC_AUDIO)	+= clk-triphase.o sclk-div.o
 obj-$(CONFIG_COMMON_CLK_MESON_AO) += meson-aoclk.o
 obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o
 obj-$(CONFIG_COMMON_CLK_GXBB)	 += gxbb.o gxbb-aoclk.o gxbb-aoclk-32k.o
diff --git a/drivers/clk/meson/clkc-audio.h b/drivers/clk/meson/clkc-audio.h
index 286ff1201258..0a7c157ebf81 100644
--- a/drivers/clk/meson/clkc-audio.h
+++ b/drivers/clk/meson/clkc-audio.h
@@ -15,6 +15,14 @@ struct meson_clk_triphase_data {
 	struct parm ph2;
 };
 
+struct meson_sclk_div_data {
+	struct parm div;
+	struct parm hi;
+	unsigned int cached_div;
+	struct clk_duty cached_duty;
+};
+
 extern const struct clk_ops meson_clk_triphase_ops;
+extern const struct clk_ops meson_sclk_div_ops;
 
 #endif /* __MESON_CLKC_AUDIO_H */
diff --git a/drivers/clk/meson/sclk-div.c b/drivers/clk/meson/sclk-div.c
new file mode 100644
index 000000000000..bc64019b8eeb
--- /dev/null
+++ b/drivers/clk/meson/sclk-div.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: (GPL-2.0 OR MIT)
+/*
+ * Copyright (c) 2018 BayLibre, SAS.
+ * Author: Jerome Brunet <jbrunet@baylibre.com>
+ *
+ * Sample clock generator divider:
+ * This HW divider gates with value 0 but is otherwise a zero based divider:
+ *
+ * val >= 1
+ * divider = val + 1
+ *
+ * The duty cycle may also be set for the LR clock variant. The duty cycle
+ * ratio is:
+ *
+ * hi = [0 - val]
+ * duty_cycle = (1 + hi) / (1 + val)
+ */
+
+#include "clkc-audio.h"
+
+static inline struct meson_sclk_div_data *
+meson_sclk_div_data(struct clk_regmap *clk)
+{
+	return (struct meson_sclk_div_data *)clk->data;
+}
+
+static int sclk_div_maxval(struct meson_sclk_div_data *sclk)
+{
+	return (1 << sclk->div.width) - 1;
+}
+
+static int sclk_div_maxdiv(struct meson_sclk_div_data *sclk)
+{
+	return sclk_div_maxval(sclk) + 1;
+}
+
+static int sclk_div_getdiv(struct clk_hw *hw, unsigned long rate,
+			   unsigned long prate, int maxdiv)
+{
+	int div = DIV_ROUND_CLOSEST_ULL((u64)prate, rate);
+
+	return clamp(div, 2, maxdiv);
+}
+
+static int sclk_div_bestdiv(struct clk_hw *hw, unsigned long rate,
+			    unsigned long *prate,
+			    struct meson_sclk_div_data *sclk)
+{
+	struct clk_hw *parent = clk_hw_get_parent(hw);
+	int bestdiv = 0, i;
+	unsigned long maxdiv, now, parent_now;
+	unsigned long best = 0, best_parent = 0;
+
+	if (!rate)
+		rate = 1;
+
+	maxdiv = sclk_div_maxdiv(sclk);
+
+	if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT))
+		return sclk_div_getdiv(hw, rate, *prate, maxdiv);
+
+	/*
+	 * The maximum divider we can use without overflowing
+	 * unsigned long in rate * i below
+	 */
+	maxdiv = min(ULONG_MAX / rate, maxdiv);
+
+	for (i = 2; i <= maxdiv; i++) {
+		/*
+		 * It's the most ideal case if the requested rate can be
+		 * divided from parent clock without needing to change
+		 * parent rate, so return the divider immediately.
+		 */
+		if (rate * i == *prate)
+			return i;
+
+		parent_now = clk_hw_round_rate(parent, rate * i);
+		now = DIV_ROUND_UP_ULL((u64)parent_now, i);
+
+		if (abs(rate - now) < abs(rate - best)) {
+			bestdiv = i;
+			best = now;
+			best_parent = parent_now;
+		}
+	}
+
+	if (!bestdiv)
+		bestdiv = sclk_div_maxdiv(sclk);
+	else
+		*prate = best_parent;
+
+	return bestdiv;
+}
+
+static long sclk_div_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *prate)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+	int div;
+
+	div = sclk_div_bestdiv(hw, rate, prate, sclk);
+
+	return DIV_ROUND_UP_ULL((u64)*prate, div);
+}
+
+static void sclk_apply_ratio(struct clk_regmap *clk,
+			     struct meson_sclk_div_data *sclk)
+{
+	unsigned int hi = DIV_ROUND_CLOSEST(sclk->cached_div *
+					    sclk->cached_duty.num,
+					    sclk->cached_duty.den);
+
+	if (hi)
+		hi -= 1;
+
+	meson_parm_write(clk->map, &sclk->hi, hi);
+}
+
+static int sclk_div_set_duty_cycle(struct clk_hw *hw,
+				   struct clk_duty *duty)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+
+	if (MESON_PARM_APPLICABLE(&sclk->hi)) {
+		memcpy(&sclk->cached_duty, duty, sizeof(*duty));
+		sclk_apply_ratio(clk, sclk);
+	}
+
+	return 0;
+}
+
+static int sclk_div_get_duty_cycle(struct clk_hw *hw,
+				   struct clk_duty *duty)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+	int hi;
+
+	if (!MESON_PARM_APPLICABLE(&sclk->hi)) {
+		duty->num = 1;
+		duty->den = 2;
+		return 0;
+	}
+
+	hi = meson_parm_read(clk->map, &sclk->hi);
+	duty->num = hi + 1;
+	duty->den = sclk->cached_div;
+	return 0;
+}
+
+static void sclk_apply_divider(struct clk_regmap *clk,
+			       struct meson_sclk_div_data *sclk)
+{
+	if (MESON_PARM_APPLICABLE(&sclk->hi))
+		sclk_apply_ratio(clk, sclk);
+
+	meson_parm_write(clk->map, &sclk->div, sclk->cached_div - 1);
+}
+
+static int sclk_div_set_rate(struct clk_hw *hw, unsigned long rate,
+			     unsigned long prate)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+	unsigned long maxdiv = sclk_div_maxdiv(sclk);
+
+	sclk->cached_div = sclk_div_getdiv(hw, rate, prate, maxdiv);
+
+	if (clk_hw_is_enabled(hw))
+		sclk_apply_divider(clk, sclk);
+
+	return 0;
+}
+
+static unsigned long sclk_div_recalc_rate(struct clk_hw *hw,
+					  unsigned long prate)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+
+	return DIV_ROUND_UP_ULL((u64)prate, sclk->cached_div);
+}
+
+static int sclk_div_enable(struct clk_hw *hw)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+
+	sclk_apply_divider(clk, sclk);
+
+	return 0;
+}
+
+static void sclk_div_disable(struct clk_hw *hw)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+
+	meson_parm_write(clk->map, &sclk->div, 0);
+}
+
+static int sclk_div_is_enabled(struct clk_hw *hw)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+
+	if (meson_parm_read(clk->map, &sclk->div))
+		return 1;
+
+	return 0;
+}
+
+static void sclk_div_init(struct clk_hw *hw)
+{
+	struct clk_regmap *clk = to_clk_regmap(hw);
+	struct meson_sclk_div_data *sclk = meson_sclk_div_data(clk);
+	unsigned int val;
+
+	val = meson_parm_read(clk->map, &sclk->div);
+
+	/* if the divider is initially disabled, assume max */
+	if (!val)
+		sclk->cached_div = sclk_div_maxdiv(sclk);
+	else
+		sclk->cached_div = val + 1;
+
+	sclk_div_get_duty_cycle(hw, &sclk->cached_duty);
+}
+
+const struct clk_ops meson_sclk_div_ops = {
+	.recalc_rate	= sclk_div_recalc_rate,
+	.round_rate	= sclk_div_round_rate,
+	.set_rate	= sclk_div_set_rate,
+	.enable		= sclk_div_enable,
+	.disable	= sclk_div_disable,
+	.is_enabled	= sclk_div_is_enabled,
+	.get_duty_cycle	= sclk_div_get_duty_cycle,
+	.set_duty_cycle = sclk_div_set_duty_cycle,
+	.init		= sclk_div_init,
+};
+EXPORT_SYMBOL_GPL(meson_sclk_div_ops);
