mirror of
https://github.com/torvalds/linux.git
synced 2024-12-27 05:11:48 +00:00
2874c5fd28
Based on 1 normalized pattern(s): this program is free software you can redistribute it and or modify it under the terms of the gnu general public license as published by the free software foundation either version 2 of the license or at your option any later version extracted by the scancode license scanner the SPDX license identifier GPL-2.0-or-later has been chosen to replace the boilerplate/reference in 3029 file(s). Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Allison Randal <allison@lohutok.net> Cc: linux-spdx@vger.kernel.org Link: https://lkml.kernel.org/r/20190527070032.746973796@linutronix.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
157 lines
3.5 KiB
C
157 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
|
|
*/
|
|
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/clkdev.h>
|
|
#include <linux/clk/at91_pmc.h>
|
|
#include <linux/of.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "pmc.h"
|
|
|
|
#define MASTER_PRES_MASK 0x7
|
|
#define MASTER_PRES_MAX MASTER_PRES_MASK
|
|
#define MASTER_DIV_SHIFT 8
|
|
#define MASTER_DIV_MASK 0x3
|
|
|
|
#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
|
|
|
|
struct clk_master {
|
|
struct clk_hw hw;
|
|
struct regmap *regmap;
|
|
const struct clk_master_layout *layout;
|
|
const struct clk_master_characteristics *characteristics;
|
|
u32 mckr;
|
|
};
|
|
|
|
static inline bool clk_master_ready(struct regmap *regmap)
|
|
{
|
|
unsigned int status;
|
|
|
|
regmap_read(regmap, AT91_PMC_SR, &status);
|
|
|
|
return status & AT91_PMC_MCKRDY ? 1 : 0;
|
|
}
|
|
|
|
static int clk_master_prepare(struct clk_hw *hw)
|
|
{
|
|
struct clk_master *master = to_clk_master(hw);
|
|
|
|
while (!clk_master_ready(master->regmap))
|
|
cpu_relax();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int clk_master_is_prepared(struct clk_hw *hw)
|
|
{
|
|
struct clk_master *master = to_clk_master(hw);
|
|
|
|
return clk_master_ready(master->regmap);
|
|
}
|
|
|
|
static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
|
|
unsigned long parent_rate)
|
|
{
|
|
u8 pres;
|
|
u8 div;
|
|
unsigned long rate = parent_rate;
|
|
struct clk_master *master = to_clk_master(hw);
|
|
const struct clk_master_layout *layout = master->layout;
|
|
const struct clk_master_characteristics *characteristics =
|
|
master->characteristics;
|
|
unsigned int mckr;
|
|
|
|
regmap_read(master->regmap, master->layout->offset, &mckr);
|
|
mckr &= layout->mask;
|
|
|
|
pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
|
|
div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
|
|
|
|
if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
|
|
rate /= 3;
|
|
else
|
|
rate >>= pres;
|
|
|
|
rate /= characteristics->divisors[div];
|
|
|
|
if (rate < characteristics->output.min)
|
|
pr_warn("master clk is underclocked");
|
|
else if (rate > characteristics->output.max)
|
|
pr_warn("master clk is overclocked");
|
|
|
|
return rate;
|
|
}
|
|
|
|
static u8 clk_master_get_parent(struct clk_hw *hw)
|
|
{
|
|
struct clk_master *master = to_clk_master(hw);
|
|
unsigned int mckr;
|
|
|
|
regmap_read(master->regmap, master->layout->offset, &mckr);
|
|
|
|
return mckr & AT91_PMC_CSS;
|
|
}
|
|
|
|
static const struct clk_ops master_ops = {
|
|
.prepare = clk_master_prepare,
|
|
.is_prepared = clk_master_is_prepared,
|
|
.recalc_rate = clk_master_recalc_rate,
|
|
.get_parent = clk_master_get_parent,
|
|
};
|
|
|
|
struct clk_hw * __init
|
|
at91_clk_register_master(struct regmap *regmap,
|
|
const char *name, int num_parents,
|
|
const char **parent_names,
|
|
const struct clk_master_layout *layout,
|
|
const struct clk_master_characteristics *characteristics)
|
|
{
|
|
struct clk_master *master;
|
|
struct clk_init_data init;
|
|
struct clk_hw *hw;
|
|
int ret;
|
|
|
|
if (!name || !num_parents || !parent_names)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
master = kzalloc(sizeof(*master), GFP_KERNEL);
|
|
if (!master)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
init.name = name;
|
|
init.ops = &master_ops;
|
|
init.parent_names = parent_names;
|
|
init.num_parents = num_parents;
|
|
init.flags = 0;
|
|
|
|
master->hw.init = &init;
|
|
master->layout = layout;
|
|
master->characteristics = characteristics;
|
|
master->regmap = regmap;
|
|
|
|
hw = &master->hw;
|
|
ret = clk_hw_register(NULL, &master->hw);
|
|
if (ret) {
|
|
kfree(master);
|
|
hw = ERR_PTR(ret);
|
|
}
|
|
|
|
return hw;
|
|
}
|
|
|
|
const struct clk_master_layout at91rm9200_master_layout = {
|
|
.mask = 0x31F,
|
|
.pres_shift = 2,
|
|
.offset = AT91_PMC_MCKR,
|
|
};
|
|
|
|
const struct clk_master_layout at91sam9x5_master_layout = {
|
|
.mask = 0x373,
|
|
.pres_shift = 4,
|
|
.offset = AT91_PMC_MCKR,
|
|
};
|