Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a507c57d authored by Michael Turquette's avatar Michael Turquette
Browse files

Merge tag 'meson-clk-for-4.12' of git://github.com/BayLibre/clk-meson into clk-next

Pull AmLogic clk driver updates from Jerome Brunet:

2nd Amlogic clock driver update for 4.12:
* Protect against holes in onecell_data
* Fix divison by zero and overflow in the mpll driver
* Add audio clock divider driver for i2s clocks
* Add i2s and spdif master clocks
parents 0d7a5328 b609338b
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -1069,6 +1069,16 @@ F: drivers/pinctrl/meson/
F:	drivers/mmc/host/meson*
N:	meson

ARM/Amlogic Meson SoC CLOCK FRAMEWORK
M:	Neil Armstrong <narmstrong@baylibre.com>
M:	Jerome Brunet <jbrunet@baylibre.com>
L:	linux-amlogic@lists.infradead.org
S:	Maintained
F:	drivers/clk/meson/
F:	include/dt-bindings/clock/meson*
F:	include/dt-bindings/clock/gxbb*
F:	Documentation/devicetree/bindings/clock/amlogic*

ARM/Annapurna Labs ALPINE ARCHITECTURE
M:	Tsahee Zidenberg <tsahee@annapurnalabs.com>
M:	Antoine Tenart <antoine.tenart@free-electrons.com>
+1 −1
Original line number Diff line number Diff line
@@ -2,6 +2,6 @@
# Makefile for Meson specific clk
#

obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o
obj-$(CONFIG_COMMON_CLK_AMLOGIC) += clk-pll.o clk-cpu.o clk-mpll.o clk-audio-divider.o
obj-$(CONFIG_COMMON_CLK_MESON8B) += meson8b.o
obj-$(CONFIG_COMMON_CLK_GXBB)	 += gxbb.o gxbb-aoclk.o
+144 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2017 AmLogic, Inc.
 * Author: Jerome Brunet <jbrunet@baylibre.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * i2s master clock divider: The algorithm of the generic clk-divider used with
 * a very precise clock parent such as the mpll tends to select a low divider
 * factor. This gives poor results with this particular divider, especially with
 * high frequencies (> 100 MHz)
 *
 * This driver try to select the maximum possible divider with the rate the
 * upstream clock can provide.
 */

#include <linux/clk-provider.h>
#include "clkc.h"

#define to_meson_clk_audio_divider(_hw) container_of(_hw, \
				struct meson_clk_audio_divider, hw)

static int _div_round(unsigned long parent_rate, unsigned long rate,
		      unsigned long flags)
{
	if (flags & CLK_DIVIDER_ROUND_CLOSEST)
		return DIV_ROUND_CLOSEST_ULL((u64)parent_rate, rate);

	return DIV_ROUND_UP_ULL((u64)parent_rate, rate);
}

static int _get_val(unsigned long parent_rate, unsigned long rate)
{
	return DIV_ROUND_UP_ULL((u64)parent_rate, rate) - 1;
}

static int _valid_divider(struct clk_hw *hw, int divider)
{
	struct meson_clk_audio_divider *adiv =
		to_meson_clk_audio_divider(hw);
	int max_divider;
	u8 width;

	width = adiv->div.width;
	max_divider = 1 << width;

	return clamp(divider, 1, max_divider);
}

static unsigned long audio_divider_recalc_rate(struct clk_hw *hw,
					       unsigned long parent_rate)
{
	struct meson_clk_audio_divider *adiv =
		to_meson_clk_audio_divider(hw);
	struct parm *p;
	unsigned long reg, divider;

	p = &adiv->div;
	reg = readl(adiv->base + p->reg_off);
	divider = PARM_GET(p->width, p->shift, reg) + 1;

	return DIV_ROUND_UP_ULL((u64)parent_rate, divider);
}

static long audio_divider_round_rate(struct clk_hw *hw,
				     unsigned long rate,
				     unsigned long *parent_rate)
{
	struct meson_clk_audio_divider *adiv =
		to_meson_clk_audio_divider(hw);
	unsigned long max_prate;
	int divider;

	if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
		divider = _div_round(*parent_rate, rate, adiv->flags);
		divider = _valid_divider(hw, divider);
		return DIV_ROUND_UP_ULL((u64)*parent_rate, divider);
	}

	/* Get the maximum parent rate */
	max_prate = clk_hw_round_rate(clk_hw_get_parent(hw), ULONG_MAX);

	/* Get the corresponding rounded down divider */
	divider = max_prate / rate;
	divider = _valid_divider(hw, divider);

	/* Get actual rate of the parent */
	*parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
					 divider * rate);

	return DIV_ROUND_UP_ULL((u64)*parent_rate, divider);
}

