blob: 074e0c7dcb1042c95fb45fc844dd7a1ee16e00d0 [file] [log] [blame]
/*
* drivers/amlogic/clk/clk-cpu-fclk-composite.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* 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.
*
* This program is distributed in the hope that 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.
*
*/
/*
* CPU clock path:
*
* +-[/N]-----|3|
* MUX2 +--[/3]-+----------|2| MUX1
* [sys_pll]---|1| |--[/2]------------|1|-|1|
* | |---+------------------|0| | |----- [a5_clk]
* +--|0| | |
* [xtal]---+-------------------------------|0|
*
*
*
*/
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/slab.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#define MESON_CPU_CLK_CNTL 0x00
#define MESON_CPU_CLK_CNTL1 0x40
#define MESON_POST_MUX0 BIT(2)
#define MESON_DYN_MUX BIT(10)
#define MESON_FINAL_MUX BIT(11)
#define MESON_POST_MUX1 BIT(18)
#define MESON_DYN_ENABLE BIT(26)
#define MESON_N_WIDTH 9
#define MESON_N_SHIFT 20
#define MESON_SEL_WIDTH 2
#define MESON_SEL_SHIFT 2
#include "clkc.h"
#define to_clk_mux_divider(_hw) \
container_of(_hw, struct meson_cpu_mux_divider, hw)
static unsigned int gap_rate = (10*1000*1000);
/* GX series, control cpu clk in firmware,
* kernel do not know when freq will change
*/
static const struct fclk_rate_table *meson_fclk_get_pll_settings
(struct meson_cpu_mux_divider *pll, unsigned long rate)
{
const struct fclk_rate_table *rate_table = pll->rate_table;
int i;
for (i = 0; i < pll->rate_count; i++) {
if (abs(rate-rate_table[i].rate) < gap_rate)
return &rate_table[i];
}
return NULL;
}
static u8 meson_fclk_cpu_get_parent(struct clk_hw *hw)
{
struct meson_cpu_mux_divider *mux_divider =
to_clk_mux_divider(hw);
int num_parents = clk_hw_get_num_parents(hw);
u32 val, final_dyn_mask, premux_mask;
u8 final_dyn_shift, premux_shift;
final_dyn_mask = mux_divider->cpu_fclk_p.mask;
final_dyn_shift = mux_divider->cpu_fclk_p.shift;
val = clk_readl(mux_divider->reg);
if ((val >> final_dyn_shift) & final_dyn_mask) {
premux_mask = mux_divider->cpu_fclk_p10.mask;
premux_shift = mux_divider->cpu_fclk_p10.shift;
} else {
premux_mask = mux_divider->cpu_fclk_p00.mask;
premux_shift = mux_divider->cpu_fclk_p00.shift;
}
val = clk_readl(mux_divider->reg) >> premux_shift;
val &= premux_mask;
if (val >= num_parents)
return -EINVAL;
if (mux_divider->table) {
int i;
for (i = 0; i < num_parents; i++)
if (mux_divider->table[i] == val)
return i;
}
return val;
}
static int meson_fclk_cpu_set_parent(struct clk_hw *hw, u8 index)
{
struct meson_cpu_mux_divider *mux_divider =
to_clk_mux_divider(hw);
u32 val, final_dyn_mask, premux_mask;
u8 final_dyn_shift, premux_shift;
unsigned long flags = 0;
final_dyn_mask = mux_divider->cpu_fclk_p.mask;
final_dyn_shift = mux_divider->cpu_fclk_p.shift;
val = clk_readl(mux_divider->reg);
if ((val >> final_dyn_shift) & final_dyn_mask) {
premux_mask = mux_divider->cpu_fclk_p00.mask;
premux_shift = mux_divider->cpu_fclk_p00.shift;
} else {
premux_mask = mux_divider->cpu_fclk_p10.mask;
premux_shift = mux_divider->cpu_fclk_p10.shift;
}
if (mux_divider->table) {
index = mux_divider->table[index];
} else {
if (mux_divider->flags & CLK_MUX_INDEX_BIT)
index = (1 << ffs(index));
if (mux_divider->flags & CLK_MUX_INDEX_ONE)
index++;
}
if (mux_divider->lock)
spin_lock_irqsave(mux_divider->lock, flags);
else
__acquire(mux_divider->lock);
if (mux_divider->flags & CLK_MUX_HIWORD_MASK) {
val = premux_mask << (premux_shift + 16);
} else {
val = clk_readl(mux_divider->reg);
val &= ~(premux_mask << premux_shift);
}
val |= index << premux_shift;
clk_writel(val, mux_divider->reg);
if (mux_divider->lock)
spin_unlock_irqrestore(mux_divider->lock, flags);
else
__release(mux_divider->lock);
return 0;
}
static unsigned long meson_fclk_cpu_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct meson_cpu_mux_divider *mux_divider =
to_clk_mux_divider(hw);
struct parm_fclk *p_premux, *p_postmux, *p_div;
unsigned long rate;
u32 val, final_dyn_mask, div;
u8 final_dyn_shift;
final_dyn_mask = mux_divider->cpu_fclk_p.mask;
final_dyn_shift = mux_divider->cpu_fclk_p.shift;
val = readl(mux_divider->reg);
if ((val >> final_dyn_shift) & final_dyn_mask) {
p_premux = &mux_divider->cpu_fclk_p10;
p_postmux = &mux_divider->cpu_fclk_p1;
p_div = &mux_divider->cpu_fclk_p11;
} else {
p_premux = &mux_divider->cpu_fclk_p00;
p_postmux = &mux_divider->cpu_fclk_p0;
p_div = &mux_divider->cpu_fclk_p01;
}
div = PARM_GET(p_div->width, p_div->shift, val);
rate = parent_rate / (div + 1);
return rate;
}
static int meson_fclk_divider_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct meson_cpu_mux_divider *mux_divider =
to_clk_mux_divider(hw);
const struct fclk_rate_table *rate_set;
struct parm_fclk *p_premux, *p_postmux, *p_div;
u32 val, final_dyn_mask;
u8 final_dyn_shift;
unsigned long old_rate;
unsigned int tmp;
unsigned long flags = 0;
if (parent_rate == 0 || rate == 0)
return -EINVAL;
final_dyn_mask = mux_divider->cpu_fclk_p.mask;
final_dyn_shift = mux_divider->cpu_fclk_p.shift;
val = readl(mux_divider->reg);
if ((val >> final_dyn_shift) & final_dyn_mask) {
p_premux = &mux_divider->cpu_fclk_p00;
p_postmux = &mux_divider->cpu_fclk_p0;
p_div = &mux_divider->cpu_fclk_p01;
} else {
p_premux = &mux_divider->cpu_fclk_p10;
p_postmux = &mux_divider->cpu_fclk_p1;
p_div = &mux_divider->cpu_fclk_p11;
}
old_rate = rate;
rate_set = meson_fclk_get_pll_settings(mux_divider, rate);
if (!rate_set)
return -EINVAL;
if (mux_divider->lock)
spin_lock_irqsave(mux_divider->lock, flags);
else
__acquire(mux_divider->lock);
writel((val | MESON_DYN_ENABLE), mux_divider->reg);
/*set mux_divider clk divider*/
tmp = rate_set->mux_div;
val = PARM_SET(p_div->width, p_div->shift, val, tmp);
writel(val, mux_divider->reg);
/*set mux_divider postmux*/
tmp = rate_set->postmux;
val = PARM_SET(p_postmux->width, p_postmux->shift, val, tmp);
writel(val, mux_divider->reg);
/*set mux_divider final dyn*/
val = readl(mux_divider->reg);
if ((val >> final_dyn_shift) & final_dyn_mask)
val &= ~(1 << final_dyn_shift);
else
val |= (1 << final_dyn_shift);
writel(val, mux_divider->reg);
if (mux_divider->lock)
spin_unlock_irqrestore(mux_divider->lock, flags);
else
__release(mux_divider->lock);
return 0;
}
int meson_fclk_mux_divider_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_hw *best_parent = NULL;
unsigned long best = 0;
struct clk_rate_request parent_req = *req;
struct meson_cpu_mux_divider *mux_divider =
to_clk_mux_divider(hw);
const struct fclk_rate_table *rate_set;
u32 premux;
rate_set = meson_fclk_get_pll_settings(mux_divider, req->rate);
if (!rate_set)
return -EINVAL;
premux = rate_set->premux;
best_parent = clk_hw_get_parent_by_index(hw, premux);
if (!best_parent)
return -EINVAL;
best = clk_hw_get_rate(best_parent);
if (best != parent_req.rate)
meson_fclk_cpu_set_parent(hw, premux);
if (best_parent)
req->best_parent_hw = best_parent;
req->best_parent_rate = best;
return 0;
}
const struct clk_ops meson_fclk_cpu_ops = {
.determine_rate = meson_fclk_mux_divider_determine_rate,
.recalc_rate = meson_fclk_cpu_recalc_rate,
.get_parent = meson_fclk_cpu_get_parent,
.set_parent = meson_fclk_cpu_set_parent,
.set_rate = meson_fclk_divider_set_rate,
};