forked from 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.
192 lines
4.3 KiB
192 lines
4.3 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Copyright (c) 2018 MediaTek Inc. |
|
* Author: Owen Chen <[email protected]> |
|
*/ |
|
|
|
#include <linux/of.h> |
|
#include <linux/of_address.h> |
|
#include <linux/slab.h> |
|
#include <linux/mfd/syscon.h> |
|
|
|
#include "clk-mtk.h" |
|
#include "clk-mux.h" |
|
|
|
static inline struct mtk_clk_mux *to_mtk_clk_mux(struct clk_hw *hw) |
|
{ |
|
return container_of(hw, struct mtk_clk_mux, hw); |
|
} |
|
|
|
static int mtk_clk_mux_enable_setclr(struct clk_hw *hw) |
|
{ |
|
struct mtk_clk_mux *mux = to_mtk_clk_mux(hw); |
|
unsigned long flags = 0; |
|
|
|
if (mux->lock) |
|
spin_lock_irqsave(mux->lock, flags); |
|
else |
|
__acquire(mux->lock); |
|
|
|
regmap_write(mux->regmap, mux->data->clr_ofs, |
|
BIT(mux->data->gate_shift)); |
|
|
|
/* |
|
* If the parent has been changed when the clock was disabled, it will |
|
* not be effective yet. Set the update bit to ensure the mux gets |
|
* updated. |
|
*/ |
|
if (mux->reparent && mux->data->upd_shift >= 0) { |
|
regmap_write(mux->regmap, mux->data->upd_ofs, |
|
BIT(mux->data->upd_shift)); |
|
mux->reparent = false; |
|
} |
|
|
|
if (mux->lock) |
|
spin_unlock_irqrestore(mux->lock, flags); |
|
else |
|
__release(mux->lock); |
|
|
|
return 0; |
|
} |
|
|
|
static void mtk_clk_mux_disable_setclr(struct clk_hw *hw) |
|
{ |
|
struct mtk_clk_mux *mux = to_mtk_clk_mux(hw); |
|
|
|
regmap_write(mux->regmap, mux->data->set_ofs, |
|
BIT(mux->data->gate_shift)); |
|
} |
|
|
|
static int mtk_clk_mux_is_enabled(struct clk_hw *hw) |
|
{ |
|
struct mtk_clk_mux *mux = to_mtk_clk_mux(hw); |
|
u32 val; |
|
|
|
regmap_read(mux->regmap, mux->data->mux_ofs, &val); |
|
|
|
return (val & BIT(mux->data->gate_shift)) == 0; |
|
} |
|
|
|
static u8 mtk_clk_mux_get_parent(struct clk_hw *hw) |
|
{ |
|
struct mtk_clk_mux *mux = to_mtk_clk_mux(hw); |
|
u32 mask = GENMASK(mux->data->mux_width - 1, 0); |
|
u32 val; |
|
|
|
regmap_read(mux->regmap, mux->data->mux_ofs, &val); |
|
val = (val >> mux->data->mux_shift) & mask; |
|
|
|
return val; |
|
} |
|
|
|
static int mtk_clk_mux_set_parent_setclr_lock(struct clk_hw *hw, u8 index) |
|
{ |
|
struct mtk_clk_mux *mux = to_mtk_clk_mux(hw); |
|
u32 mask = GENMASK(mux->data->mux_width - 1, 0); |
|
u32 val, orig; |
|
unsigned long flags = 0; |
|
|
|
if (mux->lock) |
|
spin_lock_irqsave(mux->lock, flags); |
|
else |
|
__acquire(mux->lock); |
|
|
|
regmap_read(mux->regmap, mux->data->mux_ofs, &orig); |
|
val = (orig & ~(mask << mux->data->mux_shift)) |
|
| (index << mux->data->mux_shift); |
|
|
|
if (val != orig) { |
|
regmap_write(mux->regmap, mux->data->clr_ofs, |
|
mask << mux->data->mux_shift); |
|
regmap_write(mux->regmap, mux->data->set_ofs, |
|
index << mux->data->mux_shift); |
|
|
|
if (mux->data->upd_shift >= 0) { |
|
regmap_write(mux->regmap, mux->data->upd_ofs, |
|
BIT(mux->data->upd_shift)); |
|
mux->reparent = true; |
|
} |
|
} |
|
|
|
if (mux->lock) |
|
spin_unlock_irqrestore(mux->lock, flags); |
|
else |
|
__release(mux->lock); |
|
|
|
return 0; |
|
} |
|
|
|
static const struct clk_ops mtk_mux_ops = { |
|
.enable = mtk_clk_mux_enable_setclr, |
|
.disable = mtk_clk_mux_disable_setclr, |
|
.is_enabled = mtk_clk_mux_is_enabled, |
|
.get_parent = mtk_clk_mux_get_parent, |
|
.set_parent = mtk_clk_mux_set_parent_setclr_lock, |
|
}; |
|
|
|
static struct clk *mtk_clk_register_mux(const struct mtk_mux *mux, |
|
struct regmap *regmap, |
|
spinlock_t *lock) |
|
{ |
|
struct mtk_clk_mux *clk_mux; |
|
struct clk_init_data init = {}; |
|
struct clk *clk; |
|
|
|
clk_mux = kzalloc(sizeof(*clk_mux), GFP_KERNEL); |
|
if (!clk_mux) |
|
return ERR_PTR(-ENOMEM); |
|
|
|
init.name = mux->name; |
|
init.flags = mux->flags | CLK_SET_RATE_PARENT; |
|
init.parent_names = mux->parent_names; |
|
init.num_parents = mux->num_parents; |
|
init.ops = &mtk_mux_ops; |
|
|
|
clk_mux->regmap = regmap; |
|
clk_mux->data = mux; |
|
clk_mux->lock = lock; |
|
clk_mux->hw.init = &init; |
|
|
|
clk = clk_register(NULL, &clk_mux->hw); |
|
if (IS_ERR(clk)) { |
|
kfree(clk_mux); |
|
return clk; |
|
} |
|
|
|
return clk; |
|
} |
|
|
|
int mtk_clk_register_muxes(const struct mtk_mux *muxes, |
|
int num, struct device_node *node, |
|
spinlock_t *lock, |
|
struct clk_onecell_data *clk_data) |
|
{ |
|
struct regmap *regmap; |
|
struct clk *clk; |
|
int i; |
|
|
|
regmap = syscon_node_to_regmap(node); |
|
if (IS_ERR(regmap)) { |
|
pr_err("Cannot find regmap for %pOF: %ld\n", node, |
|
PTR_ERR(regmap)); |
|
return PTR_ERR(regmap); |
|
} |
|
|
|
for (i = 0; i < num; i++) { |
|
const struct mtk_mux *mux = &muxes[i]; |
|
|
|
if (IS_ERR_OR_NULL(clk_data->clks[mux->id])) { |
|
clk = mtk_clk_register_mux(mux, regmap, lock); |
|
|
|
if (IS_ERR(clk)) { |
|
pr_err("Failed to register clk %s: %ld\n", |
|
mux->name, PTR_ERR(clk)); |
|
continue; |
|
} |
|
|
|
clk_data->clks[mux->id] = clk; |
|
} |
|
} |
|
|
|
return 0; |
|
}
|
|
|