2
0
mirror of https://github.com/edk2-porting/linux-next.git synced 2024-12-29 15:43:59 +08:00
linux-next/drivers/clk/renesas/rcar-gen3-cpg.c
Geert Uytterhoeven 3f7a4d0841 clk: renesas: rcar-gen3: Restore R clock during resume
On R-Car Gen3 systems, PSCI system suspend powers down the SoC, losing
clock configuration.  Register a notifier to save/restore the RCKCR
register during system suspend/resume.

Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
Tested-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
2017-10-20 11:16:09 +02:00

445 lines
11 KiB
C

/*
* R-Car Gen3 Clock Pulse Generator
*
* Copyright (C) 2015-2016 Glider bvba
*
* Based on clk-rcar-gen3.c
*
* Copyright (C) 2015 Renesas Electronics Corp.
*
* 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; version 2 of the License.
*/
#include <linux/bug.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sys_soc.h>
#include "renesas-cpg-mssr.h"
#include "rcar-gen3-cpg.h"
#define CPG_PLL0CR 0x00d8
#define CPG_PLL2CR 0x002c
#define CPG_PLL4CR 0x01f4
struct cpg_simple_notifier {
struct notifier_block nb;
void __iomem *reg;
u32 saved;
};
static int cpg_simple_notifier_call(struct notifier_block *nb,
unsigned long action, void *data)
{
struct cpg_simple_notifier *csn =
container_of(nb, struct cpg_simple_notifier, nb);
switch (action) {
case PM_EVENT_SUSPEND:
csn->saved = readl(csn->reg);
return NOTIFY_OK;
case PM_EVENT_RESUME:
writel(csn->saved, csn->reg);
return NOTIFY_OK;
}
return NOTIFY_DONE;
}
static void cpg_simple_notifier_register(struct raw_notifier_head *notifiers,
struct cpg_simple_notifier *csn)
{
csn->nb.notifier_call = cpg_simple_notifier_call;
raw_notifier_chain_register(notifiers, &csn->nb);
}
/*
* SDn Clock
*/
#define CPG_SD_STP_HCK BIT(9)
#define CPG_SD_STP_CK BIT(8)
#define CPG_SD_STP_MASK (CPG_SD_STP_HCK | CPG_SD_STP_CK)
#define CPG_SD_FC_MASK (0x7 << 2 | 0x3 << 0)
#define CPG_SD_DIV_TABLE_DATA(stp_hck, stp_ck, sd_srcfc, sd_fc, sd_div) \
{ \
.val = ((stp_hck) ? CPG_SD_STP_HCK : 0) | \
((stp_ck) ? CPG_SD_STP_CK : 0) | \
((sd_srcfc) << 2) | \
((sd_fc) << 0), \
.div = (sd_div), \
}
struct sd_div_table {
u32 val;
unsigned int div;
};
struct sd_clock {
struct clk_hw hw;
const struct sd_div_table *div_table;
struct cpg_simple_notifier csn;
unsigned int div_num;
unsigned int div_min;
unsigned int div_max;
unsigned int cur_div_idx;
};
/* SDn divider
* sd_srcfc sd_fc div
* stp_hck stp_ck (div) (div) = sd_srcfc x sd_fc
*-------------------------------------------------------------------
* 0 0 0 (1) 1 (4) 4
* 0 0 1 (2) 1 (4) 8
* 1 0 2 (4) 1 (4) 16
* 1 0 3 (8) 1 (4) 32
* 1 0 4 (16) 1 (4) 64
* 0 0 0 (1) 0 (2) 2
* 0 0 1 (2) 0 (2) 4
* 1 0 2 (4) 0 (2) 8
* 1 0 3 (8) 0 (2) 16
* 1 0 4 (16) 0 (2) 32
*/
static const struct sd_div_table cpg_sd_div_table[] = {
/* CPG_SD_DIV_TABLE_DATA(stp_hck, stp_ck, sd_srcfc, sd_fc, sd_div) */
CPG_SD_DIV_TABLE_DATA(0, 0, 0, 1, 4),
CPG_SD_DIV_TABLE_DATA(0, 0, 1, 1, 8),
CPG_SD_DIV_TABLE_DATA(1, 0, 2, 1, 16),
CPG_SD_DIV_TABLE_DATA(1, 0, 3, 1, 32),
CPG_SD_DIV_TABLE_DATA(1, 0, 4, 1, 64),
CPG_SD_DIV_TABLE_DATA(0, 0, 0, 0, 2),
CPG_SD_DIV_TABLE_DATA(0, 0, 1, 0, 4),
CPG_SD_DIV_TABLE_DATA(1, 0, 2, 0, 8),
CPG_SD_DIV_TABLE_DATA(1, 0, 3, 0, 16),
CPG_SD_DIV_TABLE_DATA(1, 0, 4, 0, 32),
};
#define to_sd_clock(_hw) container_of(_hw, struct sd_clock, hw)
static int cpg_sd_clock_enable(struct clk_hw *hw)
{
struct sd_clock *clock = to_sd_clock(hw);
u32 val = readl(clock->csn.reg);
val &= ~(CPG_SD_STP_MASK);
val |= clock->div_table[clock->cur_div_idx].val & CPG_SD_STP_MASK;
writel(val, clock->csn.reg);
return 0;
}
static void cpg_sd_clock_disable(struct clk_hw *hw)
{
struct sd_clock *clock = to_sd_clock(hw);
writel(readl(clock->csn.reg) | CPG_SD_STP_MASK, clock->csn.reg);
}
static int cpg_sd_clock_is_enabled(struct clk_hw *hw)
{
struct sd_clock *clock = to_sd_clock(hw);
return !(readl(clock->csn.reg) & CPG_SD_STP_MASK);
}
static unsigned long cpg_sd_clock_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct sd_clock *clock = to_sd_clock(hw);
return DIV_ROUND_CLOSEST(parent_rate,
clock->div_table[clock->cur_div_idx].div);
}
static unsigned int cpg_sd_clock_calc_div(struct sd_clock *clock,
unsigned long rate,
unsigned long parent_rate)
{
unsigned int div;
if (!rate)
rate = 1;
div = DIV_ROUND_CLOSEST(parent_rate, rate);
return clamp_t(unsigned int, div, clock->div_min, clock->div_max);
}
static long cpg_sd_clock_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *parent_rate)
{
struct sd_clock *clock = to_sd_clock(hw);
unsigned int div = cpg_sd_clock_calc_div(clock, rate, *parent_rate);
return DIV_ROUND_CLOSEST(*parent_rate, div);
}
static int cpg_sd_clock_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct sd_clock *clock = to_sd_clock(hw);
unsigned int div = cpg_sd_clock_calc_div(clock, rate, parent_rate);
u32 val;
unsigned int i;
for (i = 0; i < clock->div_num; i++)
if (div == clock->div_table[i].div)
break;
if (i >= clock->div_num)
return -EINVAL;
clock->cur_div_idx = i;
val = readl(clock->csn.reg);
val &= ~(CPG_SD_STP_MASK | CPG_SD_FC_MASK);
val |= clock->div_table[i].val & (CPG_SD_STP_MASK | CPG_SD_FC_MASK);
writel(val, clock->csn.reg);
return 0;
}
static const struct clk_ops cpg_sd_clock_ops = {
.enable = cpg_sd_clock_enable,
.disable = cpg_sd_clock_disable,
.is_enabled = cpg_sd_clock_is_enabled,
.recalc_rate = cpg_sd_clock_recalc_rate,
.round_rate = cpg_sd_clock_round_rate,
.set_rate = cpg_sd_clock_set_rate,
};
static struct clk * __init cpg_sd_clk_register(const struct cpg_core_clk *core,
void __iomem *base, const char *parent_name,
struct raw_notifier_head *notifiers)
{
struct clk_init_data init;
struct sd_clock *clock;
struct clk *clk;
unsigned int i;
u32 sd_fc;
clock = kzalloc(sizeof(*clock), GFP_KERNEL);
if (!clock)
return ERR_PTR(-ENOMEM);
init.name = core->name;
init.ops = &cpg_sd_clock_ops;
init.flags = CLK_IS_BASIC | CLK_SET_RATE_PARENT;
init.parent_names = &parent_name;
init.num_parents = 1;
clock->csn.reg = base + core->offset;
clock->hw.init = &init;
clock->div_table = cpg_sd_div_table;
clock->div_num = ARRAY_SIZE(cpg_sd_div_table);
sd_fc = readl(clock->csn.reg) & CPG_SD_FC_MASK;
for (i = 0; i < clock->div_num; i++)
if (sd_fc == (clock->div_table[i].val & CPG_SD_FC_MASK))
break;
if (WARN_ON(i >= clock->div_num)) {
kfree(clock);
return ERR_PTR(-EINVAL);
}
clock->cur_div_idx = i;
clock->div_max = clock->div_table[0].div;
clock->div_min = clock->div_max;
for (i = 1; i < clock->div_num; i++) {
clock->div_max = max(clock->div_max, clock->div_table[i].div);
clock->div_min = min(clock->div_min, clock->div_table[i].div);
}
clk = clk_register(NULL, &clock->hw);
if (IS_ERR(clk))
goto free_clock;
cpg_simple_notifier_register(notifiers, &clock->csn);
return clk;
free_clock:
kfree(clock);
return clk;
}
static const struct rcar_gen3_cpg_pll_config *cpg_pll_config __initdata;
static unsigned int cpg_clk_extalr __initdata;
static u32 cpg_mode __initdata;
static u32 cpg_quirks __initdata;
#define PLL_ERRATA BIT(0) /* Missing PLL0/2/4 post-divider */
#define RCKCR_CKSEL BIT(1) /* Manual RCLK parent selection */
static const struct soc_device_attribute cpg_quirks_match[] __initconst = {
{
.soc_id = "r8a7795", .revision = "ES1.0",
.data = (void *)(PLL_ERRATA | RCKCR_CKSEL),
},
{
.soc_id = "r8a7795", .revision = "ES1.*",
.data = (void *)RCKCR_CKSEL,
},
{
.soc_id = "r8a7796", .revision = "ES1.0",
.data = (void *)RCKCR_CKSEL,
},
{ /* sentinel */ }
};
struct clk * __init rcar_gen3_cpg_clk_register(struct device *dev,
const struct cpg_core_clk *core, const struct cpg_mssr_info *info,
struct clk **clks, void __iomem *base,
struct raw_notifier_head *notifiers)
{
const struct clk *parent;
unsigned int mult = 1;
unsigned int div = 1;
u32 value;
parent = clks[core->parent & 0xffff]; /* CLK_TYPE_PE uses high bits */
if (IS_ERR(parent))
return ERR_CAST(parent);
switch (core->type) {
case CLK_TYPE_GEN3_MAIN:
div = cpg_pll_config->extal_div;
break;
case CLK_TYPE_GEN3_PLL0:
/*
* PLL0 is a configurable multiplier clock. Register it as a
* fixed factor clock for now as there's no generic multiplier
* clock implementation and we currently have no need to change
* the multiplier value.
*/
value = readl(base + CPG_PLL0CR);
mult = (((value >> 24) & 0x7f) + 1) * 2;
if (cpg_quirks & PLL_ERRATA)
mult *= 2;
break;
case CLK_TYPE_GEN3_PLL1:
mult = cpg_pll_config->pll1_mult;
div = cpg_pll_config->pll1_div;
break;
case CLK_TYPE_GEN3_PLL2:
/*
* PLL2 is a configurable multiplier clock. Register it as a
* fixed factor clock for now as there's no generic multiplier
* clock implementation and we currently have no need to change
* the multiplier value.
*/
value = readl(base + CPG_PLL2CR);
mult = (((value >> 24) & 0x7f) + 1) * 2;
if (cpg_quirks & PLL_ERRATA)
mult *= 2;
break;
case CLK_TYPE_GEN3_PLL3:
mult = cpg_pll_config->pll3_mult;
div = cpg_pll_config->pll3_div;
break;
case CLK_TYPE_GEN3_PLL4:
/*
* PLL4 is a configurable multiplier clock. Register it as a
* fixed factor clock for now as there's no generic multiplier
* clock implementation and we currently have no need to change
* the multiplier value.
*/
value = readl(base + CPG_PLL4CR);
mult = (((value >> 24) & 0x7f) + 1) * 2;
if (cpg_quirks & PLL_ERRATA)
mult *= 2;
break;
case CLK_TYPE_GEN3_SD:
return cpg_sd_clk_register(core, base, __clk_get_name(parent),
notifiers);
case CLK_TYPE_GEN3_R:
if (cpg_quirks & RCKCR_CKSEL) {
struct cpg_simple_notifier *csn;
csn = kzalloc(sizeof(*csn), GFP_KERNEL);
if (!csn)
return ERR_PTR(-ENOMEM);
csn->reg = base + CPG_RCKCR;
/*
* RINT is default.
* Only if EXTALR is populated, we switch to it.
*/
value = readl(csn->reg) & 0x3f;
if (clk_get_rate(clks[cpg_clk_extalr])) {
parent = clks[cpg_clk_extalr];
value |= BIT(15);
}
writel(value, csn->reg);
cpg_simple_notifier_register(notifiers, csn);
break;
}
/* Select parent clock of RCLK by MD28 */
if (cpg_mode & BIT(28))
parent = clks[cpg_clk_extalr];
break;
case CLK_TYPE_GEN3_PE:
/*
* Peripheral clock with a fixed divider, selectable between
* clean and spread spectrum parents using MD12
*/
if (cpg_mode & BIT(12)) {
/* Clean */
div = core->div & 0xffff;
} else {
/* SCCG */
parent = clks[core->parent >> 16];
if (IS_ERR(parent))
return ERR_CAST(parent);
div = core->div >> 16;
}
mult = 1;
break;
default:
return ERR_PTR(-EINVAL);
}
return clk_register_fixed_factor(NULL, core->name,
__clk_get_name(parent), 0, mult, div);
}
int __init rcar_gen3_cpg_init(const struct rcar_gen3_cpg_pll_config *config,
unsigned int clk_extalr, u32 mode)
{
const struct soc_device_attribute *attr;
cpg_pll_config = config;
cpg_clk_extalr = clk_extalr;
cpg_mode = mode;
attr = soc_device_match(cpg_quirks_match);
if (attr)
cpg_quirks = (uintptr_t)attr->data;
pr_debug("%s: mode = 0x%x quirks = 0x%x\n", __func__, mode, cpg_quirks);
return 0;
}