static int audio_divider_set_rate(struct clk_hw *hw,
				  unsigned long rate,
				  unsigned long parent_rate)
{
	struct meson_clk_audio_divider *adiv =
		to_meson_clk_audio_divider(hw);
	struct parm *p;
	unsigned long reg, flags = 0;
	int val;

	val = _get_val(parent_rate, rate);

	if (adiv->lock)
		spin_lock_irqsave(adiv->lock, flags);
	else
		__acquire(adiv->lock);

	p = &adiv->div;
	reg = readl(adiv->base + p->reg_off);
	reg = PARM_SET(p->width, p->shift, reg, val);
	writel(reg, adiv->base + p->reg_off);

	if (adiv->lock)
		spin_unlock_irqrestore(adiv->lock, flags);
	else
		__release(adiv->lock);

	return 0;
}

const struct clk_ops meson_clk_audio_divider_ro_ops = {
	.recalc_rate	= audio_divider_recalc_rate,
	.round_rate	= audio_divider_round_rate,
};

const struct clk_ops meson_clk_audio_divider_ops = {
	.recalc_rate	= audio_divider_recalc_rate,
	.round_rate	= audio_divider_round_rate,
	.set_rate	= audio_divider_set_rate,
};
+15 −11
Original line number Diff line number Diff line
@@ -65,18 +65,21 @@
#include "clkc.h"

#define SDM_DEN 16384
#define SDM_MIN 1
#define SDM_MAX 16383
#define N2_MIN	4
#define N2_MAX	511

#define to_meson_clk_mpll(_hw) container_of(_hw, struct meson_clk_mpll, hw)

static unsigned long rate_from_params(unsigned long parent_rate,
static long rate_from_params(unsigned long parent_rate,
				      unsigned long sdm,
				      unsigned long n2)
{
	return (parent_rate * SDM_DEN) / ((SDM_DEN * n2) + sdm);
	unsigned long divisor = (SDM_DEN * n2) + sdm;

	if (n2 < N2_MIN)
		return -EINVAL;

	return DIV_ROUND_UP_ULL((u64)parent_rate * SDM_DEN, divisor);
}

static void params_from_rate(unsigned long requested_rate,
@@ -89,17 +92,13 @@ static void params_from_rate(unsigned long requested_rate,

	if (div < N2_MIN) {
		*n2 = N2_MIN;
		*sdm = SDM_MIN;
		*sdm = 0;
	} else if (div > N2_MAX) {
		*n2 = N2_MAX;
		*sdm = SDM_MAX;
		*sdm = SDM_DEN - 1;
	} else {
		*n2 = div;
		*sdm = DIV_ROUND_UP(rem * SDM_DEN, requested_rate);
		if (*sdm < SDM_MIN)
			*sdm = SDM_MIN;
		else if (*sdm > SDM_MAX)
			*sdm = SDM_MAX;
	}
}

@@ -109,6 +108,7 @@ static unsigned long mpll_recalc_rate(struct clk_hw *hw,
	struct meson_clk_mpll *mpll = to_meson_clk_mpll(hw);
	struct parm *p;
	unsigned long reg, sdm, n2;
	long rate;

	p = &mpll->sdm;
	reg = readl(mpll->base + p->reg_off);
@@ -118,7 +118,11 @@ static unsigned long mpll_recalc_rate(struct clk_hw *hw,
	reg = readl(mpll->base + p->reg_off);
	n2 = PARM_GET(p->width, p->shift, reg);

	return rate_from_params(parent_rate, sdm, n2);
	rate = rate_from_params(parent_rate, sdm, n2);
	if (rate < 0)
		return 0;

	return rate;
}

static long mpll_round_rate(struct clk_hw *hw,
+10 −0
Original line number Diff line number Diff line
@@ -121,6 +121,14 @@ struct meson_clk_mpll {
	spinlock_t *lock;
};

struct meson_clk_audio_divider {
	struct clk_hw hw;
	void __iomem *base;
	struct parm div;
	u8 flags;
	spinlock_t *lock;
};

#define MESON_GATE(_name, _reg, _bit)					\
struct clk_gate _name = { 						\
	.reg = (void __iomem *) _reg, 					\
@@ -141,5 +149,7 @@ extern const struct clk_ops meson_clk_pll_ops;
extern const struct clk_ops meson_clk_cpu_ops;
extern const struct clk_ops meson_clk_mpll_ro_ops;
extern const struct clk_ops meson_clk_mpll_ops;
extern const struct clk_ops meson_clk_audio_divider_ro_ops;
extern const struct clk_ops meson_clk_audio_divider_ops;

#endif /* __CLKC_H */
Loading