mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
701 lines
17 KiB
701 lines
17 KiB
// SPDX-License-Identifier: GPL-2.0-or-later |
|
/* |
|
* Copyright (C) 2013 Boris BREZILLON <[email protected]> |
|
*/ |
|
|
|
#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 0x7 |
|
|
|
#define PMC_MCR 0x30 |
|
#define PMC_MCR_ID_MSK GENMASK(3, 0) |
|
#define PMC_MCR_CMD BIT(7) |
|
#define PMC_MCR_DIV GENMASK(10, 8) |
|
#define PMC_MCR_CSS GENMASK(20, 16) |
|
#define PMC_MCR_CSS_SHIFT (16) |
|
#define PMC_MCR_EN BIT(28) |
|
|
|
#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK) |
|
|
|
#define MASTER_MAX_ID 4 |
|
|
|
#define to_clk_master(hw) container_of(hw, struct clk_master, hw) |
|
|
|
struct clk_master { |
|
struct clk_hw hw; |
|
struct regmap *regmap; |
|
spinlock_t *lock; |
|
const struct clk_master_layout *layout; |
|
const struct clk_master_characteristics *characteristics; |
|
u32 *mux_table; |
|
u32 mckr; |
|
int chg_pid; |
|
u8 id; |
|
u8 parent; |
|
u8 div; |
|
}; |
|
|
|
static inline bool clk_master_ready(struct clk_master *master) |
|
{ |
|
unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY; |
|
unsigned int status; |
|
|
|
regmap_read(master->regmap, AT91_PMC_SR, &status); |
|
|
|
return !!(status & bit); |
|
} |
|
|
|
static int clk_master_prepare(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
|
|
while (!clk_master_ready(master)) |
|
cpu_relax(); |
|
|
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int clk_master_is_prepared(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
bool status; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
status = clk_master_ready(master); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return status; |
|
} |
|
|
|
static unsigned long clk_master_div_recalc_rate(struct clk_hw *hw, |
|
unsigned long parent_rate) |
|
{ |
|
u8 div; |
|
unsigned long flags, 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; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_read(master->regmap, master->layout->offset, &mckr); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
mckr &= layout->mask; |
|
|
|
div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK; |
|
|
|
rate /= characteristics->divisors[div]; |
|
|
|
if (rate < characteristics->output.min) |
|
pr_warn("master clk div is underclocked"); |
|
else if (rate > characteristics->output.max) |
|
pr_warn("master clk div is overclocked"); |
|
|
|
return rate; |
|
} |
|
|
|
static const struct clk_ops master_div_ops = { |
|
.prepare = clk_master_prepare, |
|
.is_prepared = clk_master_is_prepared, |
|
.recalc_rate = clk_master_div_recalc_rate, |
|
}; |
|
|
|
static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate, |
|
unsigned long parent_rate) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
const struct clk_master_characteristics *characteristics = |
|
master->characteristics; |
|
unsigned long flags; |
|
int div, i; |
|
|
|
div = DIV_ROUND_CLOSEST(parent_rate, rate); |
|
if (div > ARRAY_SIZE(characteristics->divisors)) |
|
return -EINVAL; |
|
|
|
for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) { |
|
if (!characteristics->divisors[i]) |
|
break; |
|
|
|
if (div == characteristics->divisors[i]) { |
|
div = i; |
|
break; |
|
} |
|
} |
|
|
|
if (i == ARRAY_SIZE(characteristics->divisors)) |
|
return -EINVAL; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_update_bits(master->regmap, master->layout->offset, |
|
(MASTER_DIV_MASK << MASTER_DIV_SHIFT), |
|
(div << MASTER_DIV_SHIFT)); |
|
while (!clk_master_ready(master)) |
|
cpu_relax(); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int clk_master_div_determine_rate(struct clk_hw *hw, |
|
struct clk_rate_request *req) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
const struct clk_master_characteristics *characteristics = |
|
master->characteristics; |
|
struct clk_hw *parent; |
|
unsigned long parent_rate, tmp_rate, best_rate = 0; |
|
int i, best_diff = INT_MIN, tmp_diff; |
|
|
|
parent = clk_hw_get_parent(hw); |
|
if (!parent) |
|
return -EINVAL; |
|
|
|
parent_rate = clk_hw_get_rate(parent); |
|
if (!parent_rate) |
|
return -EINVAL; |
|
|
|
for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) { |
|
if (!characteristics->divisors[i]) |
|
break; |
|
|
|
tmp_rate = DIV_ROUND_CLOSEST_ULL(parent_rate, |
|
characteristics->divisors[i]); |
|
tmp_diff = abs(tmp_rate - req->rate); |
|
|
|
if (!best_rate || best_diff > tmp_diff) { |
|
best_diff = tmp_diff; |
|
best_rate = tmp_rate; |
|
} |
|
|
|
if (!best_diff) |
|
break; |
|
} |
|
|
|
req->best_parent_rate = best_rate; |
|
req->best_parent_hw = parent; |
|
req->rate = best_rate; |
|
|
|
return 0; |
|
} |
|
|
|
static const struct clk_ops master_div_ops_chg = { |
|
.prepare = clk_master_prepare, |
|
.is_prepared = clk_master_is_prepared, |
|
.recalc_rate = clk_master_div_recalc_rate, |
|
.determine_rate = clk_master_div_determine_rate, |
|
.set_rate = clk_master_div_set_rate, |
|
}; |
|
|
|
static void clk_sama7g5_master_best_diff(struct clk_rate_request *req, |
|
struct clk_hw *parent, |
|
unsigned long parent_rate, |
|
long *best_rate, |
|
long *best_diff, |
|
u32 div) |
|
{ |
|
unsigned long tmp_rate, tmp_diff; |
|
|
|
if (div == MASTER_PRES_MAX) |
|
tmp_rate = parent_rate / 3; |
|
else |
|
tmp_rate = parent_rate >> div; |
|
|
|
tmp_diff = abs(req->rate - tmp_rate); |
|
|
|
if (*best_diff < 0 || *best_diff >= tmp_diff) { |
|
*best_rate = tmp_rate; |
|
*best_diff = tmp_diff; |
|
req->best_parent_rate = parent_rate; |
|
req->best_parent_hw = parent; |
|
} |
|
} |
|
|
|
static int clk_master_pres_determine_rate(struct clk_hw *hw, |
|
struct clk_rate_request *req) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
struct clk_rate_request req_parent = *req; |
|
const struct clk_master_characteristics *characteristics = |
|
master->characteristics; |
|
struct clk_hw *parent; |
|
long best_rate = LONG_MIN, best_diff = LONG_MIN; |
|
u32 pres; |
|
int i; |
|
|
|
if (master->chg_pid < 0) |
|
return -EOPNOTSUPP; |
|
|
|
parent = clk_hw_get_parent_by_index(hw, master->chg_pid); |
|
if (!parent) |
|
return -EOPNOTSUPP; |
|
|
|
for (i = 0; i <= MASTER_PRES_MAX; i++) { |
|
if (characteristics->have_div3_pres && i == MASTER_PRES_MAX) |
|
pres = 3; |
|
else |
|
pres = 1 << i; |
|
|
|
req_parent.rate = req->rate * pres; |
|
if (__clk_determine_rate(parent, &req_parent)) |
|
continue; |
|
|
|
clk_sama7g5_master_best_diff(req, parent, req_parent.rate, |
|
&best_diff, &best_rate, pres); |
|
if (!best_diff) |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static int clk_master_pres_set_rate(struct clk_hw *hw, unsigned long rate, |
|
unsigned long parent_rate) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
unsigned int pres; |
|
|
|
pres = DIV_ROUND_CLOSEST(parent_rate, rate); |
|
if (pres > MASTER_PRES_MAX) |
|
return -EINVAL; |
|
|
|
else if (pres == 3) |
|
pres = MASTER_PRES_MAX; |
|
else if (pres) |
|
pres = ffs(pres) - 1; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_update_bits(master->regmap, master->layout->offset, |
|
(MASTER_PRES_MASK << master->layout->pres_shift), |
|
(pres << master->layout->pres_shift)); |
|
|
|
while (!clk_master_ready(master)) |
|
cpu_relax(); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static unsigned long clk_master_pres_recalc_rate(struct clk_hw *hw, |
|
unsigned long parent_rate) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
const struct clk_master_characteristics *characteristics = |
|
master->characteristics; |
|
unsigned long flags; |
|
unsigned int val, pres; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_read(master->regmap, master->layout->offset, &val); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
pres = (val >> master->layout->pres_shift) & MASTER_PRES_MASK; |
|
if (pres == MASTER_PRES_MAX && characteristics->have_div3_pres) |
|
pres = 3; |
|
else |
|
pres = (1 << pres); |
|
|
|
return DIV_ROUND_CLOSEST_ULL(parent_rate, pres); |
|
} |
|
|
|
static u8 clk_master_pres_get_parent(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
unsigned int mckr; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_read(master->regmap, master->layout->offset, &mckr); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return mckr & AT91_PMC_CSS; |
|
} |
|
|
|
static const struct clk_ops master_pres_ops = { |
|
.prepare = clk_master_prepare, |
|
.is_prepared = clk_master_is_prepared, |
|
.recalc_rate = clk_master_pres_recalc_rate, |
|
.get_parent = clk_master_pres_get_parent, |
|
}; |
|
|
|
static const struct clk_ops master_pres_ops_chg = { |
|
.prepare = clk_master_prepare, |
|
.is_prepared = clk_master_is_prepared, |
|
.determine_rate = clk_master_pres_determine_rate, |
|
.recalc_rate = clk_master_pres_recalc_rate, |
|
.get_parent = clk_master_pres_get_parent, |
|
.set_rate = clk_master_pres_set_rate, |
|
}; |
|
|
|
static struct clk_hw * __init |
|
at91_clk_register_master_internal(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, |
|
const struct clk_ops *ops, spinlock_t *lock, u32 flags, |
|
int chg_pid) |
|
{ |
|
struct clk_master *master; |
|
struct clk_init_data init; |
|
struct clk_hw *hw; |
|
int ret; |
|
|
|
if (!name || !num_parents || !parent_names || !lock) |
|
return ERR_PTR(-EINVAL); |
|
|
|
master = kzalloc(sizeof(*master), GFP_KERNEL); |
|
if (!master) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
init.name = name; |
|
init.ops = ops; |
|
init.parent_names = parent_names; |
|
init.num_parents = num_parents; |
|
init.flags = flags; |
|
|
|
master->hw.init = &init; |
|
master->layout = layout; |
|
master->characteristics = characteristics; |
|
master->regmap = regmap; |
|
master->chg_pid = chg_pid; |
|
master->lock = lock; |
|
|
|
hw = &master->hw; |
|
ret = clk_hw_register(NULL, &master->hw); |
|
if (ret) { |
|
kfree(master); |
|
hw = ERR_PTR(ret); |
|
} |
|
|
|
return hw; |
|
} |
|
|
|
struct clk_hw * __init |
|
at91_clk_register_master_pres(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, |
|
spinlock_t *lock, u32 flags, int chg_pid) |
|
{ |
|
const struct clk_ops *ops; |
|
|
|
if (flags & CLK_SET_RATE_GATE) |
|
ops = &master_pres_ops; |
|
else |
|
ops = &master_pres_ops_chg; |
|
|
|
return at91_clk_register_master_internal(regmap, name, num_parents, |
|
parent_names, layout, |
|
characteristics, ops, |
|
lock, flags, chg_pid); |
|
} |
|
|
|
struct clk_hw * __init |
|
at91_clk_register_master_div(struct regmap *regmap, |
|
const char *name, const char *parent_name, |
|
const struct clk_master_layout *layout, |
|
const struct clk_master_characteristics *characteristics, |
|
spinlock_t *lock, u32 flags) |
|
{ |
|
const struct clk_ops *ops; |
|
|
|
if (flags & CLK_SET_RATE_GATE) |
|
ops = &master_div_ops; |
|
else |
|
ops = &master_div_ops_chg; |
|
|
|
return at91_clk_register_master_internal(regmap, name, 1, |
|
&parent_name, layout, |
|
characteristics, ops, |
|
lock, flags, -EINVAL); |
|
} |
|
|
|
static unsigned long |
|
clk_sama7g5_master_recalc_rate(struct clk_hw *hw, |
|
unsigned long parent_rate) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
|
|
return DIV_ROUND_CLOSEST_ULL(parent_rate, (1 << master->div)); |
|
} |
|
|
|
static int clk_sama7g5_master_determine_rate(struct clk_hw *hw, |
|
struct clk_rate_request *req) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
struct clk_rate_request req_parent = *req; |
|
struct clk_hw *parent; |
|
long best_rate = LONG_MIN, best_diff = LONG_MIN; |
|
unsigned long parent_rate; |
|
unsigned int div, i; |
|
|
|
/* First: check the dividers of MCR. */ |
|
for (i = 0; i < clk_hw_get_num_parents(hw); i++) { |
|
parent = clk_hw_get_parent_by_index(hw, i); |
|
if (!parent) |
|
continue; |
|
|
|
parent_rate = clk_hw_get_rate(parent); |
|
if (!parent_rate) |
|
continue; |
|
|
|
for (div = 0; div < MASTER_PRES_MAX + 1; div++) { |
|
clk_sama7g5_master_best_diff(req, parent, parent_rate, |
|
&best_rate, &best_diff, |
|
div); |
|
if (!best_diff) |
|
break; |
|
} |
|
|
|
if (!best_diff) |
|
break; |
|
} |
|
|
|
/* Second: try to request rate form changeable parent. */ |
|
if (master->chg_pid < 0) |
|
goto end; |
|
|
|
parent = clk_hw_get_parent_by_index(hw, master->chg_pid); |
|
if (!parent) |
|
goto end; |
|
|
|
for (div = 0; div < MASTER_PRES_MAX + 1; div++) { |
|
if (div == MASTER_PRES_MAX) |
|
req_parent.rate = req->rate * 3; |
|
else |
|
req_parent.rate = req->rate << div; |
|
|
|
if (__clk_determine_rate(parent, &req_parent)) |
|
continue; |
|
|
|
clk_sama7g5_master_best_diff(req, parent, req_parent.rate, |
|
&best_rate, &best_diff, div); |
|
|
|
if (!best_diff) |
|
break; |
|
} |
|
|
|
end: |
|
pr_debug("MCK: %s, best_rate = %ld, parent clk: %s @ %ld\n", |
|
__func__, best_rate, |
|
__clk_get_name((req->best_parent_hw)->clk), |
|
req->best_parent_rate); |
|
|
|
if (best_rate < 0) |
|
return -EINVAL; |
|
|
|
req->rate = best_rate; |
|
|
|
return 0; |
|
} |
|
|
|
static u8 clk_sama7g5_master_get_parent(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
u8 index; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
index = clk_mux_val_to_index(&master->hw, master->mux_table, 0, |
|
master->parent); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return index; |
|
} |
|
|
|
static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
|
|
if (index >= clk_hw_get_num_parents(hw)) |
|
return -EINVAL; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
master->parent = clk_mux_index_to_val(master->mux_table, 0, index); |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static int clk_sama7g5_master_enable(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
unsigned int val, cparent; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
|
|
regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id)); |
|
regmap_read(master->regmap, PMC_MCR, &val); |
|
regmap_update_bits(master->regmap, PMC_MCR, |
|
PMC_MCR_EN | PMC_MCR_CSS | PMC_MCR_DIV | |
|
PMC_MCR_CMD | PMC_MCR_ID_MSK, |
|
PMC_MCR_EN | (master->parent << PMC_MCR_CSS_SHIFT) | |
|
(master->div << MASTER_DIV_SHIFT) | |
|
PMC_MCR_CMD | PMC_MCR_ID(master->id)); |
|
|
|
cparent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT; |
|
|
|
/* Wait here only if parent is being changed. */ |
|
while ((cparent != master->parent) && !clk_master_ready(master)) |
|
cpu_relax(); |
|
|
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static void clk_sama7g5_master_disable(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
|
|
regmap_write(master->regmap, PMC_MCR, master->id); |
|
regmap_update_bits(master->regmap, PMC_MCR, |
|
PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK, |
|
PMC_MCR_CMD | PMC_MCR_ID(master->id)); |
|
|
|
spin_unlock_irqrestore(master->lock, flags); |
|
} |
|
|
|
static int clk_sama7g5_master_is_enabled(struct clk_hw *hw) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long flags; |
|
unsigned int val; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
|
|
regmap_write(master->regmap, PMC_MCR, master->id); |
|
regmap_read(master->regmap, PMC_MCR, &val); |
|
|
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return !!(val & PMC_MCR_EN); |
|
} |
|
|
|
static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate, |
|
unsigned long parent_rate) |
|
{ |
|
struct clk_master *master = to_clk_master(hw); |
|
unsigned long div, flags; |
|
|
|
div = DIV_ROUND_CLOSEST(parent_rate, rate); |
|
if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1))) |
|
return -EINVAL; |
|
|
|
if (div == 3) |
|
div = MASTER_PRES_MAX; |
|
else if (div) |
|
div = ffs(div) - 1; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
master->div = div; |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct clk_ops sama7g5_master_ops = { |
|
.enable = clk_sama7g5_master_enable, |
|
.disable = clk_sama7g5_master_disable, |
|
.is_enabled = clk_sama7g5_master_is_enabled, |
|
.recalc_rate = clk_sama7g5_master_recalc_rate, |
|
.determine_rate = clk_sama7g5_master_determine_rate, |
|
.set_rate = clk_sama7g5_master_set_rate, |
|
.get_parent = clk_sama7g5_master_get_parent, |
|
.set_parent = clk_sama7g5_master_set_parent, |
|
}; |
|
|
|
struct clk_hw * __init |
|
at91_clk_sama7g5_register_master(struct regmap *regmap, |
|
const char *name, int num_parents, |
|
const char **parent_names, |
|
u32 *mux_table, |
|
spinlock_t *lock, u8 id, |
|
bool critical, int chg_pid) |
|
{ |
|
struct clk_master *master; |
|
struct clk_hw *hw; |
|
struct clk_init_data init; |
|
unsigned long flags; |
|
unsigned int val; |
|
int ret; |
|
|
|
if (!name || !num_parents || !parent_names || !mux_table || |
|
!lock || id > MASTER_MAX_ID) |
|
return ERR_PTR(-EINVAL); |
|
|
|
master = kzalloc(sizeof(*master), GFP_KERNEL); |
|
if (!master) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
init.name = name; |
|
init.ops = &sama7g5_master_ops; |
|
init.parent_names = parent_names; |
|
init.num_parents = num_parents; |
|
init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; |
|
if (chg_pid >= 0) |
|
init.flags |= CLK_SET_RATE_PARENT; |
|
if (critical) |
|
init.flags |= CLK_IS_CRITICAL; |
|
|
|
master->hw.init = &init; |
|
master->regmap = regmap; |
|
master->id = id; |
|
master->chg_pid = chg_pid; |
|
master->lock = lock; |
|
master->mux_table = mux_table; |
|
|
|
spin_lock_irqsave(master->lock, flags); |
|
regmap_write(master->regmap, PMC_MCR, master->id); |
|
regmap_read(master->regmap, PMC_MCR, &val); |
|
master->parent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT; |
|
master->div = (val & PMC_MCR_DIV) >> MASTER_DIV_SHIFT; |
|
spin_unlock_irqrestore(master->lock, flags); |
|
|
|
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, |
|
};
|
|
|