blob: 0850ba2e26e53db4a80f3a8b00b2a199727d2402 [file] [log] [blame]
/*
* Copyright (C) 2016 Freescale Semiconductor, Inc.
*
* Based on driver/clk/clk-fractional-divider.c
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/bitops.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/slab.h>
#include <linux/rational.h>
#include "clk.h"
#define to_clk_frac_divider(_hw) container_of(_hw, struct clk_frac_divider, hw)
static unsigned long clk_frac_divider_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct clk_frac_divider *fd = to_clk_frac_divider(hw);
u32 val, m, n;
u64 ret;
val = readl_relaxed(fd->reg);
m = (val & fd->mmask) >> fd->mshift;
n = (val & fd->nmask) >> fd->nshift;
ret = (u64)parent_rate * (m + 1);
do_div(ret, n + 1);
return ret;
}
static long clk_frac_divider_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct clk_frac_divider *fd = to_clk_frac_divider(hw);
unsigned long scale;
unsigned long m, n;
u64 ret;
if (!rate || rate >= *parent_rate)
return *parent_rate;
scale = fls_long(*parent_rate / rate - 1);
if (scale > 4)
rate <<= scale - fd->nwidth;
rational_best_approximation(rate, *parent_rate,
GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
&m, &n);
ret = (u64)*parent_rate * m;
do_div(ret, n);
return ret;
}
static int clk_frac_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct clk_frac_divider *fd = to_clk_frac_divider(hw);
unsigned long m, n;
u32 val;
rational_best_approximation(rate, parent_rate,
GENMASK(fd->mwidth - 1, 0), GENMASK(fd->nwidth - 1, 0),
&m, &n);
m = m - 1;
n = n - 1;
if (m && !n)
return -EINVAL;
val = readl_relaxed(fd->reg);
val &= ~(fd->mmask | fd->nmask);
val |= (m << fd->mshift) | (n << fd->nshift);
writel_relaxed(val, fd->reg);
return 0;
}
const struct clk_ops clk_frac_divider_ops = {
.recalc_rate = clk_frac_divider_recalc_rate,
.round_rate = clk_frac_divider_round_rate,
.set_rate = clk_frac_divider_set_rate,
};
EXPORT_SYMBOL_GPL(clk_frac_divider_ops);