Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
config CLK_TEGRA_BPMP
def_bool y
depends on TEGRA_BPMP
config TEGRA_CLK_DFLL
depends on ARCH_TEGRA_114_SOC || ARCH_TEGRA_124_SOC || ARCH_TEGRA_210_SOC
select PM_OPP
def_bool y
config TEGRA124_CLK_EMC
bool
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0
obj-y += clk.o
obj-y += clk-audio-sync.o
obj-y += clk-device.o
obj-y += clk-dfll.o
obj-y += clk-divider.o
obj-y += clk-periph.o
obj-y += clk-periph-fixed.o
obj-y += clk-periph-gate.o
obj-y += clk-pll.o
obj-y += clk-pll-out.o
obj-y += clk-sdmmc-mux.o
obj-y += clk-super.o
obj-y += clk-tegra-audio.o
obj-y += clk-tegra-periph.o
obj-y += clk-tegra-fixed.o
obj-y += clk-tegra-super-cclk.o
obj-y += clk-tegra-super-gen4.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20-emc.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra20-emc.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += clk-tegra114.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124.o
obj-$(CONFIG_TEGRA_CLK_DFLL) += clk-tegra124-dfll-fcpu.o
obj-$(CONFIG_TEGRA124_CLK_EMC) += clk-tegra124-emc.o
obj-$(CONFIG_ARCH_TEGRA_132_SOC) += clk-tegra124.o
obj-y += cvb.o
obj-$(CONFIG_ARCH_TEGRA_210_SOC) += clk-tegra210.o
obj-$(CONFIG_ARCH_TEGRA_210_SOC) += clk-tegra210-emc.o
obj-$(CONFIG_CLK_TEGRA_BPMP) += clk-bpmp.o
obj-y += clk-utils.o
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/err.h>
#include "clk.h"
static unsigned long clk_sync_source_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_sync_source *sync = to_clk_sync_source(hw);
return sync->rate;
}
static int clk_sync_source_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra_clk_sync_source *sync = to_clk_sync_source(hw);
if (req->rate > sync->max_rate)
return -EINVAL;
else
return 0;
}
static int clk_sync_source_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_sync_source *sync = to_clk_sync_source(hw);
sync->rate = rate;
return 0;
}
const struct clk_ops tegra_clk_sync_source_ops = {
.determine_rate = clk_sync_source_determine_rate,
.set_rate = clk_sync_source_set_rate,
.recalc_rate = clk_sync_source_recalc_rate,
};
struct clk *tegra_clk_register_sync_source(const char *name,
unsigned long max_rate)
{
struct tegra_clk_sync_source *sync;
struct clk_init_data init;
struct clk *clk;
sync = kzalloc_obj(*sync);
if (!sync) {
pr_err("%s: could not allocate sync source clk\n", __func__);
return ERR_PTR(-ENOMEM);
}
sync->max_rate = max_rate;
init.ops = &tegra_clk_sync_source_ops;
init.name = name;
init.flags = 0;
init.parent_names = NULL;
init.num_parents = 0;
/* Data in .init is copied by clk_register(), so stack variable OK */
sync->hw.init = &init;
clk = clk_register(NULL, &sync->hw);
if (IS_ERR(clk))
kfree(sync);
return clk;
}
+705
View File
@@ -0,0 +1,705 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2016-2022 NVIDIA Corporation
*/
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/seq_buf.h>
#include <linux/slab.h>
#include <soc/tegra/bpmp.h>
#include <soc/tegra/bpmp-abi.h>
#define TEGRA_BPMP_DUMP_CLOCK_INFO 0
#define TEGRA_BPMP_CLK_HAS_MUX BIT(0)
#define TEGRA_BPMP_CLK_HAS_SET_RATE BIT(1)
#define TEGRA_BPMP_CLK_IS_ROOT BIT(2)
struct tegra_bpmp_clk_info {
unsigned int id;
char name[MRQ_CLK_NAME_MAXLEN];
unsigned int parents[MRQ_CLK_MAX_PARENTS];
unsigned int num_parents;
unsigned long flags;
};
struct tegra_bpmp_clk {
struct clk_hw hw;
struct tegra_bpmp *bpmp;
unsigned int id;
unsigned int num_parents;
unsigned int *parents;
};
static inline struct tegra_bpmp_clk *to_tegra_bpmp_clk(struct clk_hw *hw)
{
return container_of(hw, struct tegra_bpmp_clk, hw);
}
struct tegra_bpmp_clk_message {
unsigned int cmd;
unsigned int id;
struct {
const void *data;
size_t size;
} tx;
struct {
void *data;
size_t size;
int ret;
} rx;
};
static int tegra_bpmp_clk_transfer(struct tegra_bpmp *bpmp,
const struct tegra_bpmp_clk_message *clk)
{
struct mrq_clk_request request;
struct tegra_bpmp_message msg;
void *req = &request;
int err;
memset(&request, 0, sizeof(request));
request.cmd_and_id = (clk->cmd << 24) | clk->id;
/*
* The mrq_clk_request structure has an anonymous union at offset 4
* that contains all possible sub-command structures. Copy the data
* to that union. Ideally we'd be able to refer to it by name, but
* doing so would require changing the ABI header and increase the
* maintenance burden.
*/
memcpy(req + 4, clk->tx.data, clk->tx.size);
memset(&msg, 0, sizeof(msg));
msg.mrq = MRQ_CLK;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = clk->rx.data;
msg.rx.size = clk->rx.size;
err = tegra_bpmp_transfer(bpmp, &msg);
if (err < 0)
return err;
else if (msg.rx.ret < 0)
return -EINVAL;
return 0;
}
static int tegra_bpmp_clk_prepare(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct tegra_bpmp_clk_message msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_ENABLE;
msg.id = clk->id;
return tegra_bpmp_clk_transfer(clk->bpmp, &msg);
}
static void tegra_bpmp_clk_unprepare(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_DISABLE;
msg.id = clk->id;
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
dev_err(clk->bpmp->dev, "failed to disable clock %s: %d\n",
clk_hw_get_name(hw), err);
}
static int tegra_bpmp_clk_is_prepared(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_is_enabled_response response;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_IS_ENABLED;
msg.id = clk->id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
return response.state;
}
static unsigned long tegra_bpmp_clk_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_get_rate_response response;
struct cmd_clk_get_rate_request request;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return 0;
return response.rate;
}
static int tegra_bpmp_clk_determine_rate(struct clk_hw *hw,
struct clk_rate_request *rate_req)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_round_rate_response response;
struct cmd_clk_round_rate_request request;
struct tegra_bpmp_clk_message msg;
unsigned long rate;
int err;
rate = clamp(rate_req->rate, rate_req->min_rate, rate_req->max_rate);
memset(&request, 0, sizeof(request));
request.rate = min_t(u64, rate, S64_MAX);
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_ROUND_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
rate_req->rate = (unsigned long)response.rate;
return 0;
}
static int tegra_bpmp_clk_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_set_parent_response response;
struct cmd_clk_set_parent_request request;
struct tegra_bpmp_clk_message msg;
int err;
memset(&request, 0, sizeof(request));
request.parent_id = clk->parents[index];
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_SET_PARENT;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0)
return err;
/* XXX check parent ID in response */
return 0;
}
static u8 tegra_bpmp_clk_get_parent(struct clk_hw *hw)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_get_parent_response response;
struct tegra_bpmp_clk_message msg;
unsigned int i;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_PARENT;
msg.id = clk->id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(clk->bpmp, &msg);
if (err < 0) {
dev_err(clk->bpmp->dev, "failed to get parent for %s: %d\n",
clk_hw_get_name(hw), err);
return U8_MAX;
}
for (i = 0; i < clk->num_parents; i++)
if (clk->parents[i] == response.parent_id)
return i;
return U8_MAX;
}
static int tegra_bpmp_clk_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_bpmp_clk *clk = to_tegra_bpmp_clk(hw);
struct cmd_clk_set_rate_response response;
struct cmd_clk_set_rate_request request;
struct tegra_bpmp_clk_message msg;
memset(&request, 0, sizeof(request));
request.rate = min_t(u64, rate, S64_MAX);
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_SET_RATE;
msg.id = clk->id;
msg.tx.data = &request;
msg.tx.size = sizeof(request);
msg.rx.data = &response;
msg.rx.size = sizeof(response);
return tegra_bpmp_clk_transfer(clk->bpmp, &msg);
}
static const struct clk_ops tegra_bpmp_clk_gate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = clk_hw_determine_rate_no_reparent,
.set_parent = tegra_bpmp_clk_set_parent,
.get_parent = tegra_bpmp_clk_get_parent,
};
static const struct clk_ops tegra_bpmp_clk_rate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = tegra_bpmp_clk_determine_rate,
.set_rate = tegra_bpmp_clk_set_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_rate_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.determine_rate = tegra_bpmp_clk_determine_rate,
.set_parent = tegra_bpmp_clk_set_parent,
.get_parent = tegra_bpmp_clk_get_parent,
.set_rate = tegra_bpmp_clk_set_rate,
};
static const struct clk_ops tegra_bpmp_clk_mux_read_only_ops = {
.get_parent = tegra_bpmp_clk_get_parent,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_read_only_ops = {
.recalc_rate = tegra_bpmp_clk_recalc_rate,
};
static const struct clk_ops tegra_bpmp_clk_gate_mux_read_only_ops = {
.prepare = tegra_bpmp_clk_prepare,
.unprepare = tegra_bpmp_clk_unprepare,
.is_prepared = tegra_bpmp_clk_is_prepared,
.recalc_rate = tegra_bpmp_clk_recalc_rate,
.get_parent = tegra_bpmp_clk_get_parent,
};
static int tegra_bpmp_clk_get_max_id(struct tegra_bpmp *bpmp)
{
struct cmd_clk_get_max_clk_id_response response;
struct tegra_bpmp_clk_message msg;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_MAX_CLK_ID;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(bpmp, &msg);
if (err < 0)
return err;
if (response.max_id > INT_MAX)
return -E2BIG;
return response.max_id;
}
static int tegra_bpmp_clk_get_info(struct tegra_bpmp *bpmp, unsigned int id,
struct tegra_bpmp_clk_info *info)
{
struct cmd_clk_get_all_info_response response;
struct tegra_bpmp_clk_message msg;
unsigned int i;
int err;
memset(&msg, 0, sizeof(msg));
msg.cmd = CMD_CLK_GET_ALL_INFO;
msg.id = id;
msg.rx.data = &response;
msg.rx.size = sizeof(response);
err = tegra_bpmp_clk_transfer(bpmp, &msg);
if (err < 0)
return err;
strscpy(info->name, response.name, MRQ_CLK_NAME_MAXLEN);
info->num_parents = response.num_parents;
for (i = 0; i < info->num_parents; i++)
info->parents[i] = response.parents[i];
info->flags = response.flags;
return 0;
}
static void tegra_bpmp_clk_info_dump(struct tegra_bpmp *bpmp,
const char *level,
const struct tegra_bpmp_clk_info *info)
{
const char *prefix = "";
struct seq_buf buf;
unsigned int i;
char flags[64];
seq_buf_init(&buf, flags, sizeof(flags));
if (info->flags)
seq_buf_printf(&buf, "(");
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX) {
seq_buf_printf(&buf, "%smux", prefix);
prefix = ", ";
}
if ((info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE) == 0) {
seq_buf_printf(&buf, "%sfixed", prefix);
prefix = ", ";
}
if (info->flags & TEGRA_BPMP_CLK_IS_ROOT) {
seq_buf_printf(&buf, "%sroot", prefix);
prefix = ", ";
}
if (info->flags)
seq_buf_printf(&buf, ")");
dev_printk(level, bpmp->dev, "%03u: %s\n", info->id, info->name);
dev_printk(level, bpmp->dev, " flags: %lx %s\n", info->flags, flags);
dev_printk(level, bpmp->dev, " parents: %u\n", info->num_parents);
for (i = 0; i < info->num_parents; i++)
dev_printk(level, bpmp->dev, " %03u\n", info->parents[i]);
}
static int tegra_bpmp_probe_clocks(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info **clocksp)
{
struct tegra_bpmp_clk_info *clocks;
unsigned int max_id, id, count = 0;
unsigned int holes = 0;
int err;
err = tegra_bpmp_clk_get_max_id(bpmp);
if (err < 0)
return err;
max_id = err;
dev_dbg(bpmp->dev, "maximum clock ID: %u\n", max_id);
clocks = kzalloc_objs(*clocks, max_id + 1);
if (!clocks)
return -ENOMEM;
for (id = 0; id <= max_id; id++) {
struct tegra_bpmp_clk_info *info = &clocks[count];
err = tegra_bpmp_clk_get_info(bpmp, id, info);
if (err < 0)
continue;
if (info->num_parents >= U8_MAX) {
dev_err(bpmp->dev,
"clock %u has too many parents (%u, max: %u)\n",
id, info->num_parents, U8_MAX);
continue;
}
/* clock not exposed by BPMP */
if (info->name[0] == '\0') {
holes++;
continue;
}
info->id = id;
count++;
if (TEGRA_BPMP_DUMP_CLOCK_INFO)
tegra_bpmp_clk_info_dump(bpmp, KERN_DEBUG, info);
}
dev_dbg(bpmp->dev, "holes: %u\n", holes);
*clocksp = clocks;
return count;
}
static unsigned int
tegra_bpmp_clk_id_to_index(const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks, unsigned int id)
{
unsigned int i;
for (i = 0; i < num_clocks; i++)
if (clocks[i].id == id)
return i;
return UINT_MAX;
}
static const struct tegra_bpmp_clk_info *
tegra_bpmp_clk_find(const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks, unsigned int id)
{
unsigned int i;
i = tegra_bpmp_clk_id_to_index(clocks, num_clocks, id);
if (i < num_clocks)
return &clocks[i];
return NULL;
}
static struct tegra_bpmp_clk *
tegra_bpmp_clk_register(struct tegra_bpmp *bpmp,
const struct tegra_bpmp_clk_info *info,
const struct tegra_bpmp_clk_info *clocks,
unsigned int num_clocks)
{
struct tegra_bpmp_clk *clk;
struct clk_init_data init;
const char **parents;
unsigned int i;
int err;
clk = devm_kzalloc(bpmp->dev, sizeof(*clk), GFP_KERNEL);
if (!clk)
return ERR_PTR(-ENOMEM);
clk->id = info->id;
clk->bpmp = bpmp;
clk->parents = devm_kcalloc(bpmp->dev, info->num_parents,
sizeof(*clk->parents), GFP_KERNEL);
if (!clk->parents)
return ERR_PTR(-ENOMEM);
clk->num_parents = info->num_parents;
/* hardware clock initialization */
memset(&init, 0, sizeof(init));
init.name = info->name;
clk->hw.init = &init;
if (info->flags & BPMP_CLK_STATE_CHANGE_DENIED) {
if ((info->flags & BPMP_CLK_RATE_PARENT_CHANGE_DENIED) == 0) {
dev_WARN(bpmp->dev,
"Firmware bug! Inconsistent permission bits for clock %s. State and parent/rate changes disabled.",
init.name);
}
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX)
init.ops = &tegra_bpmp_clk_mux_read_only_ops;
else
init.ops = &tegra_bpmp_clk_read_only_ops;
} else if (info->flags & BPMP_CLK_RATE_PARENT_CHANGE_DENIED) {
if (info->flags & TEGRA_BPMP_CLK_HAS_MUX)
init.ops = &tegra_bpmp_clk_gate_mux_read_only_ops;
else
init.ops = &tegra_bpmp_clk_gate_ops;
} else if (info->flags & TEGRA_BPMP_CLK_HAS_MUX) {
if (info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE)
init.ops = &tegra_bpmp_clk_mux_rate_ops;
else
init.ops = &tegra_bpmp_clk_mux_ops;
} else {
if (info->flags & TEGRA_BPMP_CLK_HAS_SET_RATE)
init.ops = &tegra_bpmp_clk_rate_ops;
else
init.ops = &tegra_bpmp_clk_gate_ops;
}
init.num_parents = info->num_parents;
parents = kcalloc(info->num_parents, sizeof(*parents), GFP_KERNEL);
if (!parents)
return ERR_PTR(-ENOMEM);
for (i = 0; i < info->num_parents; i++) {
const struct tegra_bpmp_clk_info *parent;
/* keep a private copy of the ID to parent index map */
clk->parents[i] = info->parents[i];
parent = tegra_bpmp_clk_find(clocks, num_clocks,
info->parents[i]);
if (!parent) {
dev_err(bpmp->dev, "no parent %u found for %u\n",
info->parents[i], info->id);
continue;
}
parents[i] = parent->name;
}
init.parent_names = parents;
err = devm_clk_hw_register(bpmp->dev, &clk->hw);
kfree(parents);
if (err < 0)
return ERR_PTR(err);
return clk;
}
static void tegra_bpmp_register_clocks_one(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info *infos,
unsigned int i,
unsigned int count)
{
unsigned int j;
struct tegra_bpmp_clk_info *info;
struct tegra_bpmp_clk *clk;
if (bpmp->clocks[i]) {
/* already registered */
return;
}
info = &infos[i];
for (j = 0; j < info->num_parents; ++j) {
unsigned int p_id = info->parents[j];
unsigned int p_i = tegra_bpmp_clk_id_to_index(infos, count,
p_id);
if (p_i < count)
tegra_bpmp_register_clocks_one(bpmp, infos, p_i, count);
}
clk = tegra_bpmp_clk_register(bpmp, info, infos, count);
if (IS_ERR(clk)) {
dev_err(bpmp->dev,
"failed to register clock %u (%s): %ld\n",
info->id, info->name, PTR_ERR(clk));
/* intentionally store the error pointer to
* bpmp->clocks[i] to avoid re-attempting the
* registration later
*/
}
bpmp->clocks[i] = clk;
}
static int tegra_bpmp_register_clocks(struct tegra_bpmp *bpmp,
struct tegra_bpmp_clk_info *infos,
unsigned int count)
{
unsigned int i;
bpmp->num_clocks = count;
bpmp->clocks = devm_kcalloc(bpmp->dev, count, sizeof(*bpmp->clocks), GFP_KERNEL);
if (!bpmp->clocks)
return -ENOMEM;
for (i = 0; i < count; i++) {
tegra_bpmp_register_clocks_one(bpmp, infos, i, count);
}
return 0;
}
static void tegra_bpmp_unregister_clocks(struct tegra_bpmp *bpmp)
{
unsigned int i;
for (i = 0; i < bpmp->num_clocks; i++)
clk_hw_unregister(&bpmp->clocks[i]->hw);
}
static struct clk_hw *tegra_bpmp_clk_of_xlate(struct of_phandle_args *clkspec,
void *data)
{
unsigned int id = clkspec->args[0], i;
struct tegra_bpmp *bpmp = data;
for (i = 0; i < bpmp->num_clocks; i++) {
struct tegra_bpmp_clk *clk = bpmp->clocks[i];
if (!clk)
continue;
if (clk->id == id)
return &clk->hw;
}
return NULL;
}
int tegra_bpmp_init_clocks(struct tegra_bpmp *bpmp)
{
struct tegra_bpmp_clk_info *clocks;
unsigned int count;
int err;
err = tegra_bpmp_probe_clocks(bpmp, &clocks);
if (err < 0)
return err;
count = err;
dev_dbg(bpmp->dev, "%u clocks probed\n", count);
err = tegra_bpmp_register_clocks(bpmp, clocks, count);
if (err < 0)
goto free;
err = of_clk_add_hw_provider(bpmp->dev->of_node,
tegra_bpmp_clk_of_xlate,
bpmp);
if (err < 0) {
tegra_bpmp_unregister_clocks(bpmp);
goto free;
}
free:
kfree(clocks);
return err;
}
+209
View File
@@ -0,0 +1,209 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/pm_domain.h>
#include <linux/pm_opp.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <soc/tegra/common.h>
#include "clk.h"
/*
* This driver manages performance state of the core power domain for the
* independent PLLs and system clocks. We created a virtual clock device
* for such clocks, see tegra_clk_dev_register().
*/
struct tegra_clk_device {
struct notifier_block clk_nb;
struct device *dev;
struct clk_hw *hw;
struct mutex lock;
};
static int tegra_clock_set_pd_state(struct tegra_clk_device *clk_dev,
unsigned long rate)
{
struct device *dev = clk_dev->dev;
struct dev_pm_opp *opp;
unsigned int pstate;
opp = dev_pm_opp_find_freq_ceil(dev, &rate);
if (opp == ERR_PTR(-ERANGE)) {
/*
* Some clocks may be unused by a particular board and they
* may have uninitiated clock rate that is overly high. In
* this case clock is expected to be disabled, but still we
* need to set up performance state of the power domain and
* not error out clk initialization. A typical example is
* a PCIe clock on Android tablets.
*/
dev_dbg(dev, "failed to find ceil OPP for %luHz\n", rate);
opp = dev_pm_opp_find_freq_floor(dev, &rate);
}
if (IS_ERR(opp)) {
dev_err(dev, "failed to find OPP for %luHz: %pe\n", rate, opp);
return PTR_ERR(opp);
}
pstate = dev_pm_opp_get_required_pstate(opp, 0);
dev_pm_opp_put(opp);
return dev_pm_genpd_set_performance_state(dev, pstate);
}
static int tegra_clock_change_notify(struct notifier_block *nb,
unsigned long msg, void *data)
{
struct clk_notifier_data *cnd = data;
struct tegra_clk_device *clk_dev;
int err = 0;
clk_dev = container_of(nb, struct tegra_clk_device, clk_nb);
mutex_lock(&clk_dev->lock);
switch (msg) {
case PRE_RATE_CHANGE:
if (cnd->new_rate > cnd->old_rate)
err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
break;
case ABORT_RATE_CHANGE:
err = tegra_clock_set_pd_state(clk_dev, cnd->old_rate);
break;
case POST_RATE_CHANGE:
if (cnd->new_rate < cnd->old_rate)
err = tegra_clock_set_pd_state(clk_dev, cnd->new_rate);
break;
default:
break;
}
mutex_unlock(&clk_dev->lock);
return notifier_from_errno(err);
}
static int tegra_clock_sync_pd_state(struct tegra_clk_device *clk_dev)
{
unsigned long rate;
int ret;
mutex_lock(&clk_dev->lock);
rate = clk_hw_get_rate(clk_dev->hw);
ret = tegra_clock_set_pd_state(clk_dev, rate);
mutex_unlock(&clk_dev->lock);
return ret;
}
static int tegra_clock_probe(struct platform_device *pdev)
{
struct tegra_core_opp_params opp_params = {};
struct tegra_clk_device *clk_dev;
struct device *dev = &pdev->dev;
struct clk *clk;
int err;
if (!dev->pm_domain)
return -EINVAL;
clk_dev = devm_kzalloc(dev, sizeof(*clk_dev), GFP_KERNEL);
if (!clk_dev)
return -ENOMEM;
clk = devm_clk_get(dev, NULL);
if (IS_ERR(clk))
return PTR_ERR(clk);
clk_dev->dev = dev;
clk_dev->hw = __clk_get_hw(clk);
clk_dev->clk_nb.notifier_call = tegra_clock_change_notify;
mutex_init(&clk_dev->lock);
platform_set_drvdata(pdev, clk_dev);
/*
* Runtime PM was already enabled for this device by the parent clk
* driver and power domain state should be synced under clk_dev lock,
* hence we don't use the common OPP helper that initializes OPP
* state. For some clocks common OPP helper may fail to find ceil
* rate, it's handled by this driver.
*/
err = devm_tegra_core_dev_init_opp_table(dev, &opp_params);
if (err)
return err;
err = clk_notifier_register(clk, &clk_dev->clk_nb);
if (err) {
dev_err(dev, "failed to register clk notifier: %d\n", err);
return err;
}
/*
* The driver is attaching to a potentially active/resumed clock, hence
* we need to sync the power domain performance state in a accordance to
* the clock rate if clock is resumed.
*/
err = tegra_clock_sync_pd_state(clk_dev);
if (err)
goto unreg_clk;
return 0;
unreg_clk:
clk_notifier_unregister(clk, &clk_dev->clk_nb);
return err;
}
/*
* Tegra GENPD driver enables clocks during NOIRQ phase. It can't be done
* for clocks served by this driver because runtime PM is unavailable in
* NOIRQ phase. We will keep clocks resumed during suspend to mitigate this
* problem. In practice this makes no difference from a power management
* perspective since voltage is kept at a nominal level during suspend anyways.
*/
static inline int tegra_clock_suspend(struct device *dev)
{
int ret;
ret = pm_runtime_resume(dev);
if (ret < 0)
return ret;
return 0;
}
static const struct dev_pm_ops tegra_clock_pm = {
SET_SYSTEM_SLEEP_PM_OPS(tegra_clock_suspend, NULL)
};
static const struct of_device_id tegra_clock_match[] = {
{ .compatible = "nvidia,tegra20-sclk" },
{ .compatible = "nvidia,tegra30-sclk" },
{ .compatible = "nvidia,tegra30-pllc" },
{ .compatible = "nvidia,tegra30-plle" },
{ .compatible = "nvidia,tegra30-pllm" },
{ }
};
static struct platform_driver tegra_clock_driver = {
.driver = {
.name = "tegra-clock",
.of_match_table = tegra_clock_match,
.pm = &tegra_clock_pm,
.suppress_bind_attrs = true,
},
.probe = tegra_clock_probe,
};
builtin_platform_driver(tegra_clock_driver);
File diff suppressed because it is too large Load Diff
+48
View File
@@ -0,0 +1,48 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* clk-dfll.h - prototypes and macros for the Tegra DFLL clocksource driver
* Copyright (C) 2013-2019 NVIDIA Corporation. All rights reserved.
*
* Aleksandr Frid <afrid@nvidia.com>
* Paul Walmsley <pwalmsley@nvidia.com>
*/
#ifndef __DRIVERS_CLK_TEGRA_CLK_DFLL_H
#define __DRIVERS_CLK_TEGRA_CLK_DFLL_H
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/types.h>
#include "cvb.h"
/**
* struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver
* @dev: struct device * that holds the OPP table for the DFLL
* @max_freq: maximum frequency supported on this SoC
* @cvb: CPU frequency table for this SoC
* @alignment: parameters of the regulator step and offset
* @init_clock_trimmers: callback to initialize clock trimmers
* @set_clock_trimmers_high: callback to tune clock trimmers for high voltage
* @set_clock_trimmers_low: callback to tune clock trimmers for low voltage
*/
struct tegra_dfll_soc_data {
struct device *dev;
unsigned long max_freq;
const struct cvb_table *cvb;
struct rail_alignment alignment;
void (*init_clock_trimmers)(void);
void (*set_clock_trimmers_high)(void);
void (*set_clock_trimmers_low)(void);
};
int tegra_dfll_register(struct platform_device *pdev,
struct tegra_dfll_soc_data *soc);
struct tegra_dfll_soc_data *tegra_dfll_unregister(struct platform_device *pdev);
int tegra_dfll_runtime_suspend(struct device *dev);
int tegra_dfll_runtime_resume(struct device *dev);
int tegra_dfll_suspend(struct device *dev);
int tegra_dfll_resume(struct device *dev);
#endif /* __DRIVERS_CLK_TEGRA_CLK_DFLL_H */
+194
View File
@@ -0,0 +1,194 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk-provider.h>
#include "clk.h"
#define pll_out_override(p) (BIT((p->shift - 6)))
#define div_mask(d) ((1 << (d->width)) - 1)
#define get_mul(d) (1 << d->frac_width)
#define get_max_div(d) div_mask(d)
#define PERIPH_CLK_UART_DIV_ENB BIT(24)
static int get_div(struct tegra_clk_frac_div *divider, unsigned long rate,
unsigned long parent_rate)
{
int div;
div = div_frac_get(rate, parent_rate, divider->width,
divider->frac_width, divider->flags);
if (div < 0)
return 0;
return div;
}
static unsigned long clk_frac_div_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
u32 reg;
int div, mul;
u64 rate = parent_rate;
reg = readl_relaxed(divider->reg);
if ((divider->flags & TEGRA_DIVIDER_UART) &&
!(reg & PERIPH_CLK_UART_DIV_ENB))
return rate;
div = (reg >> divider->shift) & div_mask(divider);
mul = get_mul(divider);
div += mul;
rate *= mul;
rate += div - 1;
do_div(rate, div);
return rate;
}
static int clk_frac_div_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
int div, mul;
unsigned long output_rate = req->best_parent_rate;
if (!req->rate) {
req->rate = output_rate;
return 0;
}
div = get_div(divider, req->rate, output_rate);
if (div < 0) {
req->rate = req->best_parent_rate;
return 0;
}
mul = get_mul(divider);
req->rate = DIV_ROUND_UP(output_rate * mul, div + mul);
return 0;
}
static int clk_frac_div_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_frac_div *divider = to_clk_frac_div(hw);
int div;
unsigned long flags = 0;
u32 val;
div = get_div(divider, rate, parent_rate);
if (div < 0)
return div;
if (divider->lock)
spin_lock_irqsave(divider->lock, flags);
val = readl_relaxed(divider->reg);
val &= ~(div_mask(divider) << divider->shift);
val |= div << divider->shift;
if (divider->flags & TEGRA_DIVIDER_UART) {
if (div)
val |= PERIPH_CLK_UART_DIV_ENB;
else
val &= ~PERIPH_CLK_UART_DIV_ENB;
}
if (divider->flags & TEGRA_DIVIDER_FIXED)
val |= pll_out_override(divider);
writel_relaxed(val, divider->reg);
if (divider->lock)
spin_unlock_irqrestore(divider->lock, flags);
return 0;
}
static void clk_divider_restore_context(struct clk_hw *hw)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned long parent_rate = clk_hw_get_rate(parent);
unsigned long rate = clk_hw_get_rate(hw);
if (clk_frac_div_set_rate(hw, rate, parent_rate) < 0)
WARN_ON(1);
}
const struct clk_ops tegra_clk_frac_div_ops = {
.recalc_rate = clk_frac_div_recalc_rate,
.set_rate = clk_frac_div_set_rate,
.determine_rate = clk_frac_div_determine_rate,
.restore_context = clk_divider_restore_context,
};
struct clk *tegra_clk_register_divider(const char *name,
const char *parent_name, void __iomem *reg,
unsigned long flags, u8 clk_divider_flags, u8 shift, u8 width,
u8 frac_width, spinlock_t *lock)
{
struct tegra_clk_frac_div *divider;
struct clk *clk;
struct clk_init_data init;
divider = kzalloc_obj(*divider);
if (!divider) {
pr_err("%s: could not allocate fractional divider clk\n",
__func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.ops = &tegra_clk_frac_div_ops;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.num_parents = parent_name ? 1 : 0;
divider->reg = reg;
divider->shift = shift;
divider->width = width;
divider->frac_width = frac_width;
divider->lock = lock;
divider->flags = clk_divider_flags;
/* Data in .init is copied by clk_register(), so stack variable OK */
divider->hw.init = &init;
clk = clk_register(NULL, &divider->hw);
if (IS_ERR(clk))
kfree(divider);
return clk;
}
static const struct clk_div_table mc_div_table[] = {
{ .val = 0, .div = 2 },
{ .val = 1, .div = 1 },
{ .val = 0, .div = 0 },
};
struct clk *tegra_clk_register_mc(const char *name, const char *parent_name,
void __iomem *reg, spinlock_t *lock)
{
return clk_register_divider_table(NULL, name, parent_name,
CLK_IS_CRITICAL,
reg, 16, 1, CLK_DIVIDER_READ_ONLY,
mc_div_table, lock);
}
+325
View File
@@ -0,0 +1,325 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This header provides IDs for clocks common between several Tegra SoCs
*/
#ifndef _TEGRA_CLK_ID_H
#define _TEGRA_CLK_ID_H
enum clk_id {
tegra_clk_actmon,
tegra_clk_adx,
tegra_clk_adx1,
tegra_clk_afi,
tegra_clk_amx,
tegra_clk_amx1,
tegra_clk_apb2ape,
tegra_clk_ahbdma,
tegra_clk_apbdma,
tegra_clk_apbif,
tegra_clk_ape,
tegra_clk_audio0,
tegra_clk_audio0_2x,
tegra_clk_audio0_mux,
tegra_clk_audio1,
tegra_clk_audio1_2x,
tegra_clk_audio1_mux,
tegra_clk_audio2,
tegra_clk_audio2_2x,
tegra_clk_audio2_mux,
tegra_clk_audio3,
tegra_clk_audio3_2x,
tegra_clk_audio3_mux,
tegra_clk_audio4,
tegra_clk_audio4_2x,
tegra_clk_audio4_mux,
tegra_clk_bsea,
tegra_clk_bsev,
tegra_clk_cclk_g,
tegra_clk_cclk_lp,
tegra_clk_cilab,
tegra_clk_cilcd,
tegra_clk_cile,
tegra_clk_clk_32k,
tegra_clk_clk72Mhz,
tegra_clk_clk72Mhz_8,
tegra_clk_clk_m,
tegra_clk_osc,
tegra_clk_osc_div2,
tegra_clk_osc_div4,
tegra_clk_cml0,
tegra_clk_cml1,
tegra_clk_csi,
tegra_clk_csite,
tegra_clk_csite_8,
tegra_clk_csus,
tegra_clk_cve,
tegra_clk_dam0,
tegra_clk_dam1,
tegra_clk_dam2,
tegra_clk_d_audio,
tegra_clk_dbgapb,
tegra_clk_dds,
tegra_clk_dfll_ref,
tegra_clk_dfll_soc,
tegra_clk_disp1,
tegra_clk_disp1_8,
tegra_clk_disp2,
tegra_clk_disp2_8,
tegra_clk_dp2,
tegra_clk_dpaux,
tegra_clk_dpaux1,
tegra_clk_dsialp,
tegra_clk_dsia_mux,
tegra_clk_dsiblp,
tegra_clk_dsib_mux,
tegra_clk_dtv,
tegra_clk_emc,
tegra_clk_entropy,
tegra_clk_entropy_8,
tegra_clk_epp,
tegra_clk_epp_8,
tegra_clk_extern1,
tegra_clk_extern2,
tegra_clk_extern3,
tegra_clk_fuse,
tegra_clk_fuse_burn,
tegra_clk_gpu,
tegra_clk_gr2d,
tegra_clk_gr2d_8,
tegra_clk_gr3d,
tegra_clk_gr3d_8,
tegra_clk_hclk,
tegra_clk_hda,
tegra_clk_hda_8,
tegra_clk_hda2codec_2x,
tegra_clk_hda2codec_2x_8,
tegra_clk_hda2hdmi,
tegra_clk_hdmi,
tegra_clk_hdmi_audio,
tegra_clk_host1x,
tegra_clk_host1x_8,
tegra_clk_host1x_9,
tegra_clk_hsic_trk,
tegra_clk_i2c1,
tegra_clk_i2c2,
tegra_clk_i2c3,
tegra_clk_i2c4,
tegra_clk_i2c5,
tegra_clk_i2c6,
tegra_clk_i2cslow,
tegra_clk_i2s0,
tegra_clk_i2s0_sync,
tegra_clk_i2s1,
tegra_clk_i2s1_sync,
tegra_clk_i2s2,
tegra_clk_i2s2_sync,
tegra_clk_i2s3,
tegra_clk_i2s3_sync,
tegra_clk_i2s4,
tegra_clk_i2s4_sync,
tegra_clk_isp,
tegra_clk_isp_8,
tegra_clk_isp_9,
tegra_clk_ispb,
tegra_clk_kbc,
tegra_clk_kfuse,
tegra_clk_la,
tegra_clk_maud,
tegra_clk_mipi,
tegra_clk_mipibif,
tegra_clk_mipi_cal,
tegra_clk_mpe,
tegra_clk_mselect,
tegra_clk_msenc,
tegra_clk_ndflash,
tegra_clk_ndflash_8,
tegra_clk_ndspeed,
tegra_clk_ndspeed_8,
tegra_clk_nor,
tegra_clk_nvdec,
tegra_clk_nvenc,
tegra_clk_nvjpg,
tegra_clk_owr,
tegra_clk_owr_8,
tegra_clk_pcie,
tegra_clk_pclk,
tegra_clk_pll_a,
tegra_clk_pll_a_out0,
tegra_clk_pll_a1,
tegra_clk_pll_c,
tegra_clk_pll_c2,
tegra_clk_pll_c3,
tegra_clk_pll_c4,
tegra_clk_pll_c4_out0,
tegra_clk_pll_c4_out1,
tegra_clk_pll_c4_out2,
tegra_clk_pll_c4_out3,
tegra_clk_pll_c_out1,
tegra_clk_pll_d,
tegra_clk_pll_d2,
tegra_clk_pll_d2_out0,
tegra_clk_pll_d_out0,
tegra_clk_pll_dp,
tegra_clk_pll_e_out0,
tegra_clk_pll_g_ref,
tegra_clk_pll_m,
tegra_clk_pll_m_out1,
tegra_clk_pll_mb,
tegra_clk_pll_p,
tegra_clk_pll_p_out1,
tegra_clk_pll_p_out2,
tegra_clk_pll_p_out2_int,
tegra_clk_pll_p_out3,
tegra_clk_pll_p_out4,
tegra_clk_pll_p_out4_cpu,
tegra_clk_pll_p_out5,
tegra_clk_pll_p_out_hsio,
tegra_clk_pll_p_out_xusb,
tegra_clk_pll_p_out_cpu,
tegra_clk_pll_p_out_adsp,
tegra_clk_pll_ref,
tegra_clk_pll_re_out,
tegra_clk_pll_re_vco,
tegra_clk_pll_u,
tegra_clk_pll_u_out,
tegra_clk_pll_u_out1,
tegra_clk_pll_u_out2,
tegra_clk_pll_u_12m,
tegra_clk_pll_u_480m,
tegra_clk_pll_u_48m,
tegra_clk_pll_u_60m,
tegra_clk_pll_x,
tegra_clk_pll_x_out0,
tegra_clk_pwm,
tegra_clk_qspi,
tegra_clk_rtc,
tegra_clk_sata,
tegra_clk_sata_8,
tegra_clk_sata_cold,
tegra_clk_sata_oob,
tegra_clk_sata_oob_8,
tegra_clk_sbc1,
tegra_clk_sbc1_8,
tegra_clk_sbc1_9,
tegra_clk_sbc2,
tegra_clk_sbc2_8,
tegra_clk_sbc2_9,
tegra_clk_sbc3,
tegra_clk_sbc3_8,
tegra_clk_sbc3_9,
tegra_clk_sbc4,
tegra_clk_sbc4_8,
tegra_clk_sbc4_9,
tegra_clk_sbc5,
tegra_clk_sbc5_8,
tegra_clk_sbc6,
tegra_clk_sbc6_8,
tegra_clk_sclk,
tegra_clk_sdmmc_legacy,
tegra_clk_sdmmc1,
tegra_clk_sdmmc1_8,
tegra_clk_sdmmc1_9,
tegra_clk_sdmmc2,
tegra_clk_sdmmc2_8,
tegra_clk_sdmmc3,
tegra_clk_sdmmc3_8,
tegra_clk_sdmmc3_9,
tegra_clk_sdmmc4,
tegra_clk_sdmmc4_8,
tegra_clk_se,
tegra_clk_se_10,
tegra_clk_soc_therm,
tegra_clk_soc_therm_8,
tegra_clk_sor0,
tegra_clk_sor0_out,
tegra_clk_sor1,
tegra_clk_sor1_out,
tegra_clk_spdif,
tegra_clk_spdif_2x,
tegra_clk_spdif_in,
tegra_clk_spdif_in_8,
tegra_clk_spdif_in_sync,
tegra_clk_spdif_mux,
tegra_clk_spdif_out,
tegra_clk_timer,
tegra_clk_trace,
tegra_clk_tsec,
tegra_clk_tsec_8,
tegra_clk_tsecb,
tegra_clk_tsensor,
tegra_clk_tvdac,
tegra_clk_tvo,
tegra_clk_uarta,
tegra_clk_uarta_8,
tegra_clk_uartb,
tegra_clk_uartb_8,
tegra_clk_uartc,
tegra_clk_uartc_8,
tegra_clk_uartd,
tegra_clk_uartd_8,
tegra_clk_uarte,
tegra_clk_uarte_8,
tegra_clk_uartape,
tegra_clk_usb2,
tegra_clk_usb2_hsic_trk,
tegra_clk_usb2_trk,
tegra_clk_usb3,
tegra_clk_usbd,
tegra_clk_vcp,
tegra_clk_vde,
tegra_clk_vde_8,
tegra_clk_vfir,
tegra_clk_vi,
tegra_clk_vi_8,
tegra_clk_vi_9,
tegra_clk_vi_10,
tegra_clk_vi_i2c,
tegra_clk_vic03,
tegra_clk_vic03_8,
tegra_clk_vim2_clk,
tegra_clk_vimclk_sync,
tegra_clk_vi_sensor,
tegra_clk_vi_sensor_8,
tegra_clk_vi_sensor_9,
tegra_clk_vi_sensor2,
tegra_clk_vi_sensor2_8,
tegra_clk_xusb_dev,
tegra_clk_xusb_dev_src,
tegra_clk_xusb_dev_src_8,
tegra_clk_xusb_falcon_src,
tegra_clk_xusb_falcon_src_8,
tegra_clk_xusb_fs_src,
tegra_clk_xusb_gate,
tegra_clk_xusb_host,
tegra_clk_xusb_host_src,
tegra_clk_xusb_host_src_8,
tegra_clk_xusb_hs_src,
tegra_clk_xusb_hs_src_4,
tegra_clk_xusb_ss,
tegra_clk_xusb_ss_src,
tegra_clk_xusb_ss_src_8,
tegra_clk_xusb_ss_div2,
tegra_clk_xusb_ssp_src,
tegra_clk_sclk_mux,
tegra_clk_sor_safe,
tegra_clk_cec,
tegra_clk_ispa,
tegra_clk_dmic1,
tegra_clk_dmic2,
tegra_clk_dmic3,
tegra_clk_dmic1_sync_clk,
tegra_clk_dmic2_sync_clk,
tegra_clk_dmic3_sync_clk,
tegra_clk_dmic1_sync_clk_mux,
tegra_clk_dmic2_sync_clk_mux,
tegra_clk_dmic3_sync_clk_mux,
tegra_clk_iqc1,
tegra_clk_iqc2,
tegra_clk_pll_a_out_adsp,
tegra_clk_pll_a_out0_out_adsp,
tegra_clk_adsp,
tegra_clk_adsp_neon,
tegra_clk_max,
};
#endif /* _TEGRA_CLK_ID_H */
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2015, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk-provider.h>
#include <linux/io.h>
#include "clk.h"
static inline struct tegra_clk_periph_fixed *
to_tegra_clk_periph_fixed(struct clk_hw *hw)
{
return container_of(hw, struct tegra_clk_periph_fixed, hw);
}
static int tegra_clk_periph_fixed_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_periph_fixed *fixed = to_tegra_clk_periph_fixed(hw);
u32 mask = 1 << (fixed->num % 32), value;
value = readl(fixed->base + fixed->regs->enb_reg);
if (value & mask) {
value = readl(fixed->base + fixed->regs->rst_reg);
if ((value & mask) == 0)
return 1;
}
return 0;
}
static int tegra_clk_periph_fixed_enable(struct clk_hw *hw)
{
struct tegra_clk_periph_fixed *fixed = to_tegra_clk_periph_fixed(hw);
u32 mask = 1 << (fixed->num % 32);
writel(mask, fixed->base + fixed->regs->enb_set_reg);
return 0;
}
static void tegra_clk_periph_fixed_disable(struct clk_hw *hw)
{
struct tegra_clk_periph_fixed *fixed = to_tegra_clk_periph_fixed(hw);
u32 mask = 1 << (fixed->num % 32);
writel(mask, fixed->base + fixed->regs->enb_clr_reg);
}
static unsigned long
tegra_clk_periph_fixed_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_periph_fixed *fixed = to_tegra_clk_periph_fixed(hw);
unsigned long long rate;
rate = (unsigned long long)parent_rate * fixed->mul;
do_div(rate, fixed->div);
return (unsigned long)rate;
}
static const struct clk_ops tegra_clk_periph_fixed_ops = {
.is_enabled = tegra_clk_periph_fixed_is_enabled,
.enable = tegra_clk_periph_fixed_enable,
.disable = tegra_clk_periph_fixed_disable,
.recalc_rate = tegra_clk_periph_fixed_recalc_rate,
};
struct clk *tegra_clk_register_periph_fixed(const char *name,
const char *parent,
unsigned long flags,
void __iomem *base,
unsigned int mul,
unsigned int div,
unsigned int num)
{
const struct tegra_clk_periph_regs *regs;
struct tegra_clk_periph_fixed *fixed;
struct clk_init_data init;
struct clk *clk;
regs = get_reg_bank(num);
if (!regs)
return ERR_PTR(-EINVAL);
fixed = kzalloc_obj(*fixed);
if (!fixed)
return ERR_PTR(-ENOMEM);
init.name = name;
init.flags = flags;
init.parent_names = parent ? &parent : NULL;
init.num_parents = parent ? 1 : 0;
init.ops = &tegra_clk_periph_fixed_ops;
fixed->base = base;
fixed->regs = regs;
fixed->mul = mul;
fixed->div = div;
fixed->num = num;
fixed->hw.init = &init;
clk = clk_register(NULL, &fixed->hw);
if (IS_ERR(clk))
kfree(fixed);
return clk;
}
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk-provider.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <soc/tegra/fuse.h>
#include "clk.h"
static DEFINE_SPINLOCK(periph_ref_lock);
/* Macros to assist peripheral gate clock */
#define read_enb(gate) \
readl_relaxed(gate->clk_base + (gate->regs->enb_reg))
#define write_enb_set(val, gate) \
writel_relaxed(val, gate->clk_base + (gate->regs->enb_set_reg))
#define write_enb_clr(val, gate) \
writel_relaxed(val, gate->clk_base + (gate->regs->enb_clr_reg))
#define read_rst(gate) \
readl_relaxed(gate->clk_base + (gate->regs->rst_reg))
#define write_rst_clr(val, gate) \
writel_relaxed(val, gate->clk_base + (gate->regs->rst_clr_reg))
#define periph_clk_to_bit(gate) (1 << (gate->clk_num % 32))
#define LVL2_CLK_GATE_OVRE 0x554
/* Peripheral gate clock ops */
static int clk_periph_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
int state = 1;
if (!(read_enb(gate) & periph_clk_to_bit(gate)))
state = 0;
if (!(gate->flags & TEGRA_PERIPH_NO_RESET))
if (read_rst(gate) & periph_clk_to_bit(gate))
state = 0;
return state;
}
static void clk_periph_enable_locked(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
write_enb_set(periph_clk_to_bit(gate), gate);
udelay(2);
if (gate->flags & TEGRA_PERIPH_WAR_1005168) {
writel_relaxed(0, gate->clk_base + LVL2_CLK_GATE_OVRE);
writel_relaxed(BIT(22), gate->clk_base + LVL2_CLK_GATE_OVRE);
udelay(1);
writel_relaxed(0, gate->clk_base + LVL2_CLK_GATE_OVRE);
}
}
static void clk_periph_disable_locked(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
/*
* If peripheral is in the APB bus then read the APB bus to
* flush the write operation in apb bus. This will avoid the
* peripheral access after disabling clock
*/
if (gate->flags & TEGRA_PERIPH_ON_APB)
tegra_read_chipid();
write_enb_clr(periph_clk_to_bit(gate), gate);
}
static int clk_periph_enable(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
unsigned long flags = 0;
spin_lock_irqsave(&periph_ref_lock, flags);
if (!gate->enable_refcnt[gate->clk_num]++)
clk_periph_enable_locked(hw);
spin_unlock_irqrestore(&periph_ref_lock, flags);
return 0;
}
static void clk_periph_disable(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
unsigned long flags = 0;
spin_lock_irqsave(&periph_ref_lock, flags);
WARN_ON(!gate->enable_refcnt[gate->clk_num]);
if (--gate->enable_refcnt[gate->clk_num] == 0)
clk_periph_disable_locked(hw);
spin_unlock_irqrestore(&periph_ref_lock, flags);
}
static void clk_periph_disable_unused(struct clk_hw *hw)
{
struct tegra_clk_periph_gate *gate = to_clk_periph_gate(hw);
unsigned long flags = 0;
spin_lock_irqsave(&periph_ref_lock, flags);
/*
* Some clocks are duplicated and some of them are marked as critical,
* like fuse and fuse_burn for example, thus the enable_refcnt will
* be non-zero here if the "unused" duplicate is disabled by CCF.
*/
if (!gate->enable_refcnt[gate->clk_num])
clk_periph_disable_locked(hw);
spin_unlock_irqrestore(&periph_ref_lock, flags);
}
const struct clk_ops tegra_clk_periph_gate_ops = {
.is_enabled = clk_periph_is_enabled,
.enable = clk_periph_enable,
.disable = clk_periph_disable,
.disable_unused = clk_periph_disable_unused,
};
struct clk *tegra_clk_register_periph_gate(const char *name,
const char *parent_name, u8 gate_flags, void __iomem *clk_base,
unsigned long flags, int clk_num, int *enable_refcnt)
{
struct tegra_clk_periph_gate *gate;
struct clk *clk;
struct clk_init_data init;
const struct tegra_clk_periph_regs *pregs;
pregs = get_reg_bank(clk_num);
if (!pregs)
return ERR_PTR(-EINVAL);
gate = kzalloc_obj(*gate);
if (!gate) {
pr_err("%s: could not allocate periph gate clk\n", __func__);
return ERR_PTR(-ENOMEM);
}
init.name = name;
init.flags = flags;
init.parent_names = parent_name ? &parent_name : NULL;
init.num_parents = parent_name ? 1 : 0;
init.ops = &tegra_clk_periph_gate_ops;
gate->magic = TEGRA_CLK_PERIPH_GATE_MAGIC;
gate->clk_base = clk_base;
gate->clk_num = clk_num;
gate->flags = gate_flags;
gate->enable_refcnt = enable_refcnt;
gate->regs = pregs;
/* Data in .init is copied by clk_register(), so stack variable OK */
gate->hw.init = &init;
clk = clk_register(NULL, &gate->hw);
if (IS_ERR(clk))
kfree(gate);
return clk;
}
+235
View File
@@ -0,0 +1,235 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/export.h>
#include <linux/slab.h>
#include <linux/err.h>
#include "clk.h"
static u8 clk_periph_get_parent(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *mux_ops = periph->mux_ops;
struct clk_hw *mux_hw = &periph->mux.hw;
__clk_hw_set_clk(mux_hw, hw);
return mux_ops->get_parent(mux_hw);
}
static int clk_periph_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *mux_ops = periph->mux_ops;
struct clk_hw *mux_hw = &periph->mux.hw;
__clk_hw_set_clk(mux_hw, hw);
return mux_ops->set_parent(mux_hw, index);
}
static unsigned long clk_periph_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *div_ops = periph->div_ops;
struct clk_hw *div_hw = &periph->divider.hw;
__clk_hw_set_clk(div_hw, hw);
return div_ops->recalc_rate(div_hw, parent_rate);
}
static int clk_periph_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *div_ops = periph->div_ops;
struct clk_hw *div_hw = &periph->divider.hw;
__clk_hw_set_clk(div_hw, hw);
return div_ops->determine_rate(div_hw, req);
}
static int clk_periph_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *div_ops = periph->div_ops;
struct clk_hw *div_hw = &periph->divider.hw;
__clk_hw_set_clk(div_hw, hw);
return div_ops->set_rate(div_hw, rate, parent_rate);
}
static int clk_periph_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *gate_ops = periph->gate_ops;
struct clk_hw *gate_hw = &periph->gate.hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->is_enabled(gate_hw);
}
static int clk_periph_enable(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *gate_ops = periph->gate_ops;
struct clk_hw *gate_hw = &periph->gate.hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->enable(gate_hw);
}
static void clk_periph_disable(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *gate_ops = periph->gate_ops;
struct clk_hw *gate_hw = &periph->gate.hw;
gate_ops->disable(gate_hw);
}
static void clk_periph_disable_unused(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *gate_ops = periph->gate_ops;
struct clk_hw *gate_hw = &periph->gate.hw;
gate_ops->disable_unused(gate_hw);
}
static void clk_periph_restore_context(struct clk_hw *hw)
{
struct tegra_clk_periph *periph = to_clk_periph(hw);
const struct clk_ops *div_ops = periph->div_ops;
struct clk_hw *div_hw = &periph->divider.hw;
int parent_id;
parent_id = clk_hw_get_parent_index(hw);
if (WARN_ON(parent_id < 0))
return;
if (!(periph->gate.flags & TEGRA_PERIPH_NO_DIV))
div_ops->restore_context(div_hw);
clk_periph_set_parent(hw, parent_id);
}
static const struct clk_ops tegra_clk_periph_ops = {
.get_parent = clk_periph_get_parent,
.set_parent = clk_periph_set_parent,
.recalc_rate = clk_periph_recalc_rate,
.determine_rate = clk_periph_determine_rate,
.set_rate = clk_periph_set_rate,
.is_enabled = clk_periph_is_enabled,
.enable = clk_periph_enable,
.disable = clk_periph_disable,
.disable_unused = clk_periph_disable_unused,
.restore_context = clk_periph_restore_context,
};
static const struct clk_ops tegra_clk_periph_nodiv_ops = {
.determine_rate = clk_hw_determine_rate_no_reparent,
.get_parent = clk_periph_get_parent,
.set_parent = clk_periph_set_parent,
.is_enabled = clk_periph_is_enabled,
.enable = clk_periph_enable,
.disable = clk_periph_disable,
.disable_unused = clk_periph_disable_unused,
.restore_context = clk_periph_restore_context,
};
static const struct clk_ops tegra_clk_periph_no_gate_ops = {
.get_parent = clk_periph_get_parent,
.set_parent = clk_periph_set_parent,
.recalc_rate = clk_periph_recalc_rate,
.determine_rate = clk_periph_determine_rate,
.set_rate = clk_periph_set_rate,
.restore_context = clk_periph_restore_context,
};
static struct clk *_tegra_clk_register_periph(const char *name,
const char * const *parent_names, int num_parents,
struct tegra_clk_periph *periph,
void __iomem *clk_base, u32 offset,
unsigned long flags)
{
struct clk *clk;
struct clk_init_data init;
const struct tegra_clk_periph_regs *bank;
bool div = !(periph->gate.flags & TEGRA_PERIPH_NO_DIV);
if (periph->gate.flags & TEGRA_PERIPH_NO_DIV) {
flags |= CLK_SET_RATE_PARENT;
init.ops = &tegra_clk_periph_nodiv_ops;
} else if (periph->gate.flags & TEGRA_PERIPH_NO_GATE)
init.ops = &tegra_clk_periph_no_gate_ops;
else
init.ops = &tegra_clk_periph_ops;
init.name = name;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
bank = get_reg_bank(periph->gate.clk_num);
if (!bank)
return ERR_PTR(-EINVAL);
/* Data in .init is copied by clk_register(), so stack variable OK */
periph->hw.init = &init;
periph->magic = TEGRA_CLK_PERIPH_MAGIC;
periph->mux.reg = clk_base + offset;
periph->divider.reg = div ? (clk_base + offset) : NULL;
periph->gate.clk_base = clk_base;
periph->gate.regs = bank;
periph->gate.enable_refcnt = periph_clk_enb_refcnt;
clk = clk_register(NULL, &periph->hw);
if (IS_ERR(clk))
return clk;
periph->mux.hw.clk = clk;
periph->divider.hw.clk = div ? clk : NULL;
periph->gate.hw.clk = clk;
return clk;
}
struct clk *tegra_clk_register_periph(const char *name,
const char * const *parent_names, int num_parents,
struct tegra_clk_periph *periph, void __iomem *clk_base,
u32 offset, unsigned long flags)
{
return _tegra_clk_register_periph(name, parent_names, num_parents,
periph, clk_base, offset, flags);
}
struct clk *tegra_clk_register_periph_nodiv(const char *name,
const char * const *parent_names, int num_parents,
struct tegra_clk_periph *periph, void __iomem *clk_base,
u32 offset)
{
periph->gate.flags |= TEGRA_PERIPH_NO_DIV;
return _tegra_clk_register_periph(name, parent_names, num_parents,
periph, clk_base, offset, CLK_SET_RATE_PARENT);
}
struct clk *tegra_clk_register_periph_data(void __iomem *clk_base,
struct tegra_periph_init_data *init)
{
return _tegra_clk_register_periph(init->name, init->p.parent_names,
init->num_parents, &init->periph,
clk_base, init->offset, init->flags);
}
+120
View File
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/clk-provider.h>
#include "clk.h"
#define pll_out_enb(p) (BIT(p->enb_bit_idx))
#define pll_out_rst(p) (BIT(p->rst_bit_idx))
static int clk_pll_out_is_enabled(struct clk_hw *hw)
{
struct tegra_clk_pll_out *pll_out = to_clk_pll_out(hw);
u32 val = readl_relaxed(pll_out->reg);
int state;
state = (val & pll_out_enb(pll_out)) ? 1 : 0;
if (!(val & (pll_out_rst(pll_out))))
state = 0;
return state;
}
static int clk_pll_out_enable(struct clk_hw *hw)
{
struct tegra_clk_pll_out *pll_out = to_clk_pll_out(hw);
unsigned long flags = 0;
u32 val;
if (pll_out->lock)
spin_lock_irqsave(pll_out->lock, flags);
val = readl_relaxed(pll_out->reg);
val |= (pll_out_enb(pll_out) | pll_out_rst(pll_out));
writel_relaxed(val, pll_out->reg);
udelay(2);
if (pll_out->lock)
spin_unlock_irqrestore(pll_out->lock, flags);
return 0;
}
static void clk_pll_out_disable(struct clk_hw *hw)
{
struct tegra_clk_pll_out *pll_out = to_clk_pll_out(hw);
unsigned long flags = 0;
u32 val;
if (pll_out->lock)
spin_lock_irqsave(pll_out->lock, flags);
val = readl_relaxed(pll_out->reg);
val &= ~(pll_out_enb(pll_out) | pll_out_rst(pll_out));
writel_relaxed(val, pll_out->reg);
udelay(2);
if (pll_out->lock)
spin_unlock_irqrestore(pll_out->lock, flags);
}
static void tegra_clk_pll_out_restore_context(struct clk_hw *hw)
{
if (!__clk_get_enable_count(hw->clk))
clk_pll_out_disable(hw);
else
clk_pll_out_enable(hw);
}
const struct clk_ops tegra_clk_pll_out_ops = {
.is_enabled = clk_pll_out_is_enabled,
.enable = clk_pll_out_enable,
.disable = clk_pll_out_disable,
.restore_context = tegra_clk_pll_out_restore_context,
};
struct clk *tegra_clk_register_pll_out(const char *name,
const char *parent_name, void __iomem *reg, u8 enb_bit_idx,
u8 rst_bit_idx, unsigned long flags, u8 pll_out_flags,
spinlock_t *lock)
{
struct tegra_clk_pll_out *pll_out;
struct clk *clk;
struct clk_init_data init;
pll_out = kzalloc_obj(*pll_out);
if (!pll_out)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &tegra_clk_pll_out_ops;
init.parent_names = (parent_name ? &parent_name : NULL);
init.num_parents = (parent_name ? 1 : 0);
init.flags = flags;
pll_out->reg = reg;
pll_out->enb_bit_idx = enb_bit_idx;
pll_out->rst_bit_idx = rst_bit_idx;
pll_out->flags = pll_out_flags;
pll_out->lock = lock;
/* Data in .init is copied by clk_register(), so stack variable OK */
pll_out->hw.init = &init;
clk = clk_register(NULL, &pll_out->hw);
if (IS_ERR(clk))
kfree(pll_out);
return clk;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,278 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018 NVIDIA CORPORATION. All rights reserved.
*
* based on clk-mux.c
*
* Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
* Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org>
* Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
*
*/
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/types.h>
#include "clk.h"
#define DIV_MASK GENMASK(7, 0)
#define MUX_SHIFT 29
#define MUX_MASK GENMASK(MUX_SHIFT + 2, MUX_SHIFT)
#define SDMMC_MUL 2
#define get_max_div(d) DIV_MASK
#define get_div_field(val) ((val) & DIV_MASK)
#define get_mux_field(val) (((val) & MUX_MASK) >> MUX_SHIFT)
static const char * const mux_sdmmc_parents[] = {
"pll_p", "pll_c4_out2", "pll_c4_out0", "pll_c4_out1", "clk_m"
};
static const u8 mux_lj_idx[] = {
[0] = 0, [1] = 1, [2] = 2, [3] = 5, [4] = 6
};
static const u8 mux_non_lj_idx[] = {
[0] = 0, [1] = 3, [2] = 7, [3] = 4, [4] = 6
};
static u8 clk_sdmmc_mux_get_parent(struct clk_hw *hw)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
int num_parents, i;
u32 src, val;
const u8 *mux_idx;
num_parents = clk_hw_get_num_parents(hw);
val = readl_relaxed(sdmmc_mux->reg);
src = get_mux_field(val);
if (get_div_field(val))
mux_idx = mux_non_lj_idx;
else
mux_idx = mux_lj_idx;
for (i = 0; i < num_parents; i++) {
if (mux_idx[i] == src)
return i;
}
WARN(1, "Unknown parent selector %d\n", src);
return 0;
}
static int clk_sdmmc_mux_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
u32 val;
val = readl_relaxed(sdmmc_mux->reg);
if (get_div_field(val))
index = mux_non_lj_idx[index];
else
index = mux_lj_idx[index];
val &= ~MUX_MASK;
val |= index << MUX_SHIFT;
writel(val, sdmmc_mux->reg);
return 0;
}
static unsigned long clk_sdmmc_mux_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
u32 val;
int div;
u64 rate = parent_rate;
val = readl_relaxed(sdmmc_mux->reg);
div = get_div_field(val);
div += SDMMC_MUL;
rate *= SDMMC_MUL;
rate += div - 1;
do_div(rate, div);
return rate;
}
static int clk_sdmmc_mux_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
int div;
unsigned long output_rate = req->best_parent_rate;
req->rate = max(req->rate, req->min_rate);
req->rate = min(req->rate, req->max_rate);
if (!req->rate)
return output_rate;
div = div_frac_get(req->rate, output_rate, 8, 1, sdmmc_mux->div_flags);
if (div < 0)
div = 0;
if (sdmmc_mux->div_flags & TEGRA_DIVIDER_ROUND_UP)
req->rate = DIV_ROUND_UP(output_rate * SDMMC_MUL,
div + SDMMC_MUL);
else
req->rate = output_rate * SDMMC_MUL / (div + SDMMC_MUL);
return 0;
}
static int clk_sdmmc_mux_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
int div;
unsigned long flags = 0;
u32 val;
u8 src;
div = div_frac_get(rate, parent_rate, 8, 1, sdmmc_mux->div_flags);
if (div < 0)
return div;
if (sdmmc_mux->lock)
spin_lock_irqsave(sdmmc_mux->lock, flags);
src = clk_sdmmc_mux_get_parent(hw);
if (div)
src = mux_non_lj_idx[src];
else
src = mux_lj_idx[src];
val = src << MUX_SHIFT;
val |= div;
writel(val, sdmmc_mux->reg);
fence_udelay(2, sdmmc_mux->reg);
if (sdmmc_mux->lock)
spin_unlock_irqrestore(sdmmc_mux->lock, flags);
return 0;
}
static int clk_sdmmc_mux_is_enabled(struct clk_hw *hw)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
const struct clk_ops *gate_ops = sdmmc_mux->gate_ops;
struct clk_hw *gate_hw = &sdmmc_mux->gate.hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->is_enabled(gate_hw);
}
static int clk_sdmmc_mux_enable(struct clk_hw *hw)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
const struct clk_ops *gate_ops = sdmmc_mux->gate_ops;
struct clk_hw *gate_hw = &sdmmc_mux->gate.hw;
__clk_hw_set_clk(gate_hw, hw);
return gate_ops->enable(gate_hw);
}
static void clk_sdmmc_mux_disable(struct clk_hw *hw)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
const struct clk_ops *gate_ops = sdmmc_mux->gate_ops;
struct clk_hw *gate_hw = &sdmmc_mux->gate.hw;
gate_ops->disable(gate_hw);
}
static void clk_sdmmc_mux_disable_unused(struct clk_hw *hw)
{
struct tegra_sdmmc_mux *sdmmc_mux = to_clk_sdmmc_mux(hw);
const struct clk_ops *gate_ops = sdmmc_mux->gate_ops;
struct clk_hw *gate_hw = &sdmmc_mux->gate.hw;
gate_ops->disable_unused(gate_hw);
}
static void clk_sdmmc_mux_restore_context(struct clk_hw *hw)
{
struct clk_hw *parent = clk_hw_get_parent(hw);
unsigned long parent_rate = clk_hw_get_rate(parent);
unsigned long rate = clk_hw_get_rate(hw);
int parent_id;
parent_id = clk_hw_get_parent_index(hw);
if (WARN_ON(parent_id < 0))
return;
clk_sdmmc_mux_set_parent(hw, parent_id);
clk_sdmmc_mux_set_rate(hw, rate, parent_rate);
}
static const struct clk_ops tegra_clk_sdmmc_mux_ops = {
.get_parent = clk_sdmmc_mux_get_parent,
.set_parent = clk_sdmmc_mux_set_parent,
.determine_rate = clk_sdmmc_mux_determine_rate,
.recalc_rate = clk_sdmmc_mux_recalc_rate,
.set_rate = clk_sdmmc_mux_set_rate,
.is_enabled = clk_sdmmc_mux_is_enabled,
.enable = clk_sdmmc_mux_enable,
.disable = clk_sdmmc_mux_disable,
.disable_unused = clk_sdmmc_mux_disable_unused,
.restore_context = clk_sdmmc_mux_restore_context,
};
struct clk *tegra_clk_register_sdmmc_mux_div(const char *name,
void __iomem *clk_base, u32 offset, u32 clk_num, u8 div_flags,
unsigned long flags, void *lock)
{
struct clk *clk;
struct clk_init_data init;
const struct tegra_clk_periph_regs *bank;
struct tegra_sdmmc_mux *sdmmc_mux;
init.ops = &tegra_clk_sdmmc_mux_ops;
init.name = name;
init.flags = flags;
init.parent_names = mux_sdmmc_parents;
init.num_parents = ARRAY_SIZE(mux_sdmmc_parents);
bank = get_reg_bank(clk_num);
if (!bank)
return ERR_PTR(-EINVAL);
sdmmc_mux = kzalloc_obj(*sdmmc_mux);
if (!sdmmc_mux)
return ERR_PTR(-ENOMEM);
/* Data in .init is copied by clk_register(), so stack variable OK */
sdmmc_mux->hw.init = &init;
sdmmc_mux->reg = clk_base + offset;
sdmmc_mux->lock = lock;
sdmmc_mux->gate.clk_base = clk_base;
sdmmc_mux->gate.regs = bank;
sdmmc_mux->gate.enable_refcnt = periph_clk_enb_refcnt;
sdmmc_mux->gate.clk_num = clk_num;
sdmmc_mux->gate.flags = TEGRA_PERIPH_ON_APB;
sdmmc_mux->div_flags = div_flags;
sdmmc_mux->gate_ops = &tegra_clk_periph_gate_ops;
clk = clk_register(NULL, &sdmmc_mux->hw);
if (IS_ERR(clk)) {
kfree(sdmmc_mux);
return clk;
}
sdmmc_mux->gate.hw.clk = clk;
return clk;
}
+275
View File
@@ -0,0 +1,275 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/kernel.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/clk-provider.h>
#include "clk.h"
#define SUPER_STATE_IDLE 0
#define SUPER_STATE_RUN 1
#define SUPER_STATE_IRQ 2
#define SUPER_STATE_FIQ 3
#define SUPER_STATE_SHIFT 28
#define SUPER_STATE_MASK ((BIT(SUPER_STATE_IDLE) | BIT(SUPER_STATE_RUN) | \
BIT(SUPER_STATE_IRQ) | BIT(SUPER_STATE_FIQ)) \
<< SUPER_STATE_SHIFT)
#define SUPER_LP_DIV2_BYPASS (1 << 16)
#define super_state(s) (BIT(s) << SUPER_STATE_SHIFT)
#define super_state_to_src_shift(m, s) ((m->width * s))
#define super_state_to_src_mask(m) (((1 << m->width) - 1))
#define CCLK_SRC_PLLP_OUT0 4
#define CCLK_SRC_PLLP_OUT4 5
static u8 clk_super_get_parent(struct clk_hw *hw)
{
struct tegra_clk_super_mux *mux = to_clk_super_mux(hw);
u32 val, state;
u8 source, shift;
val = readl_relaxed(mux->reg);
state = val & SUPER_STATE_MASK;
BUG_ON((state != super_state(SUPER_STATE_RUN)) &&
(state != super_state(SUPER_STATE_IDLE)));
shift = (state == super_state(SUPER_STATE_IDLE)) ?
super_state_to_src_shift(mux, SUPER_STATE_IDLE) :
super_state_to_src_shift(mux, SUPER_STATE_RUN);
source = (val >> shift) & super_state_to_src_mask(mux);
/*
* If LP_DIV2_BYPASS is not set and PLLX is current parent then
* PLLX/2 is the input source to CCLKLP.
*/
if ((mux->flags & TEGRA_DIVIDER_2) && !(val & SUPER_LP_DIV2_BYPASS) &&
(source == mux->pllx_index))
source = mux->div2_index;
return source;
}
static int clk_super_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_clk_super_mux *mux = to_clk_super_mux(hw);
u32 val, state;
int err = 0;
u8 parent_index, shift;
unsigned long flags = 0;
if (mux->lock)
spin_lock_irqsave(mux->lock, flags);
val = readl_relaxed(mux->reg);
state = val & SUPER_STATE_MASK;
BUG_ON((state != super_state(SUPER_STATE_RUN)) &&
(state != super_state(SUPER_STATE_IDLE)));
shift = (state == super_state(SUPER_STATE_IDLE)) ?
super_state_to_src_shift(mux, SUPER_STATE_IDLE) :
super_state_to_src_shift(mux, SUPER_STATE_RUN);
/*
* For LP mode super-clock switch between PLLX direct
* and divided-by-2 outputs is allowed only when other
* than PLLX clock source is current parent.
*/
if ((mux->flags & TEGRA_DIVIDER_2) && ((index == mux->div2_index) ||
(index == mux->pllx_index))) {
parent_index = clk_super_get_parent(hw);
if ((parent_index == mux->div2_index) ||
(parent_index == mux->pllx_index)) {
err = -EINVAL;
goto out;
}
val ^= SUPER_LP_DIV2_BYPASS;
writel_relaxed(val, mux->reg);
udelay(2);
if (index == mux->div2_index)
index = mux->pllx_index;
}
/* enable PLLP branches to CPU before selecting PLLP source */
if ((mux->flags & TEGRA210_CPU_CLK) &&
(index == CCLK_SRC_PLLP_OUT0 || index == CCLK_SRC_PLLP_OUT4))
tegra_clk_set_pllp_out_cpu(true);
val &= ~((super_state_to_src_mask(mux)) << shift);
val |= (index & (super_state_to_src_mask(mux))) << shift;
writel_relaxed(val, mux->reg);
udelay(2);
/* disable PLLP branches to CPU if not used */
if ((mux->flags & TEGRA210_CPU_CLK) &&
index != CCLK_SRC_PLLP_OUT0 && index != CCLK_SRC_PLLP_OUT4)
tegra_clk_set_pllp_out_cpu(false);
out:
if (mux->lock)
spin_unlock_irqrestore(mux->lock, flags);
return err;
}
static void clk_super_mux_restore_context(struct clk_hw *hw)
{
int parent_id;
parent_id = clk_hw_get_parent_index(hw);
if (WARN_ON(parent_id < 0))
return;
clk_super_set_parent(hw, parent_id);
}
static const struct clk_ops tegra_clk_super_mux_ops = {
.determine_rate = clk_hw_determine_rate_no_reparent,
.get_parent = clk_super_get_parent,
.set_parent = clk_super_set_parent,
.restore_context = clk_super_mux_restore_context,
};
static int clk_super_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
struct clk_hw *div_hw = &super->frac_div.hw;
__clk_hw_set_clk(div_hw, hw);
return super->div_ops->determine_rate(div_hw, req);
}
static unsigned long clk_super_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
struct clk_hw *div_hw = &super->frac_div.hw;
__clk_hw_set_clk(div_hw, hw);
return super->div_ops->recalc_rate(div_hw, parent_rate);
}
static int clk_super_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
struct clk_hw *div_hw = &super->frac_div.hw;
__clk_hw_set_clk(div_hw, hw);
return super->div_ops->set_rate(div_hw, rate, parent_rate);
}
static void clk_super_restore_context(struct clk_hw *hw)
{
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
struct clk_hw *div_hw = &super->frac_div.hw;
int parent_id;
parent_id = clk_hw_get_parent_index(hw);
if (WARN_ON(parent_id < 0))
return;
super->div_ops->restore_context(div_hw);
clk_super_set_parent(hw, parent_id);
}
const struct clk_ops tegra_clk_super_ops = {
.get_parent = clk_super_get_parent,
.set_parent = clk_super_set_parent,
.set_rate = clk_super_set_rate,
.determine_rate = clk_super_determine_rate,
.recalc_rate = clk_super_recalc_rate,
.restore_context = clk_super_restore_context,
};
struct clk *tegra_clk_register_super_mux(const char *name,
const char **parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
u8 width, u8 pllx_index, u8 div2_index, spinlock_t *lock)
{
struct tegra_clk_super_mux *super;
struct clk *clk;
struct clk_init_data init;
super = kzalloc_obj(*super);
if (!super)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &tegra_clk_super_mux_ops;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
super->reg = reg;
super->pllx_index = pllx_index;
super->div2_index = div2_index;
super->lock = lock;
super->width = width;
super->flags = clk_super_flags;
/* Data in .init is copied by clk_register(), so stack variable OK */
super->hw.init = &init;
clk = tegra_clk_dev_register(&super->hw);
if (IS_ERR(clk))
kfree(super);
return clk;
}
struct clk *tegra_clk_register_super_clk(const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
spinlock_t *lock)
{
struct tegra_clk_super_mux *super;
struct clk *clk;
struct clk_init_data init;
super = kzalloc_obj(*super);
if (!super)
return ERR_PTR(-ENOMEM);
init.name = name;
init.ops = &tegra_clk_super_ops;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
super->reg = reg;
super->lock = lock;
super->width = 4;
super->flags = clk_super_flags;
super->frac_div.reg = reg + 4;
super->frac_div.shift = 16;
super->frac_div.width = 8;
super->frac_div.frac_width = 1;
super->frac_div.lock = lock;
super->div_ops = &tegra_clk_frac_div_ops;
/* Data in .init is copied by clk_register(), so stack variable OK */
super->hw.init = &init;
clk = clk_register(NULL, &super->hw);
if (IS_ERR(clk))
kfree(super);
return clk;
}
@@ -0,0 +1,248 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include "clk.h"
#include "clk-id.h"
#define AUDIO_SYNC_CLK_I2S0 0x4a0
#define AUDIO_SYNC_CLK_I2S1 0x4a4
#define AUDIO_SYNC_CLK_I2S2 0x4a8
#define AUDIO_SYNC_CLK_I2S3 0x4ac
#define AUDIO_SYNC_CLK_I2S4 0x4b0
#define AUDIO_SYNC_CLK_SPDIF 0x4b4
#define AUDIO_SYNC_CLK_DMIC1 0x560
#define AUDIO_SYNC_CLK_DMIC2 0x564
#define AUDIO_SYNC_CLK_DMIC3 0x6b8
#define AUDIO_SYNC_DOUBLER 0x49c
#define PLLA_OUT 0xb4
struct tegra_sync_source_initdata {
char *name;
unsigned long rate;
unsigned long max_rate;
int clk_id;
};
#define SYNC(_name) \
{\
.name = #_name,\
.clk_id = tegra_clk_ ## _name,\
}
struct tegra_audio_clk_initdata {
char *gate_name;
char *mux_name;
u32 offset;
int gate_clk_id;
int mux_clk_id;
};
#define AUDIO(_name, _offset) \
{\
.gate_name = #_name,\
.mux_name = #_name"_mux",\
.offset = _offset,\
.gate_clk_id = tegra_clk_ ## _name,\
.mux_clk_id = tegra_clk_ ## _name ## _mux,\
}
struct tegra_audio2x_clk_initdata {
char *parent;
char *gate_name;
char *name_2x;
char *div_name;
int clk_id;
int clk_num;
u8 div_offset;
};
#define AUDIO2X(_name, _num, _offset) \
{\
.parent = #_name,\
.gate_name = #_name"_2x",\
.name_2x = #_name"_doubler",\
.div_name = #_name"_div",\
.clk_id = tegra_clk_ ## _name ## _2x,\
.clk_num = _num,\
.div_offset = _offset,\
}
static DEFINE_SPINLOCK(clk_doubler_lock);
static const char * const mux_audio_sync_clk[] = { "spdif_in_sync",
"i2s0_sync", "i2s1_sync", "i2s2_sync", "i2s3_sync", "i2s4_sync",
"pll_a_out0", "vimclk_sync",
};
static const char * const mux_dmic_sync_clk[] = { "unused", "i2s0_sync",
"i2s1_sync", "i2s2_sync", "i2s3_sync", "i2s4_sync", "pll_a_out0",
"vimclk_sync",
};
static struct tegra_sync_source_initdata sync_source_clks[] __initdata = {
SYNC(spdif_in_sync),
SYNC(i2s0_sync),
SYNC(i2s1_sync),
SYNC(i2s2_sync),
SYNC(i2s3_sync),
SYNC(i2s4_sync),
SYNC(vimclk_sync),
};
static struct tegra_audio_clk_initdata audio_clks[] = {
AUDIO(audio0, AUDIO_SYNC_CLK_I2S0),
AUDIO(audio1, AUDIO_SYNC_CLK_I2S1),
AUDIO(audio2, AUDIO_SYNC_CLK_I2S2),
AUDIO(audio3, AUDIO_SYNC_CLK_I2S3),
AUDIO(audio4, AUDIO_SYNC_CLK_I2S4),
AUDIO(spdif, AUDIO_SYNC_CLK_SPDIF),
};
static struct tegra_audio_clk_initdata dmic_clks[] = {
AUDIO(dmic1_sync_clk, AUDIO_SYNC_CLK_DMIC1),
AUDIO(dmic2_sync_clk, AUDIO_SYNC_CLK_DMIC2),
AUDIO(dmic3_sync_clk, AUDIO_SYNC_CLK_DMIC3),
};
static struct tegra_audio2x_clk_initdata audio2x_clks[] = {
AUDIO2X(audio0, 113, 24),
AUDIO2X(audio1, 114, 25),
AUDIO2X(audio2, 115, 26),
AUDIO2X(audio3, 116, 27),
AUDIO2X(audio4, 117, 28),
AUDIO2X(spdif, 118, 29),
};
static void __init tegra_audio_sync_clk_init(void __iomem *clk_base,
struct tegra_clk *tegra_clks,
struct tegra_audio_clk_initdata *sync,
int num_sync_clks,
const char * const *mux_names,
int num_mux_inputs)
{
struct clk *clk;
struct clk **dt_clk;
struct tegra_audio_clk_initdata *data;
int i;
for (i = 0, data = sync; i < num_sync_clks; i++, data++) {
dt_clk = tegra_lookup_dt_id(data->mux_clk_id, tegra_clks);
if (!dt_clk)
continue;
clk = clk_register_mux(NULL, data->mux_name, mux_names,
num_mux_inputs,
CLK_SET_RATE_NO_REPARENT,
clk_base + data->offset, 0, 3, 0,
NULL);
*dt_clk = clk;
dt_clk = tegra_lookup_dt_id(data->gate_clk_id, tegra_clks);
if (!dt_clk)
continue;
clk = clk_register_gate(NULL, data->gate_name, data->mux_name,
0, clk_base + data->offset, 4,
CLK_GATE_SET_TO_DISABLE, NULL);
*dt_clk = clk;
}
}
void __init tegra_audio_clk_init(void __iomem *clk_base,
void __iomem *pmc_base, struct tegra_clk *tegra_clks,
struct tegra_audio_clk_info *audio_info,
unsigned int num_plls, unsigned long sync_max_rate)
{
struct clk *clk;
struct clk **dt_clk;
int i;
if (!audio_info || num_plls < 1) {
pr_err("No audio data passed to tegra_audio_clk_init\n");
WARN_ON(1);
return;
}
for (i = 0; i < num_plls; i++) {
struct tegra_audio_clk_info *info = &audio_info[i];
dt_clk = tegra_lookup_dt_id(info->clk_id, tegra_clks);
if (dt_clk) {
clk = tegra_clk_register_pll(info->name, info->parent,
clk_base, pmc_base, 0, info->pll_params,
NULL);
*dt_clk = clk;
}
}
/* PLLA_OUT0 */
dt_clk = tegra_lookup_dt_id(tegra_clk_pll_a_out0, tegra_clks);
if (dt_clk) {
clk = tegra_clk_register_divider("pll_a_out0_div", "pll_a",
clk_base + PLLA_OUT, 0, TEGRA_DIVIDER_ROUND_UP,
8, 8, 1, NULL);
clk = tegra_clk_register_pll_out("pll_a_out0", "pll_a_out0_div",
clk_base + PLLA_OUT, 1, 0, CLK_IGNORE_UNUSED |
CLK_SET_RATE_PARENT, 0, NULL);
*dt_clk = clk;
}
for (i = 0; i < ARRAY_SIZE(sync_source_clks); i++) {
struct tegra_sync_source_initdata *data;
data = &sync_source_clks[i];
dt_clk = tegra_lookup_dt_id(data->clk_id, tegra_clks);
if (!dt_clk)
continue;
clk = tegra_clk_register_sync_source(data->name, sync_max_rate);
*dt_clk = clk;
}
tegra_audio_sync_clk_init(clk_base, tegra_clks, audio_clks,
ARRAY_SIZE(audio_clks), mux_audio_sync_clk,
ARRAY_SIZE(mux_audio_sync_clk));
/* make sure the DMIC sync clocks have a valid parent */
for (i = 0; i < ARRAY_SIZE(dmic_clks); i++)
writel_relaxed(1, clk_base + dmic_clks[i].offset);
tegra_audio_sync_clk_init(clk_base, tegra_clks, dmic_clks,
ARRAY_SIZE(dmic_clks), mux_dmic_sync_clk,
ARRAY_SIZE(mux_dmic_sync_clk));
for (i = 0; i < ARRAY_SIZE(audio2x_clks); i++) {
struct tegra_audio2x_clk_initdata *data;
data = &audio2x_clks[i];
dt_clk = tegra_lookup_dt_id(data->clk_id, tegra_clks);
if (!dt_clk)
continue;
clk = clk_register_fixed_factor(NULL, data->name_2x,
data->parent, CLK_SET_RATE_PARENT, 2, 1);
clk = tegra_clk_register_divider(data->div_name,
data->name_2x, clk_base + AUDIO_SYNC_DOUBLER,
0, 0, data->div_offset, 1, 0,
&clk_doubler_lock);
clk = tegra_clk_register_periph_gate(data->gate_name,
data->div_name, TEGRA_PERIPH_NO_RESET,
clk_base, CLK_SET_RATE_PARENT, data->clk_num,
periph_clk_enb_refcnt);
*dt_clk = clk;
}
}
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include "clk.h"
#include "clk-id.h"
#define OSC_CTRL 0x50
#define OSC_CTRL_OSC_FREQ_SHIFT 28
#define OSC_CTRL_PLL_REF_DIV_SHIFT 26
#define OSC_CTRL_MASK (0x3f2 | \
(0xf << OSC_CTRL_OSC_FREQ_SHIFT))
static u32 osc_ctrl_ctx;
int __init tegra_osc_clk_init(void __iomem *clk_base, struct tegra_clk *clks,
unsigned long *input_freqs, unsigned int num,
unsigned int clk_m_div, unsigned long *osc_freq,
unsigned long *pll_ref_freq)
{
struct clk *clk, *osc;
struct clk **dt_clk;
u32 val, pll_ref_div;
unsigned osc_idx;
val = readl_relaxed(clk_base + OSC_CTRL);
osc_ctrl_ctx = val & OSC_CTRL_MASK;
osc_idx = val >> OSC_CTRL_OSC_FREQ_SHIFT;
if (osc_idx < num)
*osc_freq = input_freqs[osc_idx];
else
*osc_freq = 0;
if (!*osc_freq) {
WARN_ON(1);
return -EINVAL;
}
dt_clk = tegra_lookup_dt_id(tegra_clk_osc, clks);
if (!dt_clk)
return 0;
osc = clk_register_fixed_rate(NULL, "osc", NULL, 0, *osc_freq);
*dt_clk = osc;
/* osc_div2 */
dt_clk = tegra_lookup_dt_id(tegra_clk_osc_div2, clks);
if (dt_clk) {
clk = clk_register_fixed_factor(NULL, "osc_div2", "osc",
0, 1, 2);
*dt_clk = clk;
}
/* osc_div4 */
dt_clk = tegra_lookup_dt_id(tegra_clk_osc_div4, clks);
if (dt_clk) {
clk = clk_register_fixed_factor(NULL, "osc_div4", "osc",
0, 1, 4);
*dt_clk = clk;
}
dt_clk = tegra_lookup_dt_id(tegra_clk_clk_m, clks);
if (!dt_clk)
return 0;
clk = clk_register_fixed_factor(NULL, "clk_m", "osc",
0, 1, clk_m_div);
*dt_clk = clk;
/* pll_ref */
val = (val >> OSC_CTRL_PLL_REF_DIV_SHIFT) & 3;
pll_ref_div = 1 << val;
dt_clk = tegra_lookup_dt_id(tegra_clk_pll_ref, clks);
if (!dt_clk)
return 0;
clk = clk_register_fixed_factor(NULL, "pll_ref", "osc",
0, 1, pll_ref_div);
*dt_clk = clk;
if (pll_ref_freq)
*pll_ref_freq = *osc_freq / pll_ref_div;
return 0;
}
void __init tegra_fixed_clk_init(struct tegra_clk *tegra_clks)
{
struct clk *clk;
struct clk **dt_clk;
/* clk_32k */
dt_clk = tegra_lookup_dt_id(tegra_clk_clk_32k, tegra_clks);
if (dt_clk) {
clk = clk_register_fixed_rate(NULL, "clk_32k", NULL, 0, 32768);
*dt_clk = clk;
}
}
void tegra_clk_osc_resume(void __iomem *clk_base)
{
u32 val;
val = readl_relaxed(clk_base + OSC_CTRL) & ~OSC_CTRL_MASK;
val |= osc_ctrl_ctx;
writel_relaxed(val, clk_base + OSC_CTRL);
fence_udelay(2, clk_base);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Based on clk-super.c
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*
* Based on older tegra20-cpufreq driver by Colin Cross <ccross@google.com>
* Copyright (C) 2010 Google, Inc.
*
* Author: Dmitry Osipenko <digetx@gmail.com>
* Copyright (C) 2019 GRATE-DRIVER project
*/
#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include "clk.h"
#define PLLP_INDEX 4
#define PLLX_INDEX 8
#define SUPER_CDIV_ENB BIT(31)
#define TSENSOR_SLOWDOWN BIT(23)
static struct tegra_clk_super_mux *cclk_super;
static bool cclk_on_pllx;
static u8 cclk_super_get_parent(struct clk_hw *hw)
{
return tegra_clk_super_ops.get_parent(hw);
}
static int cclk_super_set_parent(struct clk_hw *hw, u8 index)
{
return tegra_clk_super_ops.set_parent(hw, index);
}
static int cclk_super_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
return tegra_clk_super_ops.set_rate(hw, rate, parent_rate);
}
static unsigned long cclk_super_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
u32 val = readl_relaxed(super->reg);
unsigned int div2;
/* check whether thermal throttling is active */
if (val & TSENSOR_SLOWDOWN)
div2 = 1;
else
div2 = 0;
if (cclk_super_get_parent(hw) == PLLX_INDEX)
return parent_rate >> div2;
return tegra_clk_super_ops.recalc_rate(hw, parent_rate) >> div2;
}
static int cclk_super_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct clk_hw *pllp_hw = clk_hw_get_parent_by_index(hw, PLLP_INDEX);
struct clk_hw *pllx_hw = clk_hw_get_parent_by_index(hw, PLLX_INDEX);
struct tegra_clk_super_mux *super = to_clk_super_mux(hw);
unsigned long pllp_rate;
long rate = req->rate;
if (WARN_ON_ONCE(!pllp_hw || !pllx_hw))
return -EINVAL;
/*
* Switch parent to PLLP for all CCLK rates that are suitable for PLLP.
* PLLX will be disabled in this case, saving some power.
*/
pllp_rate = clk_hw_get_rate(pllp_hw);
if (rate <= pllp_rate) {
if (super->flags & TEGRA20_SUPER_CLK)
rate = pllp_rate;
else {
struct clk_rate_request parent = {
.rate = req->rate,
.best_parent_rate = pllp_rate,
};
clk_hw_get_rate_range(hw, &parent.min_rate,
&parent.max_rate);
tegra_clk_super_ops.determine_rate(hw, &parent);
pllp_rate = parent.best_parent_rate;
rate = parent.rate;
}
req->best_parent_rate = pllp_rate;
req->best_parent_hw = pllp_hw;
req->rate = rate;
} else {
rate = clk_hw_round_rate(pllx_hw, rate);
req->best_parent_rate = rate;
req->best_parent_hw = pllx_hw;
req->rate = rate;
}
if (WARN_ON_ONCE(rate <= 0))
return -EINVAL;
return 0;
}
static const struct clk_ops tegra_cclk_super_ops = {
.get_parent = cclk_super_get_parent,
.set_parent = cclk_super_set_parent,
.set_rate = cclk_super_set_rate,
.recalc_rate = cclk_super_recalc_rate,
.determine_rate = cclk_super_determine_rate,
};
static const struct clk_ops tegra_cclk_super_mux_ops = {
.get_parent = cclk_super_get_parent,
.set_parent = cclk_super_set_parent,
.determine_rate = cclk_super_determine_rate,
};
struct clk *tegra_clk_register_super_cclk(const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
spinlock_t *lock)
{
struct tegra_clk_super_mux *super;
struct clk *clk;
struct clk_init_data init;
u32 val;
if (WARN_ON(cclk_super))
return ERR_PTR(-EBUSY);
super = kzalloc_obj(*super);
if (!super)
return ERR_PTR(-ENOMEM);
init.name = name;
init.flags = flags;
init.parent_names = parent_names;
init.num_parents = num_parents;
super->reg = reg;
super->lock = lock;
super->width = 4;
super->flags = clk_super_flags;
super->hw.init = &init;
if (super->flags & TEGRA20_SUPER_CLK) {
init.ops = &tegra_cclk_super_mux_ops;
} else {
init.ops = &tegra_cclk_super_ops;
super->frac_div.reg = reg + 4;
super->frac_div.shift = 16;
super->frac_div.width = 8;
super->frac_div.frac_width = 1;
super->frac_div.lock = lock;
super->div_ops = &tegra_clk_frac_div_ops;
}
/*
* Tegra30+ has the following CPUG clock topology:
*
* +---+ +-------+ +-+ +-+ +-+
* PLLP+->+ +->+DIVIDER+->+0| +-------->+0| ------------->+0|
* | | +-------+ | | | +---+ | | | | |
* PLLC+->+MUX| | +->+ | S | | +->+ | +->+CPU
* ... | | | | | | K | | | | +-------+ | |
* PLLX+->+-->+------------>+1| +->+ I +->+1| +->+ DIV2 +->+1|
* +---+ +++ | P | +++ |SKIPPER| +++
* ^ | P | ^ +-------+ ^
* | | E | | |
* PLLX_SEL+--+ | R | | OVERHEAT+--+
* +---+ |
* |
* SUPER_CDIV_ENB+--+
*
* Tegra20 is similar, but simpler. It doesn't have the divider and
* thermal DIV2 skipper.
*
* At least for now we're not going to use clock-skipper, hence let's
* ensure that it is disabled.
*/
val = readl_relaxed(reg + 4);
val &= ~SUPER_CDIV_ENB;
writel_relaxed(val, reg + 4);
clk = clk_register(NULL, &super->hw);
if (IS_ERR(clk))
kfree(super);
else
cclk_super = super;
return clk;
}
int tegra_cclk_pre_pllx_rate_change(void)
{
if (IS_ERR_OR_NULL(cclk_super))
return -EINVAL;
if (cclk_super_get_parent(&cclk_super->hw) == PLLX_INDEX)
cclk_on_pllx = true;
else
cclk_on_pllx = false;
/*
* CPU needs to be temporarily re-parented away from PLLX if PLLX
* changes its rate. PLLP is a safe parent for CPU on all Tegra SoCs.
*/
if (cclk_on_pllx)
cclk_super_set_parent(&cclk_super->hw, PLLP_INDEX);
return 0;
}
void tegra_cclk_post_pllx_rate_change(void)
{
if (cclk_on_pllx)
cclk_super_set_parent(&cclk_super->hw, PLLX_INDEX);
}
@@ -0,0 +1,269 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/io.h>
#include <linux/clk-provider.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include "clk.h"
#include "clk-id.h"
#define PLLX_BASE 0xe0
#define PLLX_MISC 0xe4
#define PLLX_MISC2 0x514
#define PLLX_MISC3 0x518
#define CCLKG_BURST_POLICY 0x368
#define CCLKLP_BURST_POLICY 0x370
#define SCLK_BURST_POLICY 0x028
#define SYSTEM_CLK_RATE 0x030
#define SCLK_DIVIDER 0x2c
static DEFINE_SPINLOCK(sysrate_lock);
enum tegra_super_gen {
gen4 = 4,
gen5,
};
struct tegra_super_gen_info {
enum tegra_super_gen gen;
const char **sclk_parents;
const char **cclk_g_parents;
const char **cclk_lp_parents;
int num_sclk_parents;
int num_cclk_g_parents;
int num_cclk_lp_parents;
};
static const char *sclk_parents[] = { "clk_m", "pll_c_out1", "pll_p_out4",
"pll_p", "pll_p_out2", "unused",
"clk_32k", "pll_m_out1" };
static const char *cclk_g_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
"pll_p", "pll_p_out4", "unused",
"unused", "pll_x", "unused", "unused",
"unused", "unused", "unused", "unused",
"dfllCPU_out" };
static const char *cclk_lp_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
"pll_p", "pll_p_out4", "unused",
"unused", "pll_x", "pll_x_out0" };
static const struct tegra_super_gen_info tegra_super_gen_info_gen4 = {
.gen = gen4,
.sclk_parents = sclk_parents,
.cclk_g_parents = cclk_g_parents,
.cclk_lp_parents = cclk_lp_parents,
.num_sclk_parents = ARRAY_SIZE(sclk_parents),
.num_cclk_g_parents = ARRAY_SIZE(cclk_g_parents),
.num_cclk_lp_parents = ARRAY_SIZE(cclk_lp_parents),
};
static const char *sclk_parents_gen5[] = { "clk_m", "pll_c_out1", "pll_c4_out3",
"pll_p", "pll_p_out2", "pll_c4_out1",
"clk_32k", "pll_c4_out2" };
static const char *cclk_g_parents_gen5[] = { "clk_m", "unused", "clk_32k", "unused",
"pll_p", "pll_p_out4", "unused",
"unused", "pll_x", "unused", "unused",
"unused", "unused", "unused", "unused",
"dfllCPU_out" };
static const char *cclk_lp_parents_gen5[] = { "clk_m", "unused", "clk_32k", "unused",
"pll_p", "pll_p_out4", "unused",
"unused", "pll_x", "unused", "unused",
"unused", "unused", "unused", "unused",
"dfllCPU_out" };
static const struct tegra_super_gen_info tegra_super_gen_info_gen5 = {
.gen = gen5,
.sclk_parents = sclk_parents_gen5,
.cclk_g_parents = cclk_g_parents_gen5,
.cclk_lp_parents = cclk_lp_parents_gen5,
.num_sclk_parents = ARRAY_SIZE(sclk_parents_gen5),
.num_cclk_g_parents = ARRAY_SIZE(cclk_g_parents_gen5),
.num_cclk_lp_parents = ARRAY_SIZE(cclk_lp_parents_gen5),
};
static void __init tegra_sclk_init(void __iomem *clk_base,
struct tegra_clk *tegra_clks,
const struct tegra_super_gen_info *gen_info)
{
struct clk *clk;
struct clk **dt_clk;
/* SCLK_MUX */
dt_clk = tegra_lookup_dt_id(tegra_clk_sclk_mux, tegra_clks);
if (dt_clk) {
clk = tegra_clk_register_super_mux("sclk_mux",
gen_info->sclk_parents,
gen_info->num_sclk_parents,
CLK_SET_RATE_PARENT,
clk_base + SCLK_BURST_POLICY,
0, 4, 0, 0, NULL);
*dt_clk = clk;
/* SCLK */
dt_clk = tegra_lookup_dt_id(tegra_clk_sclk, tegra_clks);
if (dt_clk) {
clk = clk_register_divider(NULL, "sclk", "sclk_mux",
CLK_IS_CRITICAL,
clk_base + SCLK_DIVIDER, 0, 8,
0, &sysrate_lock);
*dt_clk = clk;
}
} else {
/* SCLK */
dt_clk = tegra_lookup_dt_id(tegra_clk_sclk, tegra_clks);
if (dt_clk) {
clk = tegra_clk_register_super_mux("sclk",
gen_info->sclk_parents,
gen_info->num_sclk_parents,
CLK_SET_RATE_PARENT |
CLK_IS_CRITICAL,
clk_base + SCLK_BURST_POLICY,
0, 4, 0, 0, NULL);
*dt_clk = clk;
}
}
/* HCLK */
dt_clk = tegra_lookup_dt_id(tegra_clk_hclk, tegra_clks);
if (dt_clk) {
clk = clk_register_divider(NULL, "hclk_div", "sclk", 0,
clk_base + SYSTEM_CLK_RATE, 4, 2, 0,
&sysrate_lock);
clk = clk_register_gate(NULL, "hclk", "hclk_div",
CLK_SET_RATE_PARENT | CLK_IS_CRITICAL,
clk_base + SYSTEM_CLK_RATE,
7, CLK_GATE_SET_TO_DISABLE, &sysrate_lock);
*dt_clk = clk;
}
/* PCLK */
dt_clk = tegra_lookup_dt_id(tegra_clk_pclk, tegra_clks);
if (!dt_clk)
return;
clk = clk_register_divider(NULL, "pclk_div", "hclk", 0,
clk_base + SYSTEM_CLK_RATE, 0, 2, 0,
&sysrate_lock);
clk = clk_register_gate(NULL, "pclk", "pclk_div", CLK_SET_RATE_PARENT |
CLK_IS_CRITICAL, clk_base + SYSTEM_CLK_RATE,
3, CLK_GATE_SET_TO_DISABLE, &sysrate_lock);
*dt_clk = clk;
}
static void __init tegra_super_clk_init(void __iomem *clk_base,
void __iomem *pmc_base,
struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *params,
const struct tegra_super_gen_info *gen_info)
{
struct clk *clk;
struct clk **dt_clk;
/* CCLKG */
dt_clk = tegra_lookup_dt_id(tegra_clk_cclk_g, tegra_clks);
if (dt_clk) {
if (gen_info->gen == gen5) {
clk = tegra_clk_register_super_mux("cclk_g",
gen_info->cclk_g_parents,
gen_info->num_cclk_g_parents,
CLK_SET_RATE_PARENT,
clk_base + CCLKG_BURST_POLICY,
TEGRA210_CPU_CLK, 4, 8, 0, NULL);
} else {
clk = tegra_clk_register_super_mux("cclk_g",
gen_info->cclk_g_parents,
gen_info->num_cclk_g_parents,
CLK_SET_RATE_PARENT,
clk_base + CCLKG_BURST_POLICY,
0, 4, 0, 0, NULL);
}
*dt_clk = clk;
}
/* CCLKLP */
dt_clk = tegra_lookup_dt_id(tegra_clk_cclk_lp, tegra_clks);
if (dt_clk) {
if (gen_info->gen == gen5) {
/*
* TEGRA210_CPU_CLK flag is not needed for cclk_lp as
* cluster switching is not currently supported on
* Tegra210 and also cpu_lp is not used.
*/
clk = tegra_clk_register_super_mux("cclk_lp",
gen_info->cclk_lp_parents,
gen_info->num_cclk_lp_parents,
CLK_SET_RATE_PARENT,
clk_base + CCLKLP_BURST_POLICY,
0, 4, 8, 0, NULL);
} else {
clk = tegra_clk_register_super_mux("cclk_lp",
gen_info->cclk_lp_parents,
gen_info->num_cclk_lp_parents,
CLK_SET_RATE_PARENT,
clk_base + CCLKLP_BURST_POLICY,
TEGRA_DIVIDER_2, 4, 8, 9, NULL);
}
*dt_clk = clk;
}
tegra_sclk_init(clk_base, tegra_clks, gen_info);
#if defined(CONFIG_ARCH_TEGRA_114_SOC) || \
defined(CONFIG_ARCH_TEGRA_124_SOC) || \
defined(CONFIG_ARCH_TEGRA_210_SOC)
/* PLLX */
dt_clk = tegra_lookup_dt_id(tegra_clk_pll_x, tegra_clks);
if (!dt_clk)
return;
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
if (gen_info->gen == gen5)
clk = tegra_clk_register_pllc_tegra210("pll_x", "pll_ref",
clk_base, pmc_base, CLK_IGNORE_UNUSED, params, NULL);
else
#endif
clk = tegra_clk_register_pllxc("pll_x", "pll_ref", clk_base,
pmc_base, CLK_IGNORE_UNUSED, params, NULL);
*dt_clk = clk;
/* PLLX_OUT0 */
dt_clk = tegra_lookup_dt_id(tegra_clk_pll_x_out0, tegra_clks);
if (!dt_clk)
return;
clk = clk_register_fixed_factor(NULL, "pll_x_out0", "pll_x",
CLK_SET_RATE_PARENT, 1, 2);
*dt_clk = clk;
#endif
}
void __init tegra_super_clk_gen4_init(void __iomem *clk_base,
void __iomem *pmc_base,
struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *params)
{
tegra_super_clk_init(clk_base, pmc_base, tegra_clks, params,
&tegra_super_gen_info_gen4);
}
void __init tegra_super_clk_gen5_init(void __iomem *clk_base,
void __iomem *pmc_base,
struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *params)
{
tegra_super_clk_init(clk_base, pmc_base, tegra_clks, params,
&tegra_super_gen_info_gen5);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,749 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Tegra124 DFLL FCPU clock source driver
*
* Copyright (C) 2012-2019 NVIDIA Corporation. All rights reserved.
*
* Aleksandr Frid <afrid@nvidia.com>
* Paul Walmsley <pwalmsley@nvidia.com>
*/
#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <soc/tegra/fuse.h>
#include "clk.h"
#include "clk-dfll.h"
#include "cvb.h"
struct dfll_fcpu_data {
const unsigned long *cpu_max_freq_table;
unsigned int cpu_max_freq_table_size;
const struct cvb_table *cpu_cvb_tables;
unsigned int cpu_cvb_tables_size;
};
/* Maximum CPU frequency, indexed by CPU speedo id */
static const unsigned long tegra114_cpu_max_freq_table[] = {
[0] = 2040000000UL,
[1] = 1810500000UL,
[2] = 1912500000UL,
[3] = 1810500000UL,
};
#define T114_CPU_CVB_TABLE \
.min_millivolts = 1000, \
.max_millivolts = 1320, \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 306000000UL, { 2190643, -141851, 3576 } }, \
{ 408000000UL, { 2250968, -144331, 3576 } }, \
{ 510000000UL, { 2313333, -146811, 3576 } }, \
{ 612000000UL, { 2377738, -149291, 3576 } }, \
{ 714000000UL, { 2444183, -151771, 3576 } }, \
{ 816000000UL, { 2512669, -154251, 3576 } }, \
{ 918000000UL, { 2583194, -156731, 3576 } }, \
{ 1020000000UL, { 2655759, -159211, 3576 } }, \
{ 1122000000UL, { 2730365, -161691, 3576 } }, \
{ 1224000000UL, { 2807010, -164171, 3576 } }, \
{ 1326000000UL, { 2885696, -166651, 3576 } }, \
{ 1428000000UL, { 2966422, -169131, 3576 } }, \
{ 1530000000UL, { 3049183, -171601, 3576 } }, \
{ 1606500000UL, { 3112179, -173451, 3576 } }, \
{ 1708500000UL, { 3198504, -175931, 3576 } }, \
{ 1810500000UL, { 3304747, -179126, 3576 } }, \
{ 1912500000UL, { 3395401, -181606, 3576 } }, \
{ 0UL, { 0, 0, 0 } }, \
}, \
.cpu_dfll_data = { \
.tune0_low = 0x00b0039d, \
.tune0_high = 0x00b0009d, \
.tune1 = 0x0000001f, \
.tune_high_min_millivolts = 1050, \
}
static const struct cvb_table tegra114_cpu_cvb_tables[] = {
{
.speedo_id = 0,
.process_id = -1,
.min_millivolts = 1000,
.max_millivolts = 1250,
.speedo_scale = 100,
.voltage_scale = 100,
.entries = {
{ 306000000UL, { 107330, -1569, 0 } },
{ 408000000UL, { 111250, -1666, 0 } },
{ 510000000UL, { 110000, -1460, 0 } },
{ 612000000UL, { 117290, -1745, 0 } },
{ 714000000UL, { 122700, -1910, 0 } },
{ 816000000UL, { 125620, -1945, 0 } },
{ 918000000UL, { 130560, -2076, 0 } },
{ 1020000000UL, { 137280, -2303, 0 } },
{ 1122000000UL, { 146440, -2660, 0 } },
{ 1224000000UL, { 152190, -2825, 0 } },
{ 1326000000UL, { 157520, -2953, 0 } },
{ 1428000000UL, { 166100, -3261, 0 } },
{ 1530000000UL, { 176410, -3647, 0 } },
{ 1632000000UL, { 189620, -4186, 0 } },
{ 1734000000UL, { 203190, -4725, 0 } },
{ 1836000000UL, { 222670, -5573, 0 } },
{ 1938000000UL, { 256210, -7165, 0 } },
{ 2040000000UL, { 250050, -6544, 0 } },
{ 0UL, { 0, 0, 0 } },
},
.cpu_dfll_data = {
.tune0_low = 0x00b0019d,
.tune0_high = 0x00b0019d,
.tune1 = 0x0000001f,
.tune_high_min_millivolts = 1000,
}
},
{
.speedo_id = 1,
.process_id = -1,
T114_CPU_CVB_TABLE
},
{
.speedo_id = 2,
.process_id = -1,
T114_CPU_CVB_TABLE
},
{
.speedo_id = 3,
.process_id = -1,
T114_CPU_CVB_TABLE
},
};
/* Maximum CPU frequency, indexed by CPU speedo id */
static const unsigned long tegra124_cpu_max_freq_table[] = {
[0] = 2014500000UL,
[1] = 2320500000UL,
[2] = 2116500000UL,
[3] = 2524500000UL,
};
static const struct cvb_table tegra124_cpu_cvb_tables[] = {
{
.speedo_id = -1,
.process_id = -1,
.min_millivolts = 900,
.max_millivolts = 1260,
.speedo_scale = 100,
.voltage_scale = 1000,
.entries = {
{ 204000000UL, { 1112619, -29295, 402 } },
{ 306000000UL, { 1150460, -30585, 402 } },
{ 408000000UL, { 1190122, -31865, 402 } },
{ 510000000UL, { 1231606, -33155, 402 } },
{ 612000000UL, { 1274912, -34435, 402 } },
{ 714000000UL, { 1320040, -35725, 402 } },
{ 816000000UL, { 1366990, -37005, 402 } },
{ 918000000UL, { 1415762, -38295, 402 } },
{ 1020000000UL, { 1466355, -39575, 402 } },
{ 1122000000UL, { 1518771, -40865, 402 } },
{ 1224000000UL, { 1573009, -42145, 402 } },
{ 1326000000UL, { 1629068, -43435, 402 } },
{ 1428000000UL, { 1686950, -44715, 402 } },
{ 1530000000UL, { 1746653, -46005, 402 } },
{ 1632000000UL, { 1808179, -47285, 402 } },
{ 1734000000UL, { 1871526, -48575, 402 } },
{ 1836000000UL, { 1936696, -49855, 402 } },
{ 1938000000UL, { 2003687, -51145, 402 } },
{ 2014500000UL, { 2054787, -52095, 402 } },
{ 2116500000UL, { 2124957, -53385, 402 } },
{ 2218500000UL, { 2196950, -54665, 402 } },
{ 2320500000UL, { 2270765, -55955, 402 } },
{ 2422500000UL, { 2346401, -57235, 402 } },
{ 2524500000UL, { 2437299, -58535, 402 } },
{ 0UL, { 0, 0, 0 } },
},
.cpu_dfll_data = {
.tune0_low = 0x005020ff,
.tune0_high = 0x005040ff,
.tune1 = 0x00000060,
}
},
};
static const unsigned long tegra210_cpu_max_freq_table[] = {
[0] = 1912500000UL,
[1] = 1912500000UL,
[2] = 2218500000UL,
[3] = 1785000000UL,
[4] = 1632000000UL,
[5] = 1912500000UL,
[6] = 2014500000UL,
[7] = 1734000000UL,
[8] = 1683000000UL,
[9] = 1555500000UL,
[10] = 1504500000UL,
};
#define TEGRA210_CPU_CVB_TABLE \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 1007452, -23865, 370 } }, \
{ 306000000UL, { 1052709, -24875, 370 } }, \
{ 408000000UL, { 1099069, -25895, 370 } }, \
{ 510000000UL, { 1146534, -26905, 370 } }, \
{ 612000000UL, { 1195102, -27915, 370 } }, \
{ 714000000UL, { 1244773, -28925, 370 } }, \
{ 816000000UL, { 1295549, -29935, 370 } }, \
{ 918000000UL, { 1347428, -30955, 370 } }, \
{ 1020000000UL, { 1400411, -31965, 370 } }, \
{ 1122000000UL, { 1454497, -32975, 370 } }, \
{ 1224000000UL, { 1509687, -33985, 370 } }, \
{ 1326000000UL, { 1565981, -35005, 370 } }, \
{ 1428000000UL, { 1623379, -36015, 370 } }, \
{ 1530000000UL, { 1681880, -37025, 370 } }, \
{ 1632000000UL, { 1741485, -38035, 370 } }, \
{ 1734000000UL, { 1802194, -39055, 370 } }, \
{ 1836000000UL, { 1864006, -40065, 370 } }, \
{ 1912500000UL, { 1910780, -40815, 370 } }, \
{ 2014500000UL, { 1227000, 0, 0 } }, \
{ 2218500000UL, { 1227000, 0, 0 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
#define TEGRA210_CPU_CVB_TABLE_XA \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 1250024, -39785, 565 } }, \
{ 306000000UL, { 1297556, -41145, 565 } }, \
{ 408000000UL, { 1346718, -42505, 565 } }, \
{ 510000000UL, { 1397511, -43855, 565 } }, \
{ 612000000UL, { 1449933, -45215, 565 } }, \
{ 714000000UL, { 1503986, -46575, 565 } }, \
{ 816000000UL, { 1559669, -47935, 565 } }, \
{ 918000000UL, { 1616982, -49295, 565 } }, \
{ 1020000000UL, { 1675926, -50645, 565 } }, \
{ 1122000000UL, { 1736500, -52005, 565 } }, \
{ 1224000000UL, { 1798704, -53365, 565 } }, \
{ 1326000000UL, { 1862538, -54725, 565 } }, \
{ 1428000000UL, { 1928003, -56085, 565 } }, \
{ 1530000000UL, { 1995097, -57435, 565 } }, \
{ 1606500000UL, { 2046149, -58445, 565 } }, \
{ 1632000000UL, { 2063822, -58795, 565 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
#define TEGRA210_CPU_CVB_TABLE_EUCM1 \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 734429, 0, 0 } }, \
{ 306000000UL, { 768191, 0, 0 } }, \
{ 408000000UL, { 801953, 0, 0 } }, \
{ 510000000UL, { 835715, 0, 0 } }, \
{ 612000000UL, { 869477, 0, 0 } }, \
{ 714000000UL, { 903239, 0, 0 } }, \
{ 816000000UL, { 937001, 0, 0 } }, \
{ 918000000UL, { 970763, 0, 0 } }, \
{ 1020000000UL, { 1004525, 0, 0 } }, \
{ 1122000000UL, { 1038287, 0, 0 } }, \
{ 1224000000UL, { 1072049, 0, 0 } }, \
{ 1326000000UL, { 1105811, 0, 0 } }, \
{ 1428000000UL, { 1130000, 0, 0 } }, \
{ 1555500000UL, { 1130000, 0, 0 } }, \
{ 1632000000UL, { 1170000, 0, 0 } }, \
{ 1734000000UL, { 1227500, 0, 0 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
#define TEGRA210_CPU_CVB_TABLE_EUCM2 \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 742283, 0, 0 } }, \
{ 306000000UL, { 776249, 0, 0 } }, \
{ 408000000UL, { 810215, 0, 0 } }, \
{ 510000000UL, { 844181, 0, 0 } }, \
{ 612000000UL, { 878147, 0, 0 } }, \
{ 714000000UL, { 912113, 0, 0 } }, \
{ 816000000UL, { 946079, 0, 0 } }, \
{ 918000000UL, { 980045, 0, 0 } }, \
{ 1020000000UL, { 1014011, 0, 0 } }, \
{ 1122000000UL, { 1047977, 0, 0 } }, \
{ 1224000000UL, { 1081943, 0, 0 } }, \
{ 1326000000UL, { 1090000, 0, 0 } }, \
{ 1479000000UL, { 1090000, 0, 0 } }, \
{ 1555500000UL, { 1162000, 0, 0 } }, \
{ 1683000000UL, { 1195000, 0, 0 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
#define TEGRA210_CPU_CVB_TABLE_EUCM2_JOINT_RAIL \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 742283, 0, 0 } }, \
{ 306000000UL, { 776249, 0, 0 } }, \
{ 408000000UL, { 810215, 0, 0 } }, \
{ 510000000UL, { 844181, 0, 0 } }, \
{ 612000000UL, { 878147, 0, 0 } }, \
{ 714000000UL, { 912113, 0, 0 } }, \
{ 816000000UL, { 946079, 0, 0 } }, \
{ 918000000UL, { 980045, 0, 0 } }, \
{ 1020000000UL, { 1014011, 0, 0 } }, \
{ 1122000000UL, { 1047977, 0, 0 } }, \
{ 1224000000UL, { 1081943, 0, 0 } }, \
{ 1326000000UL, { 1090000, 0, 0 } }, \
{ 1479000000UL, { 1090000, 0, 0 } }, \
{ 1504500000UL, { 1120000, 0, 0 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
#define TEGRA210_CPU_CVB_TABLE_ODN \
.speedo_scale = 100, \
.voltage_scale = 1000, \
.entries = { \
{ 204000000UL, { 721094, 0, 0 } }, \
{ 306000000UL, { 754040, 0, 0 } }, \
{ 408000000UL, { 786986, 0, 0 } }, \
{ 510000000UL, { 819932, 0, 0 } }, \
{ 612000000UL, { 852878, 0, 0 } }, \
{ 714000000UL, { 885824, 0, 0 } }, \
{ 816000000UL, { 918770, 0, 0 } }, \
{ 918000000UL, { 915716, 0, 0 } }, \
{ 1020000000UL, { 984662, 0, 0 } }, \
{ 1122000000UL, { 1017608, 0, 0 } }, \
{ 1224000000UL, { 1050554, 0, 0 } }, \
{ 1326000000UL, { 1083500, 0, 0 } }, \
{ 1428000000UL, { 1116446, 0, 0 } }, \
{ 1581000000UL, { 1130000, 0, 0 } }, \
{ 1683000000UL, { 1168000, 0, 0 } }, \
{ 1785000000UL, { 1227500, 0, 0 } }, \
{ 0UL, { 0, 0, 0 } }, \
}
static struct cvb_table tegra210_cpu_cvb_tables[] = {
{
.speedo_id = 10,
.process_id = 0,
.min_millivolts = 840,
.max_millivolts = 1120,
TEGRA210_CPU_CVB_TABLE_EUCM2_JOINT_RAIL,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 10,
.process_id = 1,
.min_millivolts = 840,
.max_millivolts = 1120,
TEGRA210_CPU_CVB_TABLE_EUCM2_JOINT_RAIL,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 9,
.process_id = 0,
.min_millivolts = 900,
.max_millivolts = 1162,
TEGRA210_CPU_CVB_TABLE_EUCM2,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 9,
.process_id = 1,
.min_millivolts = 900,
.max_millivolts = 1162,
TEGRA210_CPU_CVB_TABLE_EUCM2,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 8,
.process_id = 0,
.min_millivolts = 900,
.max_millivolts = 1195,
TEGRA210_CPU_CVB_TABLE_EUCM2,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 8,
.process_id = 1,
.min_millivolts = 900,
.max_millivolts = 1195,
TEGRA210_CPU_CVB_TABLE_EUCM2,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 7,
.process_id = 0,
.min_millivolts = 841,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE_EUCM1,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 7,
.process_id = 1,
.min_millivolts = 841,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE_EUCM1,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 6,
.process_id = 0,
.min_millivolts = 870,
.max_millivolts = 1150,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 6,
.process_id = 1,
.min_millivolts = 870,
.max_millivolts = 1150,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune1 = 0x25501d0,
}
},
{
.speedo_id = 5,
.process_id = 0,
.min_millivolts = 818,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 5,
.process_id = 1,
.min_millivolts = 818,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x25501d0,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 4,
.process_id = -1,
.min_millivolts = 918,
.max_millivolts = 1113,
TEGRA210_CPU_CVB_TABLE_XA,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune1 = 0x17711BD,
}
},
{
.speedo_id = 3,
.process_id = 0,
.min_millivolts = 825,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE_ODN,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 3,
.process_id = 1,
.min_millivolts = 825,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE_ODN,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x25501d0,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 2,
.process_id = 0,
.min_millivolts = 870,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune1 = 0x20091d9,
}
},
{
.speedo_id = 2,
.process_id = 1,
.min_millivolts = 870,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune1 = 0x25501d0,
}
},
{
.speedo_id = 1,
.process_id = 0,
.min_millivolts = 837,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 1,
.process_id = 1,
.min_millivolts = 837,
.max_millivolts = 1227,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x25501d0,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 0,
.process_id = 0,
.min_millivolts = 850,
.max_millivolts = 1170,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x20091d9,
.tune_high_min_millivolts = 864,
}
},
{
.speedo_id = 0,
.process_id = 1,
.min_millivolts = 850,
.max_millivolts = 1170,
TEGRA210_CPU_CVB_TABLE,
.cpu_dfll_data = {
.tune0_low = 0xffead0ff,
.tune0_high = 0xffead0ff,
.tune1 = 0x25501d0,
.tune_high_min_millivolts = 864,
}
},
};
static const struct dfll_fcpu_data tegra114_dfll_fcpu_data = {
.cpu_max_freq_table = tegra114_cpu_max_freq_table,
.cpu_max_freq_table_size = ARRAY_SIZE(tegra114_cpu_max_freq_table),
.cpu_cvb_tables = tegra114_cpu_cvb_tables,
.cpu_cvb_tables_size = ARRAY_SIZE(tegra114_cpu_cvb_tables)
};
static const struct dfll_fcpu_data tegra124_dfll_fcpu_data = {
.cpu_max_freq_table = tegra124_cpu_max_freq_table,
.cpu_max_freq_table_size = ARRAY_SIZE(tegra124_cpu_max_freq_table),
.cpu_cvb_tables = tegra124_cpu_cvb_tables,
.cpu_cvb_tables_size = ARRAY_SIZE(tegra124_cpu_cvb_tables)
};
static const struct dfll_fcpu_data tegra210_dfll_fcpu_data = {
.cpu_max_freq_table = tegra210_cpu_max_freq_table,
.cpu_max_freq_table_size = ARRAY_SIZE(tegra210_cpu_max_freq_table),
.cpu_cvb_tables = tegra210_cpu_cvb_tables,
.cpu_cvb_tables_size = ARRAY_SIZE(tegra210_cpu_cvb_tables),
};
static const struct of_device_id tegra124_dfll_fcpu_of_match[] = {
{
.compatible = "nvidia,tegra114-dfll",
.data = &tegra114_dfll_fcpu_data,
},
{
.compatible = "nvidia,tegra124-dfll",
.data = &tegra124_dfll_fcpu_data,
},
{
.compatible = "nvidia,tegra210-dfll",
.data = &tegra210_dfll_fcpu_data
},
{ },
};
static void get_alignment_from_dt(struct device *dev,
struct rail_alignment *align)
{
if (of_property_read_u32(dev->of_node,
"nvidia,pwm-voltage-step-microvolts",
&align->step_uv))
align->step_uv = 0;
if (of_property_read_u32(dev->of_node,
"nvidia,pwm-min-microvolts",
&align->offset_uv))
align->offset_uv = 0;
}
static int get_alignment_from_regulator(struct device *dev,
struct rail_alignment *align)
{
struct regulator *reg = regulator_get(dev, "vdd-cpu");
if (IS_ERR(reg))
return PTR_ERR(reg);
align->offset_uv = regulator_list_voltage(reg, 0);
align->step_uv = regulator_get_linear_step(reg);
regulator_put(reg);
return 0;
}
static int tegra124_dfll_fcpu_probe(struct platform_device *pdev)
{
int process_id, speedo_id, speedo_value, err;
struct tegra_dfll_soc_data *soc;
const struct dfll_fcpu_data *fcpu_data;
struct rail_alignment align;
fcpu_data = of_device_get_match_data(&pdev->dev);
if (!fcpu_data)
return -ENODEV;
process_id = tegra_sku_info.cpu_process_id;
speedo_id = tegra_sku_info.cpu_speedo_id;
speedo_value = tegra_sku_info.cpu_speedo_value;
if (speedo_id >= fcpu_data->cpu_max_freq_table_size) {
dev_err(&pdev->dev, "unknown max CPU freq for speedo_id=%d\n",
speedo_id);
return -ENODEV;
}
soc = devm_kzalloc(&pdev->dev, sizeof(*soc), GFP_KERNEL);
if (!soc)
return -ENOMEM;
soc->dev = get_cpu_device(0);
if (!soc->dev) {
dev_err(&pdev->dev, "no CPU0 device\n");
return -ENODEV;
}
if (of_property_read_bool(pdev->dev.of_node, "nvidia,pwm-to-pmic")) {
get_alignment_from_dt(&pdev->dev, &align);
} else {
err = get_alignment_from_regulator(&pdev->dev, &align);
if (err)
return err;
}
soc->max_freq = fcpu_data->cpu_max_freq_table[speedo_id];
soc->cvb = tegra_cvb_add_opp_table(soc->dev, fcpu_data->cpu_cvb_tables,
fcpu_data->cpu_cvb_tables_size,
&align, process_id, speedo_id,
speedo_value, soc->max_freq);
soc->alignment = align;
if (IS_ERR(soc->cvb)) {
dev_err(&pdev->dev, "couldn't add OPP table: %ld\n",
PTR_ERR(soc->cvb));
return PTR_ERR(soc->cvb);
}
err = tegra_dfll_register(pdev, soc);
if (err < 0) {
tegra_cvb_remove_opp_table(soc->dev, soc->cvb, soc->max_freq);
return err;
}
return 0;
}
static void tegra124_dfll_fcpu_remove(struct platform_device *pdev)
{
struct tegra_dfll_soc_data *soc;
/*
* Note that exiting early here is dangerous as after this function
* returns *soc is freed.
*/
soc = tegra_dfll_unregister(pdev);
if (IS_ERR(soc))
return;
tegra_cvb_remove_opp_table(soc->dev, soc->cvb, soc->max_freq);
}
static const struct dev_pm_ops tegra124_dfll_pm_ops = {
SET_RUNTIME_PM_OPS(tegra_dfll_runtime_suspend,
tegra_dfll_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(tegra_dfll_suspend, tegra_dfll_resume)
};
static struct platform_driver tegra124_dfll_fcpu_driver = {
.probe = tegra124_dfll_fcpu_probe,
.remove = tegra124_dfll_fcpu_remove,
.driver = {
.name = "tegra124-dfll",
.of_match_table = tegra124_dfll_fcpu_of_match,
.pm = &tegra124_dfll_pm_ops,
},
};
builtin_platform_driver(tegra124_dfll_fcpu_driver);
@@ -0,0 +1,576 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* drivers/clk/tegra/clk-emc.c
*
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
*
* Author:
* Mikko Perttunen <mperttunen@nvidia.com>
*/
#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/clkdev.h>
#include <linux/clk/tegra.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/sort.h>
#include <linux/string.h>
#include <soc/tegra/fuse.h>
#include "clk.h"
#define CLK_SOURCE_EMC 0x19c
#define CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR_SHIFT 0
#define CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR_MASK 0xff
#define CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR(x) (((x) & CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR_MASK) << \
CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR_SHIFT)
#define CLK_SOURCE_EMC_EMC_2X_CLK_SRC_SHIFT 29
#define CLK_SOURCE_EMC_EMC_2X_CLK_SRC_MASK 0x7
#define CLK_SOURCE_EMC_EMC_2X_CLK_SRC(x) (((x) & CLK_SOURCE_EMC_EMC_2X_CLK_SRC_MASK) << \
CLK_SOURCE_EMC_EMC_2X_CLK_SRC_SHIFT)
static const char * const emc_parent_clk_names[] = {
"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud",
"pll_c2", "pll_c3", "pll_c_ud"
};
/*
* List of clock sources for various parents the EMC clock can have.
* When we change the timing to a timing with a parent that has the same
* clock source as the current parent, we must first change to a backup
* timing that has a different clock source.
*/
#define EMC_SRC_PLL_M 0
#define EMC_SRC_PLL_C 1
#define EMC_SRC_PLL_P 2
#define EMC_SRC_CLK_M 3
#define EMC_SRC_PLL_C2 4
#define EMC_SRC_PLL_C3 5
static const char emc_parent_clk_sources[] = {
EMC_SRC_PLL_M, EMC_SRC_PLL_C, EMC_SRC_PLL_P, EMC_SRC_CLK_M,
EMC_SRC_PLL_M, EMC_SRC_PLL_C2, EMC_SRC_PLL_C3, EMC_SRC_PLL_C
};
struct emc_timing {
unsigned long rate, parent_rate;
u8 parent_index;
struct clk *parent;
u32 ram_code;
};
struct tegra_clk_emc {
struct clk_hw hw;
void __iomem *clk_regs;
struct clk *prev_parent;
bool changing_timing;
struct device_node *emc_node;
struct tegra_emc *emc;
int num_timings;
struct emc_timing *timings;
spinlock_t *lock;
tegra124_emc_prepare_timing_change_cb *prepare_timing_change;
tegra124_emc_complete_timing_change_cb *complete_timing_change;
};
/* Common clock framework callback implementations */
static unsigned long emc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_emc *tegra;
u32 val, div;
tegra = container_of(hw, struct tegra_clk_emc, hw);
/*
* CCF wrongly assumes that the parent won't change during set_rate,
* so get the parent rate explicitly.
*/
parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
val = readl(tegra->clk_regs + CLK_SOURCE_EMC);
div = val & CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR_MASK;
return parent_rate / (div + 2) * 2;
}
/*
* Rounds up unless no higher rate exists, in which case down. This way is
* safer since things have EMC rate floors. Also don't touch parent_rate
* since we don't want the CCF to play with our parent clocks.
*/
static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
struct tegra_clk_emc *tegra;
u8 ram_code = tegra_read_ram_code();
struct emc_timing *timing = NULL;
int i, k, t;
tegra = container_of(hw, struct tegra_clk_emc, hw);
for (k = 0; k < tegra->num_timings; k++) {
if (tegra->timings[k].ram_code == ram_code)
break;
}
for (t = k; t < tegra->num_timings; t++) {
if (tegra->timings[t].ram_code != ram_code)
break;
}
for (i = k; i < t; i++) {
timing = tegra->timings + i;
if (timing->rate < req->rate && i != t - 1)
continue;
if (timing->rate > req->max_rate) {
i = max(i, k + 1);
req->rate = tegra->timings[i - 1].rate;
return 0;
}
if (timing->rate < req->min_rate)
continue;
req->rate = timing->rate;
return 0;
}
if (timing) {
req->rate = timing->rate;
return 0;
}
req->rate = clk_hw_get_rate(hw);
return 0;
}
static u8 emc_get_parent(struct clk_hw *hw)
{
struct tegra_clk_emc *tegra;
u32 val;
tegra = container_of(hw, struct tegra_clk_emc, hw);
val = readl(tegra->clk_regs + CLK_SOURCE_EMC);
return (val >> CLK_SOURCE_EMC_EMC_2X_CLK_SRC_SHIFT)
& CLK_SOURCE_EMC_EMC_2X_CLK_SRC_MASK;
}
static struct tegra_emc *emc_ensure_emc_driver(struct tegra_clk_emc *tegra)
{
struct platform_device *pdev;
if (tegra->emc)
return tegra->emc;
if (!tegra->prepare_timing_change || !tegra->complete_timing_change)
return NULL;
if (!tegra->emc_node)
return NULL;
pdev = of_find_device_by_node(tegra->emc_node);
if (!pdev) {
pr_err("%s: could not get external memory controller\n",
__func__);
return NULL;
}
of_node_put(tegra->emc_node);
tegra->emc_node = NULL;
tegra->emc = platform_get_drvdata(pdev);
put_device(&pdev->dev);
if (!tegra->emc) {
pr_err("%s: cannot find EMC driver\n", __func__);
return NULL;
}
return tegra->emc;
}
static int emc_set_timing(struct tegra_clk_emc *tegra,
struct emc_timing *timing)
{
int err;
u8 div;
u32 car_value;
unsigned long flags = 0;
struct tegra_emc *emc = emc_ensure_emc_driver(tegra);
if (!emc)
return -ENOENT;
pr_debug("going to rate %ld prate %ld p %s\n", timing->rate,
timing->parent_rate, __clk_get_name(timing->parent));
if (emc_get_parent(&tegra->hw) == timing->parent_index &&
clk_get_rate(timing->parent) != timing->parent_rate) {
WARN_ONCE(1, "parent %s rate mismatch %lu %lu\n",
__clk_get_name(timing->parent),
clk_get_rate(timing->parent),
timing->parent_rate);
return -EINVAL;
}
tegra->changing_timing = true;
err = clk_set_rate(timing->parent, timing->parent_rate);
if (err) {
pr_err("cannot change parent %s rate to %ld: %d\n",
__clk_get_name(timing->parent), timing->parent_rate,
err);
return err;
}
err = clk_prepare_enable(timing->parent);
if (err) {
pr_err("cannot enable parent clock: %d\n", err);
return err;
}
div = timing->parent_rate / (timing->rate / 2) - 2;
err = tegra->prepare_timing_change(emc, timing->rate);
if (err) {
clk_disable_unprepare(timing->parent);
return err;
}
spin_lock_irqsave(tegra->lock, flags);
car_value = readl(tegra->clk_regs + CLK_SOURCE_EMC);
car_value &= ~CLK_SOURCE_EMC_EMC_2X_CLK_SRC(~0);
car_value |= CLK_SOURCE_EMC_EMC_2X_CLK_SRC(timing->parent_index);
car_value &= ~CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR(~0);
car_value |= CLK_SOURCE_EMC_EMC_2X_CLK_DIVISOR(div);
writel(car_value, tegra->clk_regs + CLK_SOURCE_EMC);
spin_unlock_irqrestore(tegra->lock, flags);
tegra->complete_timing_change(emc, timing->rate);
clk_hw_reparent(&tegra->hw, __clk_get_hw(timing->parent));
clk_disable_unprepare(tegra->prev_parent);
tegra->prev_parent = timing->parent;
tegra->changing_timing = false;
return 0;
}
/*
* Get backup timing to use as an intermediate step when a change between
* two timings with the same clock source has been requested. First try to
* find a timing with a higher clock rate to avoid a rate below any set rate
* floors. If that is not possible, find a lower rate.
*/
static struct emc_timing *get_backup_timing(struct tegra_clk_emc *tegra,
int timing_index)
{
int i;
u32 ram_code = tegra_read_ram_code();
struct emc_timing *timing;
for (i = timing_index+1; i < tegra->num_timings; i++) {
timing = tegra->timings + i;
if (timing->ram_code != ram_code)
break;
if (emc_parent_clk_sources[timing->parent_index] !=
emc_parent_clk_sources[
tegra->timings[timing_index].parent_index])
return timing;
}
for (i = timing_index-1; i >= 0; --i) {
timing = tegra->timings + i;
if (timing->ram_code != ram_code)
break;
if (emc_parent_clk_sources[timing->parent_index] !=
emc_parent_clk_sources[
tegra->timings[timing_index].parent_index])
return timing;
}
return NULL;
}
static int emc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_emc *tegra;
struct emc_timing *timing = NULL;
int i, err;
u32 ram_code = tegra_read_ram_code();
tegra = container_of(hw, struct tegra_clk_emc, hw);
if (clk_hw_get_rate(hw) == rate)
return 0;
/*
* When emc_set_timing changes the parent rate, CCF will propagate
* that downward to us, so ignore any set_rate calls while a rate
* change is already going on.
*/
if (tegra->changing_timing)
return 0;
for (i = 0; i < tegra->num_timings; i++) {
if (tegra->timings[i].rate == rate &&
tegra->timings[i].ram_code == ram_code) {
timing = tegra->timings + i;
break;
}
}
if (!timing) {
pr_err("cannot switch to rate %ld without emc table\n", rate);
return -EINVAL;
}
if (emc_parent_clk_sources[emc_get_parent(hw)] ==
emc_parent_clk_sources[timing->parent_index] &&
clk_get_rate(timing->parent) != timing->parent_rate) {
/*
* Parent clock source not changed but parent rate has changed,
* need to temporarily switch to another parent
*/
struct emc_timing *backup_timing;
backup_timing = get_backup_timing(tegra, i);
if (!backup_timing) {
pr_err("cannot find backup timing\n");
return -EINVAL;
}
pr_debug("using %ld as backup rate when going to %ld\n",
backup_timing->rate, rate);
err = emc_set_timing(tegra, backup_timing);
if (err) {
pr_err("cannot set backup timing: %d\n", err);
return err;
}
}
return emc_set_timing(tegra, timing);
}
/* Initialization and deinitialization */
static int load_one_timing_from_dt(struct tegra_clk_emc *tegra,
struct emc_timing *timing,
struct device_node *node)
{
int err, i;
u32 tmp;
err = of_property_read_u32(node, "clock-frequency", &tmp);
if (err) {
pr_err("timing %pOF: failed to read rate\n", node);
return err;
}
timing->rate = tmp;
err = of_property_read_u32(node, "nvidia,parent-clock-frequency", &tmp);
if (err) {
pr_err("timing %pOF: failed to read parent rate\n", node);
return err;
}
timing->parent_rate = tmp;
timing->parent = of_clk_get_by_name(node, "emc-parent");
if (IS_ERR(timing->parent)) {
pr_err("timing %pOF: failed to get parent clock\n", node);
return PTR_ERR(timing->parent);
}
timing->parent_index = 0xff;
i = match_string(emc_parent_clk_names, ARRAY_SIZE(emc_parent_clk_names),
__clk_get_name(timing->parent));
if (i < 0) {
pr_err("timing %pOF: %s is not a valid parent\n",
node, __clk_get_name(timing->parent));
clk_put(timing->parent);
return -EINVAL;
}
timing->parent_index = i;
return 0;
}
static int cmp_timings(const void *_a, const void *_b)
{
const struct emc_timing *a = _a;
const struct emc_timing *b = _b;
if (a->rate < b->rate)
return -1;
else if (a->rate == b->rate)
return 0;
else
return 1;
}
static int load_timings_from_dt(struct tegra_clk_emc *tegra,
struct device_node *node,
u32 ram_code)
{
struct emc_timing *timings_ptr;
int child_count = of_get_child_count(node);
int i = 0, err;
size_t size;
size = (tegra->num_timings + child_count) * sizeof(struct emc_timing);
tegra->timings = krealloc(tegra->timings, size, GFP_KERNEL);
if (!tegra->timings)
return -ENOMEM;
timings_ptr = tegra->timings + tegra->num_timings;
tegra->num_timings += child_count;
for_each_child_of_node_scoped(node, child) {
struct emc_timing *timing = timings_ptr + (i++);
err = load_one_timing_from_dt(tegra, timing, child);
if (err) {
kfree(tegra->timings);
return err;
}
timing->ram_code = ram_code;
}
sort(timings_ptr, child_count, sizeof(struct emc_timing),
cmp_timings, NULL);
return 0;
}
static const struct clk_ops tegra_clk_emc_ops = {
.recalc_rate = emc_recalc_rate,
.determine_rate = emc_determine_rate,
.set_rate = emc_set_rate,
.get_parent = emc_get_parent,
};
struct clk *tegra124_clk_register_emc(void __iomem *base, struct device_node *np,
spinlock_t *lock)
{
struct tegra_clk_emc *tegra;
struct clk_init_data init;
struct device_node *node;
u32 node_ram_code;
struct clk *clk;
int err;
tegra = kzalloc_objs(*tegra, 1);
if (!tegra)
return ERR_PTR(-ENOMEM);
tegra->clk_regs = base;
tegra->lock = lock;
tegra->num_timings = 0;
for_each_child_of_node(np, node) {
err = of_property_read_u32(node, "nvidia,ram-code",
&node_ram_code);
if (err)
continue;
/*
* Store timings for all ram codes as we cannot read the
* fuses until the apbmisc driver is loaded.
*/
err = load_timings_from_dt(tegra, node, node_ram_code);
if (err) {
of_node_put(node);
kfree(tegra);
return ERR_PTR(err);
}
}
if (tegra->num_timings == 0)
pr_warn("%s: no memory timings registered\n", __func__);
tegra->emc_node = of_parse_phandle(np,
"nvidia,external-memory-controller", 0);
if (!tegra->emc_node)
pr_warn("%s: couldn't find node for EMC driver\n", __func__);
init.name = "emc";
init.ops = &tegra_clk_emc_ops;
init.flags = CLK_IS_CRITICAL;
init.parent_names = emc_parent_clk_names;
init.num_parents = ARRAY_SIZE(emc_parent_clk_names);
tegra->hw.init = &init;
clk = clk_register(NULL, &tegra->hw);
if (IS_ERR(clk)) {
kfree(tegra);
return clk;
}
tegra->prev_parent = clk_hw_get_parent_by_index(
&tegra->hw, emc_get_parent(&tegra->hw))->clk;
tegra->changing_timing = false;
/* Allow debugging tools to see the EMC clock */
clk_register_clkdev(clk, "emc", "tegra-clk-debug");
return clk;
};
void tegra124_clk_set_emc_callbacks(tegra124_emc_prepare_timing_change_cb *prep_cb,
tegra124_emc_complete_timing_change_cb *complete_cb)
{
struct clk *clk = __clk_lookup("emc");
struct tegra_clk_emc *tegra;
struct clk_hw *hw;
if (clk) {
hw = __clk_get_hw(clk);
tegra = container_of(hw, struct tegra_clk_emc, hw);
tegra->prepare_timing_change = prep_cb;
tegra->complete_timing_change = complete_cb;
}
}
EXPORT_SYMBOL_GPL(tegra124_clk_set_emc_callbacks);
bool tegra124_clk_emc_driver_available(struct clk_hw *hw)
{
struct tegra_clk_emc *tegra = container_of(hw, struct tegra_clk_emc, hw);
return tegra->prepare_timing_change && tegra->complete_timing_change;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,296 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Based on drivers/clk/tegra/clk-emc.c
* Copyright (c) 2014, NVIDIA CORPORATION. All rights reserved.
*
* Author: Dmitry Osipenko <digetx@gmail.com>
* Copyright (C) 2019 GRATE-DRIVER project
*/
#define pr_fmt(fmt) "tegra-emc-clk: " fmt
#include <linux/bits.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include "clk.h"
#define CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK GENMASK(7, 0)
#define CLK_SOURCE_EMC_2X_CLK_SRC_MASK GENMASK(31, 30)
#define CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT 30
#define MC_EMC_SAME_FREQ BIT(16)
#define USE_PLLM_UD BIT(29)
#define EMC_SRC_PLL_M 0
#define EMC_SRC_PLL_C 1
#define EMC_SRC_PLL_P 2
#define EMC_SRC_CLK_M 3
static const char * const emc_parent_clk_names[] = {
"pll_m", "pll_c", "pll_p", "clk_m",
};
struct tegra_clk_emc {
struct clk_hw hw;
void __iomem *reg;
bool mc_same_freq;
bool want_low_jitter;
tegra20_clk_emc_round_cb *round_cb;
void *cb_arg;
};
static inline struct tegra_clk_emc *to_tegra_clk_emc(struct clk_hw *hw)
{
return container_of(hw, struct tegra_clk_emc, hw);
}
static unsigned long emc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
u32 val, div;
val = readl_relaxed(emc->reg);
div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
return DIV_ROUND_UP(parent_rate * 2, div + 2);
}
static u8 emc_get_parent(struct clk_hw *hw)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
return readl_relaxed(emc->reg) >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
}
static int emc_set_parent(struct clk_hw *hw, u8 index)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
u32 val, div;
val = readl_relaxed(emc->reg);
val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK;
val |= index << CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
div = val & CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
val |= USE_PLLM_UD;
else
val &= ~USE_PLLM_UD;
if (emc->mc_same_freq)
val |= MC_EMC_SAME_FREQ;
else
val &= ~MC_EMC_SAME_FREQ;
writel_relaxed(val, emc->reg);
fence_udelay(1, emc->reg);
return 0;
}
static int emc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
unsigned int index;
u32 val, div;
div = div_frac_get(rate, parent_rate, 8, 1, 0);
val = readl_relaxed(emc->reg);
val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
val |= div;
index = val >> CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
val |= USE_PLLM_UD;
else
val &= ~USE_PLLM_UD;
if (emc->mc_same_freq)
val |= MC_EMC_SAME_FREQ;
else
val &= ~MC_EMC_SAME_FREQ;
writel_relaxed(val, emc->reg);
fence_udelay(1, emc->reg);
return 0;
}
static int emc_set_rate_and_parent(struct clk_hw *hw,
unsigned long rate,
unsigned long parent_rate,
u8 index)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
u32 val, div;
div = div_frac_get(rate, parent_rate, 8, 1, 0);
val = readl_relaxed(emc->reg);
val &= ~CLK_SOURCE_EMC_2X_CLK_SRC_MASK;
val |= index << CLK_SOURCE_EMC_2X_CLK_SRC_SHIFT;
val &= ~CLK_SOURCE_EMC_2X_CLK_DIVISOR_MASK;
val |= div;
if (index == EMC_SRC_PLL_M && div == 0 && emc->want_low_jitter)
val |= USE_PLLM_UD;
else
val &= ~USE_PLLM_UD;
if (emc->mc_same_freq)
val |= MC_EMC_SAME_FREQ;
else
val &= ~MC_EMC_SAME_FREQ;
writel_relaxed(val, emc->reg);
fence_udelay(1, emc->reg);
return 0;
}
static int emc_determine_rate(struct clk_hw *hw, struct clk_rate_request *req)
{
struct tegra_clk_emc *emc = to_tegra_clk_emc(hw);
struct clk_hw *parent_hw;
unsigned long divided_rate;
unsigned long parent_rate;
unsigned int i;
long emc_rate;
int div;
emc_rate = emc->round_cb(req->rate, req->min_rate, req->max_rate,
emc->cb_arg);
if (emc_rate < 0)
return emc_rate;
for (i = 0; i < ARRAY_SIZE(emc_parent_clk_names); i++) {
parent_hw = clk_hw_get_parent_by_index(hw, i);
if (req->best_parent_hw == parent_hw)
parent_rate = req->best_parent_rate;
else
parent_rate = clk_hw_get_rate(parent_hw);
if (emc_rate > parent_rate)
continue;
div = div_frac_get(emc_rate, parent_rate, 8, 1, 0);
divided_rate = DIV_ROUND_UP(parent_rate * 2, div + 2);
if (divided_rate != emc_rate)
continue;
req->best_parent_rate = parent_rate;
req->best_parent_hw = parent_hw;
req->rate = emc_rate;
break;
}
if (i == ARRAY_SIZE(emc_parent_clk_names)) {
pr_err_once("can't find parent for rate %lu emc_rate %lu\n",
req->rate, emc_rate);
return -EINVAL;
}
return 0;
}
static const struct clk_ops tegra_clk_emc_ops = {
.recalc_rate = emc_recalc_rate,
.get_parent = emc_get_parent,
.set_parent = emc_set_parent,
.set_rate = emc_set_rate,
.set_rate_and_parent = emc_set_rate_and_parent,
.determine_rate = emc_determine_rate,
};
void tegra20_clk_set_emc_round_callback(tegra20_clk_emc_round_cb *round_cb,
void *cb_arg)
{
struct clk *clk = __clk_lookup("emc");
struct tegra_clk_emc *emc;
struct clk_hw *hw;
if (clk) {
hw = __clk_get_hw(clk);
emc = to_tegra_clk_emc(hw);
emc->round_cb = round_cb;
emc->cb_arg = cb_arg;
}
}
EXPORT_SYMBOL_GPL(tegra20_clk_set_emc_round_callback);
bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw)
{
return to_tegra_clk_emc(emc_hw)->round_cb != NULL;
}
struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter)
{
struct tegra_clk_emc *emc;
struct clk_init_data init;
struct clk *clk;
emc = kzalloc_obj(*emc);
if (!emc)
return NULL;
/*
* EMC stands for External Memory Controller.
*
* We don't want EMC clock to be disabled ever by gating its
* parent and whatnot because system is busted immediately in that
* case, hence the clock is marked as critical.
*/
init.name = "emc";
init.ops = &tegra_clk_emc_ops;
init.flags = CLK_IS_CRITICAL;
init.parent_names = emc_parent_clk_names;
init.num_parents = ARRAY_SIZE(emc_parent_clk_names);
emc->reg = ioaddr;
emc->hw.init = &init;
emc->want_low_jitter = low_jitter;
clk = clk_register(NULL, &emc->hw);
if (IS_ERR(clk)) {
kfree(emc);
return NULL;
}
return clk;
}
int tegra20_clk_prepare_emc_mc_same_freq(struct clk *emc_clk, bool same)
{
struct tegra_clk_emc *emc;
struct clk_hw *hw;
if (!emc_clk)
return -EINVAL;
hw = __clk_get_hw(emc_clk);
emc = to_tegra_clk_emc(hw);
emc->mc_same_freq = same;
return 0;
}
EXPORT_SYMBOL_GPL(tegra20_clk_prepare_emc_mc_same_freq);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,379 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2015-2020, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clk/tegra.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/slab.h>
#include "clk.h"
#define CLK_SOURCE_EMC 0x19c
#define CLK_SOURCE_EMC_2X_CLK_SRC GENMASK(31, 29)
#define CLK_SOURCE_EMC_MC_EMC_SAME_FREQ BIT(16)
#define CLK_SOURCE_EMC_2X_CLK_DIVISOR GENMASK(7, 0)
#define CLK_SRC_PLLM 0
#define CLK_SRC_PLLC 1
#define CLK_SRC_PLLP 2
#define CLK_SRC_CLK_M 3
#define CLK_SRC_PLLM_UD 4
#define CLK_SRC_PLLMB_UD 5
#define CLK_SRC_PLLMB 6
#define CLK_SRC_PLLP_UD 7
struct tegra210_clk_emc {
struct clk_hw hw;
void __iomem *regs;
struct tegra210_clk_emc_provider *provider;
struct clk *parents[8];
};
static inline struct tegra210_clk_emc *
to_tegra210_clk_emc(struct clk_hw *hw)
{
return container_of(hw, struct tegra210_clk_emc, hw);
}
static const char *tegra210_clk_emc_parents[] = {
"pll_m", "pll_c", "pll_p", "clk_m", "pll_m_ud", "pll_mb_ud",
"pll_mb", "pll_p_ud",
};
static u8 tegra210_clk_emc_get_parent(struct clk_hw *hw)
{
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
u32 value;
u8 src;
value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);
src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, value);
return src;
}
static unsigned long tegra210_clk_emc_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
u32 value, div;
/*
* CCF assumes that neither the parent nor its rate will change during
* ->set_rate(), so the parent rate passed in here was cached from the
* parent before the ->set_rate() call.
*
* This can lead to wrong results being reported for the EMC clock if
* the parent and/or parent rate have changed as part of the EMC rate
* change sequence. Fix this by overriding the parent clock with what
* we know to be the correct value after the rate change.
*/
parent_rate = clk_hw_get_rate(clk_hw_get_parent(hw));
value = readl_relaxed(emc->regs + CLK_SOURCE_EMC);
div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, value);
div += 2;
return DIV_ROUND_UP(parent_rate * 2, div);
}
static int tegra210_clk_emc_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
struct tegra210_clk_emc_provider *provider = emc->provider;
unsigned int i;
if (!provider || !provider->configs || provider->num_configs == 0) {
req->rate = clk_hw_get_rate(hw);
return 0;
}
for (i = 0; i < provider->num_configs; i++) {
if (provider->configs[i].rate >= req->rate) {
req->rate = provider->configs[i].rate;
return 0;
}
}
req->rate = provider->configs[i - 1].rate;
return 0;
}
static struct clk *tegra210_clk_emc_find_parent(struct tegra210_clk_emc *emc,
u8 index)
{
struct clk_hw *parent = clk_hw_get_parent_by_index(&emc->hw, index);
const char *name = clk_hw_get_name(parent);
/* XXX implement cache? */
return __clk_lookup(name);
}
static int tegra210_clk_emc_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
struct tegra210_clk_emc_provider *provider = emc->provider;
struct tegra210_clk_emc_config *config;
struct device *dev = provider->dev;
struct clk_hw *old, *new, *parent;
u8 old_idx, new_idx, index;
struct clk *clk;
unsigned int i;
int err;
if (!provider->configs || provider->num_configs == 0)
return -EINVAL;
for (i = 0; i < provider->num_configs; i++) {
if (provider->configs[i].rate >= rate) {
config = &provider->configs[i];
break;
}
}
if (i == provider->num_configs)
config = &provider->configs[i - 1];
old_idx = tegra210_clk_emc_get_parent(hw);
new_idx = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);
old = clk_hw_get_parent_by_index(hw, old_idx);
new = clk_hw_get_parent_by_index(hw, new_idx);
/* if the rate has changed... */
if (config->parent_rate != clk_hw_get_rate(old)) {
/* ... but the clock source remains the same ... */
if (new_idx == old_idx) {
/* ... switch to the alternative clock source. */
switch (new_idx) {
case CLK_SRC_PLLM:
new_idx = CLK_SRC_PLLMB;
break;
case CLK_SRC_PLLM_UD:
new_idx = CLK_SRC_PLLMB_UD;
break;
case CLK_SRC_PLLMB_UD:
new_idx = CLK_SRC_PLLM_UD;
break;
case CLK_SRC_PLLMB:
new_idx = CLK_SRC_PLLM;
break;
}
/*
* This should never happen because we can't deal with
* it.
*/
if (WARN_ON(new_idx == old_idx))
return -EINVAL;
new = clk_hw_get_parent_by_index(hw, new_idx);
}
index = new_idx;
parent = new;
} else {
index = old_idx;
parent = old;
}
clk = tegra210_clk_emc_find_parent(emc, index);
if (IS_ERR(clk)) {
err = PTR_ERR(clk);
dev_err(dev, "failed to get parent clock for index %u: %d\n",
index, err);
return err;
}
/* set the new parent clock to the required rate */
if (clk_get_rate(clk) != config->parent_rate) {
err = clk_set_rate(clk, config->parent_rate);
if (err < 0) {
dev_err(dev, "failed to set rate %lu Hz for %pC: %d\n",
config->parent_rate, clk, err);
return err;
}
}
/* enable the new parent clock */
if (parent != old) {
err = clk_prepare_enable(clk);
if (err < 0) {
dev_err(dev, "failed to enable parent clock %pC: %d\n",
clk, err);
return err;
}
}
/* update the EMC source configuration to reflect the new parent */
config->value &= ~CLK_SOURCE_EMC_2X_CLK_SRC;
config->value |= FIELD_PREP(CLK_SOURCE_EMC_2X_CLK_SRC, index);
/*
* Finally, switch the EMC programming with both old and new parent
* clocks enabled.
*/
err = provider->set_rate(dev, config);
if (err < 0) {
dev_err(dev, "failed to set EMC rate to %lu Hz: %d\n", rate,
err);
/*
* If we're unable to switch to the new EMC frequency, we no
* longer need the new parent to be enabled.
*/
if (parent != old)
clk_disable_unprepare(clk);
return err;
}
/* reparent to new parent clock and disable the old parent clock */
if (parent != old) {
clk = tegra210_clk_emc_find_parent(emc, old_idx);
if (IS_ERR(clk)) {
err = PTR_ERR(clk);
dev_err(dev,
"failed to get parent clock for index %u: %d\n",
old_idx, err);
return err;
}
clk_hw_reparent(hw, parent);
clk_disable_unprepare(clk);
}
return err;
}
static const struct clk_ops tegra210_clk_emc_ops = {
.get_parent = tegra210_clk_emc_get_parent,
.recalc_rate = tegra210_clk_emc_recalc_rate,
.determine_rate = tegra210_clk_emc_determine_rate,
.set_rate = tegra210_clk_emc_set_rate,
};
struct clk *tegra210_clk_register_emc(struct device_node *np,
void __iomem *regs)
{
struct tegra210_clk_emc *emc;
struct clk_init_data init;
struct clk *clk;
emc = kzalloc_obj(*emc);
if (!emc)
return ERR_PTR(-ENOMEM);
emc->regs = regs;
init.name = "emc";
init.ops = &tegra210_clk_emc_ops;
init.flags = CLK_IS_CRITICAL | CLK_GET_RATE_NOCACHE;
init.parent_names = tegra210_clk_emc_parents;
init.num_parents = ARRAY_SIZE(tegra210_clk_emc_parents);
emc->hw.init = &init;
clk = clk_register(NULL, &emc->hw);
if (IS_ERR(clk)) {
kfree(emc);
return clk;
}
return clk;
}
int tegra210_clk_emc_attach(struct clk *clk,
struct tegra210_clk_emc_provider *provider)
{
struct clk_hw *hw = __clk_get_hw(clk);
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(hw);
struct device *dev = provider->dev;
unsigned int i;
int err;
if (!try_module_get(provider->owner))
return -ENODEV;
for (i = 0; i < provider->num_configs; i++) {
struct tegra210_clk_emc_config *config = &provider->configs[i];
struct clk_hw *parent;
bool same_freq;
u8 div, src;
div = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_DIVISOR, config->value);
src = FIELD_GET(CLK_SOURCE_EMC_2X_CLK_SRC, config->value);
/* do basic sanity checking on the EMC timings */
if (div & 0x1) {
dev_err(dev, "invalid odd divider %u for rate %lu Hz\n",
div, config->rate);
err = -EINVAL;
goto put;
}
same_freq = config->value & CLK_SOURCE_EMC_MC_EMC_SAME_FREQ;
if (same_freq != config->same_freq) {
dev_err(dev,
"ambiguous EMC to MC ratio for rate %lu Hz\n",
config->rate);
err = -EINVAL;
goto put;
}
parent = clk_hw_get_parent_by_index(hw, src);
config->parent = src;
if (src == CLK_SRC_PLLM || src == CLK_SRC_PLLM_UD) {
config->parent_rate = config->rate * (1 + div / 2);
} else {
unsigned long rate = config->rate * (1 + div / 2);
config->parent_rate = clk_hw_get_rate(parent);
if (config->parent_rate != rate) {
dev_err(dev,
"rate %lu Hz does not match input\n",
config->rate);
err = -EINVAL;
goto put;
}
}
}
emc->provider = provider;
return 0;
put:
module_put(provider->owner);
return err;
}
EXPORT_SYMBOL_GPL(tegra210_clk_emc_attach);
void tegra210_clk_emc_detach(struct clk *clk)
{
struct tegra210_clk_emc *emc = to_tegra210_clk_emc(__clk_get_hw(clk));
module_put(emc->provider->owner);
emc->provider = NULL;
}
EXPORT_SYMBOL_GPL(tegra210_clk_emc_detach);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved.
*/
#include <asm/div64.h>
#include "clk.h"
#define div_mask(w) ((1 << (w)) - 1)
int div_frac_get(unsigned long rate, unsigned parent_rate, u8 width,
u8 frac_width, u8 flags)
{
u64 divider_ux1 = parent_rate;
int mul;
if (!rate)
return 0;
mul = 1 << frac_width;
if (!(flags & TEGRA_DIVIDER_INT))
divider_ux1 *= mul;
if (flags & TEGRA_DIVIDER_ROUND_UP)
divider_ux1 += rate - 1;
do_div(divider_ux1, rate);
if (flags & TEGRA_DIVIDER_INT)
divider_ux1 *= mul;
if (divider_ux1 < mul)
return 0;
divider_ux1 -= mul;
if (divider_ux1 > div_mask(width))
return div_mask(width);
return divider_ux1;
}
+451
View File
@@ -0,0 +1,451 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#include <linux/clkdev.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/clk/tegra.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset-controller.h>
#include <linux/string_helpers.h>
#include <soc/tegra/fuse.h>
#include "clk.h"
/* Global data of Tegra CPU CAR ops */
static struct device_node *tegra_car_np;
static struct tegra_cpu_car_ops dummy_car_ops;
struct tegra_cpu_car_ops *tegra_cpu_car_ops = &dummy_car_ops;
int *periph_clk_enb_refcnt;
static int periph_banks;
static u32 *periph_state_ctx;
static struct clk **clks;
static int clk_num;
static struct clk_onecell_data clk_data;
/* Handlers for SoC-specific reset lines */
static int (*special_reset_assert)(unsigned long);
static int (*special_reset_deassert)(unsigned long);
static unsigned int num_special_reset;
static const struct tegra_clk_periph_regs periph_regs[] = {
[0] = {
.enb_reg = CLK_OUT_ENB_L,
.enb_set_reg = CLK_OUT_ENB_SET_L,
.enb_clr_reg = CLK_OUT_ENB_CLR_L,
.rst_reg = RST_DEVICES_L,
.rst_set_reg = RST_DEVICES_SET_L,
.rst_clr_reg = RST_DEVICES_CLR_L,
},
[1] = {
.enb_reg = CLK_OUT_ENB_H,
.enb_set_reg = CLK_OUT_ENB_SET_H,
.enb_clr_reg = CLK_OUT_ENB_CLR_H,
.rst_reg = RST_DEVICES_H,
.rst_set_reg = RST_DEVICES_SET_H,
.rst_clr_reg = RST_DEVICES_CLR_H,
},
[2] = {
.enb_reg = CLK_OUT_ENB_U,
.enb_set_reg = CLK_OUT_ENB_SET_U,
.enb_clr_reg = CLK_OUT_ENB_CLR_U,
.rst_reg = RST_DEVICES_U,
.rst_set_reg = RST_DEVICES_SET_U,
.rst_clr_reg = RST_DEVICES_CLR_U,
},
[3] = {
.enb_reg = CLK_OUT_ENB_V,
.enb_set_reg = CLK_OUT_ENB_SET_V,
.enb_clr_reg = CLK_OUT_ENB_CLR_V,
.rst_reg = RST_DEVICES_V,
.rst_set_reg = RST_DEVICES_SET_V,
.rst_clr_reg = RST_DEVICES_CLR_V,
},
[4] = {
.enb_reg = CLK_OUT_ENB_W,
.enb_set_reg = CLK_OUT_ENB_SET_W,
.enb_clr_reg = CLK_OUT_ENB_CLR_W,
.rst_reg = RST_DEVICES_W,
.rst_set_reg = RST_DEVICES_SET_W,
.rst_clr_reg = RST_DEVICES_CLR_W,
},
[5] = {
.enb_reg = CLK_OUT_ENB_X,
.enb_set_reg = CLK_OUT_ENB_SET_X,
.enb_clr_reg = CLK_OUT_ENB_CLR_X,
.rst_reg = RST_DEVICES_X,
.rst_set_reg = RST_DEVICES_SET_X,
.rst_clr_reg = RST_DEVICES_CLR_X,
},
[6] = {
.enb_reg = CLK_OUT_ENB_Y,
.enb_set_reg = CLK_OUT_ENB_SET_Y,
.enb_clr_reg = CLK_OUT_ENB_CLR_Y,
.rst_reg = RST_DEVICES_Y,
.rst_set_reg = RST_DEVICES_SET_Y,
.rst_clr_reg = RST_DEVICES_CLR_Y,
},
};
static void __iomem *clk_base;
static int tegra_clk_rst_assert(struct reset_controller_dev *rcdev,
unsigned long id)
{
/*
* If peripheral is on the APB bus then we must read the APB bus to
* flush the write operation in apb bus. This will avoid peripheral
* access after disabling clock. Since the reset driver has no
* knowledge of which reset IDs represent which devices, simply do
* this all the time.
*/
tegra_read_chipid();
if (id < periph_banks * 32) {
writel_relaxed(BIT(id % 32),
clk_base + periph_regs[id / 32].rst_set_reg);
return 0;
} else if (id < periph_banks * 32 + num_special_reset) {
return special_reset_assert(id);
}
return -EINVAL;
}
static int tegra_clk_rst_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
{
if (id < periph_banks * 32) {
writel_relaxed(BIT(id % 32),
clk_base + periph_regs[id / 32].rst_clr_reg);
return 0;
} else if (id < periph_banks * 32 + num_special_reset) {
return special_reset_deassert(id);
}
return -EINVAL;
}
static int tegra_clk_rst_reset(struct reset_controller_dev *rcdev,
unsigned long id)
{
int err;
err = tegra_clk_rst_assert(rcdev, id);
if (err)
return err;
udelay(1);
return tegra_clk_rst_deassert(rcdev, id);
}
const struct tegra_clk_periph_regs *get_reg_bank(int clkid)
{
int reg_bank = clkid / 32;
if (reg_bank < periph_banks)
return &periph_regs[reg_bank];
else {
WARN_ON(1);
return NULL;
}
}
void tegra_clk_set_pllp_out_cpu(bool enable)
{
u32 val;
val = readl_relaxed(clk_base + CLK_OUT_ENB_Y);
if (enable)
val |= CLK_ENB_PLLP_OUT_CPU;
else
val &= ~CLK_ENB_PLLP_OUT_CPU;
writel_relaxed(val, clk_base + CLK_OUT_ENB_Y);
}
void tegra_clk_periph_suspend(void)
{
unsigned int i, idx;
idx = 0;
for (i = 0; i < periph_banks; i++, idx++)
periph_state_ctx[idx] =
readl_relaxed(clk_base + periph_regs[i].enb_reg);
for (i = 0; i < periph_banks; i++, idx++)
periph_state_ctx[idx] =
readl_relaxed(clk_base + periph_regs[i].rst_reg);
}
void tegra_clk_periph_resume(void)
{
unsigned int i, idx;
idx = 0;
for (i = 0; i < periph_banks; i++, idx++)
writel_relaxed(periph_state_ctx[idx],
clk_base + periph_regs[i].enb_reg);
/*
* All non-boot peripherals will be in reset state on resume.
* Wait for 5us of reset propagation delay before de-asserting
* the peripherals based on the saved context.
*/
fence_udelay(5, clk_base);
for (i = 0; i < periph_banks; i++, idx++)
writel_relaxed(periph_state_ctx[idx],
clk_base + periph_regs[i].rst_reg);
fence_udelay(2, clk_base);
}
static int tegra_clk_periph_ctx_init(int banks)
{
periph_state_ctx = kcalloc(2 * banks, sizeof(*periph_state_ctx),
GFP_KERNEL);
if (!periph_state_ctx)
return -ENOMEM;
return 0;
}
struct clk ** __init tegra_clk_init(void __iomem *regs, int num, int banks)
{
clk_base = regs;
if (WARN_ON(banks > ARRAY_SIZE(periph_regs)))
return NULL;
periph_clk_enb_refcnt = kzalloc_objs(*periph_clk_enb_refcnt, 32 * banks);
if (!periph_clk_enb_refcnt)
return NULL;
periph_banks = banks;
clks = kzalloc_objs(struct clk *, num);
if (!clks) {
kfree(periph_clk_enb_refcnt);
return NULL;
}
clk_num = num;
if (IS_ENABLED(CONFIG_PM_SLEEP)) {
if (tegra_clk_periph_ctx_init(banks)) {
kfree(periph_clk_enb_refcnt);
kfree(clks);
return NULL;
}
}
return clks;
}
void __init tegra_init_dup_clks(struct tegra_clk_duplicate *dup_list,
struct clk *clks[], int clk_max)
{
struct clk *clk;
for (; dup_list->clk_id < clk_max; dup_list++) {
clk = clks[dup_list->clk_id];
dup_list->lookup.clk = clk;
clkdev_add(&dup_list->lookup);
}
}
void tegra_init_from_table(struct tegra_clk_init_table *tbl,
struct clk *clks[], int clk_max)
{
struct clk *clk;
for (; tbl->clk_id < clk_max; tbl++) {
clk = clks[tbl->clk_id];
if (IS_ERR_OR_NULL(clk)) {
pr_err("%s: invalid entry %ld in clks array for id %d\n",
__func__, PTR_ERR(clk), tbl->clk_id);
WARN_ON(1);
continue;
}
if (tbl->parent_id < clk_max) {
struct clk *parent = clks[tbl->parent_id];
if (clk_set_parent(clk, parent)) {
pr_err("%s: Failed to set parent %s of %s\n",
__func__, __clk_get_name(parent),
__clk_get_name(clk));
WARN_ON(1);
}
}
if (tbl->rate)
if (clk_set_rate(clk, tbl->rate)) {
pr_err("%s: Failed to set rate %lu of %s\n",
__func__, tbl->rate,
__clk_get_name(clk));
WARN_ON(1);
}
if (tbl->state)
if (clk_prepare_enable(clk)) {
pr_err("%s: Failed to enable %s\n", __func__,
__clk_get_name(clk));
WARN_ON(1);
}
}
}
static const struct reset_control_ops rst_ops = {
.assert = tegra_clk_rst_assert,
.deassert = tegra_clk_rst_deassert,
.reset = tegra_clk_rst_reset,
};
static struct reset_controller_dev rst_ctlr = {
.ops = &rst_ops,
.owner = THIS_MODULE,
.of_reset_n_cells = 1,
};
void __init tegra_add_of_provider(struct device_node *np,
void *clk_src_onecell_get)
{
int i;
tegra_car_np = np;
for (i = 0; i < clk_num; i++) {
if (IS_ERR(clks[i])) {
pr_err
("Tegra clk %d: register failed with %ld\n",
i, PTR_ERR(clks[i]));
}
if (!clks[i])
clks[i] = ERR_PTR(-EINVAL);
}
clk_data.clks = clks;
clk_data.clk_num = clk_num;
of_clk_add_provider(np, clk_src_onecell_get, &clk_data);
rst_ctlr.of_node = np;
rst_ctlr.nr_resets = periph_banks * 32 + num_special_reset;
reset_controller_register(&rst_ctlr);
}
void __init tegra_init_special_resets(unsigned int num,
int (*assert)(unsigned long),
int (*deassert)(unsigned long))
{
num_special_reset = num;
special_reset_assert = assert;
special_reset_deassert = deassert;
}
void tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
{
int i;
for (i = 0; i < num; i++, dev_clks++)
clk_register_clkdev(clks[dev_clks->dt_id], dev_clks->con_id,
dev_clks->dev_id);
for (i = 0; i < clk_num; i++) {
if (!IS_ERR_OR_NULL(clks[i]))
clk_register_clkdev(clks[i], __clk_get_name(clks[i]),
"tegra-clk-debug");
}
}
struct clk ** __init tegra_lookup_dt_id(int clk_id,
struct tegra_clk *tegra_clk)
{
if (tegra_clk[clk_id].present)
return &clks[tegra_clk[clk_id].dt_id];
else
return NULL;
}
static struct device_node *tegra_clk_get_of_node(struct clk_hw *hw)
{
struct device_node *np;
char *node_name;
node_name = kstrdup_and_replace(hw->init->name, '_', '-', GFP_KERNEL);
if (!node_name)
return NULL;
for_each_child_of_node(tegra_car_np, np) {
if (!strcmp(np->name, node_name))
break;
}
kfree(node_name);
return np;
}
struct clk *tegra_clk_dev_register(struct clk_hw *hw)
{
struct platform_device *pdev, *parent;
const char *dev_name = NULL;
struct device *dev = NULL;
struct device_node *np;
np = tegra_clk_get_of_node(hw);
if (!of_device_is_available(np))
goto put_node;
dev_name = kasprintf(GFP_KERNEL, "tegra_clk_%s", hw->init->name);
if (!dev_name)
goto put_node;
parent = of_find_device_by_node(tegra_car_np);
if (parent) {
pdev = of_platform_device_create(np, dev_name, &parent->dev);
put_device(&parent->dev);
if (!pdev) {
pr_err("%s: failed to create device for %pOF\n",
__func__, np);
goto free_name;
}
dev = &pdev->dev;
pm_runtime_enable(dev);
} else {
WARN(1, "failed to find device for %pOF\n", tegra_car_np);
}
free_name:
kfree(dev_name);
put_node:
of_node_put(np);
return clk_register(dev, hw);
}
tegra_clk_apply_init_table_func tegra_clk_apply_init_table;
static int __init tegra_clocks_apply_init_table(void)
{
if (!tegra_clk_apply_init_table)
return 0;
tegra_clk_apply_init_table();
return 0;
}
arch_initcall(tegra_clocks_apply_init_table);
+929
View File
@@ -0,0 +1,929 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
*/
#ifndef __TEGRA_CLK_H
#define __TEGRA_CLK_H
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/delay.h>
#define CLK_OUT_ENB_L 0x010
#define CLK_OUT_ENB_H 0x014
#define CLK_OUT_ENB_U 0x018
#define CLK_OUT_ENB_V 0x360
#define CLK_OUT_ENB_W 0x364
#define CLK_OUT_ENB_X 0x280
#define CLK_OUT_ENB_Y 0x298
#define CLK_ENB_PLLP_OUT_CPU BIT(31)
#define CLK_OUT_ENB_SET_L 0x320
#define CLK_OUT_ENB_CLR_L 0x324
#define CLK_OUT_ENB_SET_H 0x328
#define CLK_OUT_ENB_CLR_H 0x32c
#define CLK_OUT_ENB_SET_U 0x330
#define CLK_OUT_ENB_CLR_U 0x334
#define CLK_OUT_ENB_SET_V 0x440
#define CLK_OUT_ENB_CLR_V 0x444
#define CLK_OUT_ENB_SET_W 0x448
#define CLK_OUT_ENB_CLR_W 0x44c
#define CLK_OUT_ENB_SET_X 0x284
#define CLK_OUT_ENB_CLR_X 0x288
#define CLK_OUT_ENB_SET_Y 0x29c
#define CLK_OUT_ENB_CLR_Y 0x2a0
#define RST_DEVICES_L 0x004
#define RST_DEVICES_H 0x008
#define RST_DEVICES_U 0x00C
#define RST_DEVICES_V 0x358
#define RST_DEVICES_W 0x35C
#define RST_DEVICES_X 0x28C
#define RST_DEVICES_Y 0x2a4
#define RST_DEVICES_SET_L 0x300
#define RST_DEVICES_CLR_L 0x304
#define RST_DEVICES_SET_H 0x308
#define RST_DEVICES_CLR_H 0x30c
#define RST_DEVICES_SET_U 0x310
#define RST_DEVICES_CLR_U 0x314
#define RST_DEVICES_SET_V 0x430
#define RST_DEVICES_CLR_V 0x434
#define RST_DEVICES_SET_W 0x438
#define RST_DEVICES_CLR_W 0x43c
#define RST_DEVICES_SET_X 0x290
#define RST_DEVICES_CLR_X 0x294
#define RST_DEVICES_SET_Y 0x2a8
#define RST_DEVICES_CLR_Y 0x2ac
/*
* Tegra CLK_OUT_ENB registers have some undefined bits which are not used and
* any accidental write of 1 to these bits can cause PSLVERR.
* So below are the valid mask defines for each CLK_OUT_ENB register used to
* turn ON only the valid clocks.
*/
#define TEGRA210_CLK_ENB_VLD_MSK_L 0xdcd7dff9
#define TEGRA210_CLK_ENB_VLD_MSK_H 0x87d1f3e7
#define TEGRA210_CLK_ENB_VLD_MSK_U 0xf3fed3fa
#define TEGRA210_CLK_ENB_VLD_MSK_V 0xffc18cfb
#define TEGRA210_CLK_ENB_VLD_MSK_W 0x793fb7ff
#define TEGRA210_CLK_ENB_VLD_MSK_X 0x3fe66fff
#define TEGRA210_CLK_ENB_VLD_MSK_Y 0xfc1fc7ff
/**
* struct tegra_clk_sync_source - external clock source from codec
*
* @hw: handle between common and hardware-specific interfaces
* @rate: input frequency from source
* @max_rate: max rate allowed
*/
struct tegra_clk_sync_source {
struct clk_hw hw;
unsigned long rate;
unsigned long max_rate;
};
#define to_clk_sync_source(_hw) \
container_of(_hw, struct tegra_clk_sync_source, hw)
extern const struct clk_ops tegra_clk_sync_source_ops;
extern int *periph_clk_enb_refcnt;
struct clk *tegra_clk_register_sync_source(const char *name,
unsigned long max_rate);
/**
* struct tegra_clk_frac_div - fractional divider clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing divider
* @flags: hardware-specific flags
* @shift: shift to the divider bit field
* @width: width of the divider bit field
* @frac_width: width of the fractional bit field
* @lock: register lock
*
* Flags:
* TEGRA_DIVIDER_ROUND_UP - This flags indicates to round up the divider value.
* TEGRA_DIVIDER_FIXED - Fixed rate PLL dividers has addition override bit, this
* flag indicates that this divider is for fixed rate PLL.
* TEGRA_DIVIDER_INT - Some modules can not cope with the duty cycle when
* fraction bit is set. This flags indicates to calculate divider for which
* fracton bit will be zero.
* TEGRA_DIVIDER_UART - UART module divider has additional enable bit which is
* set when divider value is not 0. This flags indicates that the divider
* is for UART module.
*/
struct tegra_clk_frac_div {
struct clk_hw hw;
void __iomem *reg;
u8 flags;
u8 shift;
u8 width;
u8 frac_width;
spinlock_t *lock;
};
#define to_clk_frac_div(_hw) container_of(_hw, struct tegra_clk_frac_div, hw)
#define TEGRA_DIVIDER_ROUND_UP BIT(0)
#define TEGRA_DIVIDER_FIXED BIT(1)
#define TEGRA_DIVIDER_INT BIT(2)
#define TEGRA_DIVIDER_UART BIT(3)
extern const struct clk_ops tegra_clk_frac_div_ops;
struct clk *tegra_clk_register_divider(const char *name,
const char *parent_name, void __iomem *reg,
unsigned long flags, u8 clk_divider_flags, u8 shift, u8 width,
u8 frac_width, spinlock_t *lock);
struct clk *tegra_clk_register_mc(const char *name, const char *parent_name,
void __iomem *reg, spinlock_t *lock);
/*
* Tegra PLL:
*
* In general, there are 3 requirements for each PLL
* that SW needs to be comply with.
* (1) Input frequency range (REF).
* (2) Comparison frequency range (CF). CF = REF/DIVM.
* (3) VCO frequency range (VCO). VCO = CF * DIVN.
*
* The final PLL output frequency (FO) = VCO >> DIVP.
*/
/**
* struct tegra_clk_pll_freq_table - PLL frequecy table
*
* @input_rate: input rate from source
* @output_rate: output rate from PLL for the input rate
* @n: feedback divider
* @m: input divider
* @p: post divider
* @cpcon: charge pump current
* @sdm_data: fraction divider setting (0 = disabled)
*/
struct tegra_clk_pll_freq_table {
unsigned long input_rate;
unsigned long output_rate;
u32 n;
u32 m;
u8 p;
u8 cpcon;
u16 sdm_data;
};
/**
* struct pdiv_map - map post divider to hw value
*
* @pdiv: post divider
* @hw_val: value to be written to the PLL hw
*/
struct pdiv_map {
u8 pdiv;
u8 hw_val;
};
/**
* struct div_nmp - offset and width of m,n and p fields
*
* @divn_shift: shift to the feedback divider bit field
* @divn_width: width of the feedback divider bit field
* @divm_shift: shift to the input divider bit field
* @divm_width: width of the input divider bit field
* @divp_shift: shift to the post divider bit field
* @divp_width: width of the post divider bit field
* @override_divn_shift: shift to the feedback divider bitfield in override reg
* @override_divm_shift: shift to the input divider bitfield in override reg
* @override_divp_shift: shift to the post divider bitfield in override reg
*/
struct div_nmp {
u8 divn_shift;
u8 divn_width;
u8 divm_shift;
u8 divm_width;
u8 divp_shift;
u8 divp_width;
u8 override_divn_shift;
u8 override_divm_shift;
u8 override_divp_shift;
};
#define MAX_PLL_MISC_REG_COUNT 6
struct tegra_clk_pll;
/**
* struct tegra_clk_pll_params - PLL parameters
*
* @input_min: Minimum input frequency
* @input_max: Maximum input frequency
* @cf_min: Minimum comparison frequency
* @cf_max: Maximum comparison frequency
* @vco_min: Minimum VCO frequency
* @vco_max: Maximum VCO frequency
* @base_reg: PLL base reg offset
* @misc_reg: PLL misc reg offset
* @lock_reg: PLL lock reg offset
* @lock_mask: Bitmask for PLL lock status
* @lock_enable_bit_idx: Bit index to enable PLL lock
* @iddq_reg: PLL IDDQ register offset
* @iddq_bit_idx: Bit index to enable PLL IDDQ
* @reset_reg: Register offset of where RESET bit is
* @reset_bit_idx: Shift of reset bit in reset_reg
* @sdm_din_reg: Register offset where SDM settings are
* @sdm_din_mask: Mask of SDM divider bits
* @sdm_ctrl_reg: Register offset where SDM enable is
* @sdm_ctrl_en_mask: Mask of SDM enable bit
* @ssc_ctrl_reg: Register offset where SSC settings are
* @ssc_ctrl_en_mask: Mask of SSC enable bit
* @aux_reg: AUX register offset
* @dyn_ramp_reg: Dynamic ramp control register offset
* @ext_misc_reg: Miscellaneous control register offsets
* @pmc_divnm_reg: n, m divider PMC override register offset (PLLM)
* @pmc_divp_reg: p divider PMC override register offset (PLLM)
* @flags: PLL flags
* @stepa_shift: Dynamic ramp step A field shift
* @stepb_shift: Dynamic ramp step B field shift
* @lock_delay: Delay in us if PLL lock is not used
* @max_p: maximum value for the p divider
* @defaults_set: Boolean signaling all reg defaults for PLL set.
* @pdiv_tohw: mapping of p divider to register values
* @div_nmp: offsets and widths on n, m and p fields
* @freq_table: array of frequencies supported by PLL
* @fixed_rate: PLL rate if it is fixed
* @mdiv_default: Default value for fixed mdiv for this PLL
* @round_p_to_pdiv: Callback used to round p to the closed pdiv
* @set_gain: Callback to adjust N div for SDM enabled
* PLL's based on fractional divider value.
* @calc_rate: Callback used to change how out of table
* rates (dividers and multipler) are calculated.
* @adjust_vco: Callback to adjust the programming range of the
* divider range (if SDM is present)
* @set_defaults: Callback which will try to initialize PLL
* registers to sane default values. This is first
* tried during PLL registration, but if the PLL
* is already enabled, it will be done the first
* time the rate is changed while the PLL is
* disabled.
* @dyn_ramp: Callback which can be used to define a custom
* dynamic ramp function for a given PLL.
* @pre_rate_change: Callback which is invoked just before changing
* PLL's rate.
* @post_rate_change: Callback which is invoked right after changing
* PLL's rate.
*
* Flags:
* TEGRA_PLL_USE_LOCK - This flag indicated to use lock bits for
* PLL locking. If not set it will use lock_delay value to wait.
* TEGRA_PLL_HAS_CPCON - This flag indicates that CPCON value needs
* to be programmed to change output frequency of the PLL.
* TEGRA_PLL_SET_LFCON - This flag indicates that LFCON value needs
* to be programmed to change output frequency of the PLL.
* TEGRA_PLL_SET_DCCON - This flag indicates that DCCON value needs
* to be programmed to change output frequency of the PLL.
* TEGRA_PLLU - PLLU has inverted post divider. This flags indicated
* that it is PLLU and invert post divider value.
* TEGRA_PLLM - PLLM has additional override settings in PMC. This
* flag indicates that it is PLLM and use override settings.
* TEGRA_PLL_FIXED - We are not supposed to change output frequency
* of some plls.
* TEGRA_PLLE_CONFIGURE - Configure PLLE when enabling.
* TEGRA_PLL_LOCK_MISC - Lock bit is in the misc register instead of the
* base register.
* TEGRA_PLL_BYPASS - PLL has bypass bit
* TEGRA_PLL_HAS_LOCK_ENABLE - PLL has bit to enable lock monitoring
* TEGRA_MDIV_NEW - Switch to new method for calculating fixed mdiv
* it may be more accurate (especially if SDM present)
* TEGRA_PLLMB - PLLMB has should be treated similar to PLLM. This
* flag indicated that it is PLLMB.
* TEGRA_PLL_VCO_OUT - Used to indicate that the PLL has a VCO output
*/
struct tegra_clk_pll_params {
unsigned long input_min;
unsigned long input_max;
unsigned long cf_min;
unsigned long cf_max;
unsigned long vco_min;
unsigned long vco_max;
u32 base_reg;
u32 misc_reg;
u32 lock_reg;
u32 lock_mask;
u32 lock_enable_bit_idx;
u32 iddq_reg;
u32 iddq_bit_idx;
u32 reset_reg;
u32 reset_bit_idx;
u32 sdm_din_reg;
u32 sdm_din_mask;
u32 sdm_ctrl_reg;
u32 sdm_ctrl_en_mask;
u32 ssc_ctrl_reg;
u32 ssc_ctrl_en_mask;
u32 aux_reg;
u32 dyn_ramp_reg;
u32 ext_misc_reg[MAX_PLL_MISC_REG_COUNT];
u32 pmc_divnm_reg;
u32 pmc_divp_reg;
u32 flags;
int stepa_shift;
int stepb_shift;
int lock_delay;
int max_p;
bool defaults_set;
const struct pdiv_map *pdiv_tohw;
struct div_nmp *div_nmp;
struct tegra_clk_pll_freq_table *freq_table;
unsigned long fixed_rate;
u16 mdiv_default;
u32 (*round_p_to_pdiv)(u32 p, u32 *pdiv);
void (*set_gain)(struct tegra_clk_pll_freq_table *cfg);
int (*calc_rate)(struct clk_hw *hw,
struct tegra_clk_pll_freq_table *cfg,
unsigned long rate, unsigned long parent_rate);
unsigned long (*adjust_vco)(struct tegra_clk_pll_params *pll_params,
unsigned long parent_rate);
void (*set_defaults)(struct tegra_clk_pll *pll);
int (*dyn_ramp)(struct tegra_clk_pll *pll,
struct tegra_clk_pll_freq_table *cfg);
int (*pre_rate_change)(void);
void (*post_rate_change)(void);
};
#define TEGRA_PLL_USE_LOCK BIT(0)
#define TEGRA_PLL_HAS_CPCON BIT(1)
#define TEGRA_PLL_SET_LFCON BIT(2)
#define TEGRA_PLL_SET_DCCON BIT(3)
#define TEGRA_PLLU BIT(4)
#define TEGRA_PLLM BIT(5)
#define TEGRA_PLL_FIXED BIT(6)
#define TEGRA_PLLE_CONFIGURE BIT(7)
#define TEGRA_PLL_LOCK_MISC BIT(8)
#define TEGRA_PLL_BYPASS BIT(9)
#define TEGRA_PLL_HAS_LOCK_ENABLE BIT(10)
#define TEGRA_MDIV_NEW BIT(11)
#define TEGRA_PLLMB BIT(12)
#define TEGRA_PLL_VCO_OUT BIT(13)
/**
* struct tegra_clk_pll - Tegra PLL clock
*
* @hw: handle between common and hardware-specifix interfaces
* @clk_base: address of CAR controller
* @pmc: address of PMC, required to read override bits
* @lock: register lock
* @params: PLL parameters
*/
struct tegra_clk_pll {
struct clk_hw hw;
void __iomem *clk_base;
void __iomem *pmc;
spinlock_t *lock;
struct tegra_clk_pll_params *params;
};
#define to_clk_pll(_hw) container_of(_hw, struct tegra_clk_pll, hw)
/**
* struct tegra_audio_clk_info - Tegra Audio Clk Information
*
* @name: name for the audio pll
* @pll_params: pll_params for audio pll
* @clk_id: clk_ids for the audio pll
* @parent: name of the parent of the audio pll
*/
struct tegra_audio_clk_info {
char *name;
struct tegra_clk_pll_params *pll_params;
int clk_id;
char *parent;
};
extern const struct clk_ops tegra_clk_pll_ops;
extern const struct clk_ops tegra_clk_plle_ops;
struct clk *tegra_clk_register_pll(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags, struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_plle(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags, struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllxc(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllm(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllc(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllre(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock, unsigned long parent_rate);
struct clk *tegra_clk_register_pllre_tegra210(const char *name,
const char *parent_name, void __iomem *clk_base,
void __iomem *pmc, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock, unsigned long parent_rate);
struct clk *tegra_clk_register_plle_tegra114(const char *name,
const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_plle_tegra210(const char *name,
const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllc_tegra210(const char *name,
const char *parent_name, void __iomem *clk_base,
void __iomem *pmc, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllss_tegra210(const char *name,
const char *parent_name, void __iomem *clk_base,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllss(const char *name, const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllmb(const char *name, const char *parent_name,
void __iomem *clk_base, void __iomem *pmc,
unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllu(const char *name, const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllu_tegra114(const char *name,
const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
struct clk *tegra_clk_register_pllu_tegra210(const char *name,
const char *parent_name,
void __iomem *clk_base, unsigned long flags,
struct tegra_clk_pll_params *pll_params,
spinlock_t *lock);
/**
* struct tegra_clk_pll_out - PLL divider down clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register containing the PLL divider
* @enb_bit_idx: bit to enable/disable PLL divider
* @rst_bit_idx: bit to reset PLL divider
* @lock: register lock
* @flags: hardware-specific flags
*/
struct tegra_clk_pll_out {
struct clk_hw hw;
void __iomem *reg;
u8 enb_bit_idx;
u8 rst_bit_idx;
spinlock_t *lock;
u8 flags;
};
#define to_clk_pll_out(_hw) container_of(_hw, struct tegra_clk_pll_out, hw)
extern const struct clk_ops tegra_clk_pll_out_ops;
struct clk *tegra_clk_register_pll_out(const char *name,
const char *parent_name, void __iomem *reg, u8 enb_bit_idx,
u8 rst_bit_idx, unsigned long flags, u8 pll_div_flags,
spinlock_t *lock);
/**
* struct tegra_clk_periph_regs - Registers controlling peripheral clock
*
* @enb_reg: read the enable status
* @enb_set_reg: write 1 to enable clock
* @enb_clr_reg: write 1 to disable clock
* @rst_reg: read the reset status
* @rst_set_reg: write 1 to assert the reset of peripheral
* @rst_clr_reg: write 1 to deassert the reset of peripheral
*/
struct tegra_clk_periph_regs {
u32 enb_reg;
u32 enb_set_reg;
u32 enb_clr_reg;
u32 rst_reg;
u32 rst_set_reg;
u32 rst_clr_reg;
};
/**
* struct tegra_clk_periph_gate - peripheral gate clock
*
* @magic: magic number to validate type
* @hw: handle between common and hardware-specific interfaces
* @clk_base: address of CAR controller
* @regs: Registers to control the peripheral
* @flags: hardware-specific flags
* @clk_num: Clock number
* @enable_refcnt: array to maintain reference count of the clock
*
* Flags:
* TEGRA_PERIPH_NO_RESET - This flag indicates that reset is not allowed
* for this module.
* TEGRA_PERIPH_ON_APB - If peripheral is in the APB bus then read the
* bus to flush the write operation in apb bus. This flag indicates
* that this peripheral is in apb bus.
* TEGRA_PERIPH_WAR_1005168 - Apply workaround for Tegra114 MSENC bug
*/
struct tegra_clk_periph_gate {
u32 magic;
struct clk_hw hw;
void __iomem *clk_base;
u8 flags;
int clk_num;
int *enable_refcnt;
const struct tegra_clk_periph_regs *regs;
};
#define to_clk_periph_gate(_hw) \
container_of(_hw, struct tegra_clk_periph_gate, hw)
#define TEGRA_CLK_PERIPH_GATE_MAGIC 0x17760309
#define TEGRA_PERIPH_NO_RESET BIT(0)
#define TEGRA_PERIPH_ON_APB BIT(2)
#define TEGRA_PERIPH_WAR_1005168 BIT(3)
#define TEGRA_PERIPH_NO_DIV BIT(4)
#define TEGRA_PERIPH_NO_GATE BIT(5)
extern const struct clk_ops tegra_clk_periph_gate_ops;
struct clk *tegra_clk_register_periph_gate(const char *name,
const char *parent_name, u8 gate_flags, void __iomem *clk_base,
unsigned long flags, int clk_num, int *enable_refcnt);
struct tegra_clk_periph_fixed {
struct clk_hw hw;
void __iomem *base;
const struct tegra_clk_periph_regs *regs;
unsigned int mul;
unsigned int div;
unsigned int num;
};
struct clk *tegra_clk_register_periph_fixed(const char *name,
const char *parent,
unsigned long flags,
void __iomem *base,
unsigned int mul,
unsigned int div,
unsigned int num);
/**
* struct clk-periph - peripheral clock
*
* @magic: magic number to validate type
* @hw: handle between common and hardware-specific interfaces
* @mux: mux clock
* @divider: divider clock
* @gate: gate clock
* @mux_ops: mux clock ops
* @div_ops: divider clock ops
* @gate_ops: gate clock ops
*/
struct tegra_clk_periph {
u32 magic;
struct clk_hw hw;
struct clk_mux mux;
struct tegra_clk_frac_div divider;
struct tegra_clk_periph_gate gate;
const struct clk_ops *mux_ops;
const struct clk_ops *div_ops;
const struct clk_ops *gate_ops;
};
#define to_clk_periph(_hw) container_of(_hw, struct tegra_clk_periph, hw)
#define TEGRA_CLK_PERIPH_MAGIC 0x18221223
struct clk *tegra_clk_register_periph(const char *name,
const char * const *parent_names, int num_parents,
struct tegra_clk_periph *periph, void __iomem *clk_base,
u32 offset, unsigned long flags);
struct clk *tegra_clk_register_periph_nodiv(const char *name,
const char * const *parent_names, int num_parents,
struct tegra_clk_periph *periph, void __iomem *clk_base,
u32 offset);
#define TEGRA_CLK_PERIPH(_mux_shift, _mux_mask, _mux_flags, \
_div_shift, _div_width, _div_frac_width, \
_div_flags, _clk_num,\
_gate_flags, _table, _lock) \
{ \
.mux = { \
.flags = _mux_flags, \
.shift = _mux_shift, \
.mask = _mux_mask, \
.table = _table, \
.lock = _lock, \
}, \
.divider = { \
.flags = _div_flags, \
.shift = _div_shift, \
.width = _div_width, \
.frac_width = _div_frac_width, \
.lock = _lock, \
}, \
.gate = { \
.flags = _gate_flags, \
.clk_num = _clk_num, \
}, \
.mux_ops = &clk_mux_ops, \
.div_ops = &tegra_clk_frac_div_ops, \
.gate_ops = &tegra_clk_periph_gate_ops, \
}
struct tegra_periph_init_data {
const char *name;
int clk_id;
union {
const char *const *parent_names;
const char *parent_name;
} p;
int num_parents;
struct tegra_clk_periph periph;
u32 offset;
const char *con_id;
const char *dev_id;
unsigned long flags;
};
#define TEGRA_INIT_DATA_TABLE(_name, _con_id, _dev_id, _parent_names, _offset,\
_mux_shift, _mux_mask, _mux_flags, _div_shift, \
_div_width, _div_frac_width, _div_flags, \
_clk_num, _gate_flags, _clk_id, _table, \
_flags, _lock) \
{ \
.name = _name, \
.clk_id = _clk_id, \
.p.parent_names = _parent_names, \
.num_parents = ARRAY_SIZE(_parent_names), \
.periph = TEGRA_CLK_PERIPH(_mux_shift, _mux_mask, \
_mux_flags, _div_shift, \
_div_width, _div_frac_width, \
_div_flags, _clk_num, \
_gate_flags, _table, _lock), \
.offset = _offset, \
.con_id = _con_id, \
.dev_id = _dev_id, \
.flags = _flags \
}
#define TEGRA_INIT_DATA(_name, _con_id, _dev_id, _parent_names, _offset,\
_mux_shift, _mux_width, _mux_flags, _div_shift, \
_div_width, _div_frac_width, _div_flags, \
_clk_num, _gate_flags, _clk_id) \
TEGRA_INIT_DATA_TABLE(_name, _con_id, _dev_id, _parent_names, _offset,\
_mux_shift, BIT(_mux_width) - 1, _mux_flags, \
_div_shift, _div_width, _div_frac_width, _div_flags, \
_clk_num, _gate_flags, _clk_id,\
NULL, 0, NULL)
struct clk *tegra_clk_register_periph_data(void __iomem *clk_base,
struct tegra_periph_init_data *init);
/**
* struct clk_super_mux - super clock
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling multiplexer
* @width: width of the multiplexer bit field
* @flags: hardware-specific flags
* @div2_index: bit controlling divide-by-2
* @pllx_index: PLLX index in the parent list
* @lock: register lock
*
* Flags:
* TEGRA_DIVIDER_2 - LP cluster has additional divider. This flag indicates
* that this is LP cluster clock.
* TEGRA210_CPU_CLK - This flag is used to identify CPU cluster for gen5
* super mux parent using PLLP branches. To use PLLP branches to CPU, need
* to configure additional bit PLLP_OUT_CPU in the clock registers.
* TEGRA20_SUPER_CLK - Tegra20 doesn't have a dedicated divider for Super
* clocks, it only has a clock-skipper.
*/
struct tegra_clk_super_mux {
struct clk_hw hw;
void __iomem *reg;
struct tegra_clk_frac_div frac_div;
const struct clk_ops *div_ops;
u8 width;
u8 flags;
u8 div2_index;
u8 pllx_index;
spinlock_t *lock;
};
#define to_clk_super_mux(_hw) container_of(_hw, struct tegra_clk_super_mux, hw)
#define TEGRA_DIVIDER_2 BIT(0)
#define TEGRA210_CPU_CLK BIT(1)
#define TEGRA20_SUPER_CLK BIT(2)
extern const struct clk_ops tegra_clk_super_ops;
struct clk *tegra_clk_register_super_mux(const char *name,
const char **parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
u8 width, u8 pllx_index, u8 div2_index, spinlock_t *lock);
struct clk *tegra_clk_register_super_clk(const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
spinlock_t *lock);
struct clk *tegra_clk_register_super_cclk(const char *name,
const char * const *parent_names, u8 num_parents,
unsigned long flags, void __iomem *reg, u8 clk_super_flags,
spinlock_t *lock);
int tegra_cclk_pre_pllx_rate_change(void);
void tegra_cclk_post_pllx_rate_change(void);
/**
* struct tegra_sdmmc_mux - switch divider with Low Jitter inputs for SDMMC
*
* @hw: handle between common and hardware-specific interfaces
* @reg: register controlling mux and divider
* @flags: hardware-specific flags
* @lock: optional register lock
* @gate: gate clock
* @gate_ops: gate clock ops
*/
struct tegra_sdmmc_mux {
struct clk_hw hw;
void __iomem *reg;
spinlock_t *lock;
const struct clk_ops *gate_ops;
struct tegra_clk_periph_gate gate;
u8 div_flags;
};
#define to_clk_sdmmc_mux(_hw) container_of(_hw, struct tegra_sdmmc_mux, hw)
struct clk *tegra_clk_register_sdmmc_mux_div(const char *name,
void __iomem *clk_base, u32 offset, u32 clk_num, u8 div_flags,
unsigned long flags, void *lock);
/**
* struct clk_init_table - clock initialization table
* @clk_id: clock id as mentioned in device tree bindings
* @parent_id: parent clock id as mentioned in device tree bindings
* @rate: rate to set
* @state: enable/disable
*/
struct tegra_clk_init_table {
unsigned int clk_id;
unsigned int parent_id;
unsigned long rate;
int state;
};
/**
* struct clk_duplicate - duplicate clocks
* @clk_id: clock id as mentioned in device tree bindings
* @lookup: duplicate lookup entry for the clock
*/
struct tegra_clk_duplicate {
int clk_id;
struct clk_lookup lookup;
};
#define TEGRA_CLK_DUPLICATE(_clk_id, _dev, _con) \
{ \
.clk_id = _clk_id, \
.lookup = { \
.dev_id = _dev, \
.con_id = _con, \
}, \
}
struct tegra_clk {
int dt_id;
bool present;
};
struct tegra_devclk {
int dt_id;
char *dev_id;
char *con_id;
};
void tegra_init_special_resets(unsigned int num, int (*assert)(unsigned long),
int (*deassert)(unsigned long));
void tegra_init_from_table(struct tegra_clk_init_table *tbl,
struct clk *clks[], int clk_max);
void tegra_init_dup_clks(struct tegra_clk_duplicate *dup_list,
struct clk *clks[], int clk_max);
const struct tegra_clk_periph_regs *get_reg_bank(int clkid);
struct clk **tegra_clk_init(void __iomem *clk_base, int num, int periph_banks);
struct clk **tegra_lookup_dt_id(int clk_id, struct tegra_clk *tegra_clk);
void tegra_add_of_provider(struct device_node *np, void *clk_src_onecell_get);
void tegra_register_devclks(struct tegra_devclk *dev_clks, int num);
void tegra_audio_clk_init(void __iomem *clk_base,
void __iomem *pmc_base, struct tegra_clk *tegra_clks,
struct tegra_audio_clk_info *audio_info,
unsigned int num_plls, unsigned long sync_max_rate);
void tegra_periph_clk_init(void __iomem *clk_base, void __iomem *pmc_base,
struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *pll_params);
void tegra_fixed_clk_init(struct tegra_clk *tegra_clks);
int tegra_osc_clk_init(void __iomem *clk_base, struct tegra_clk *clks,
unsigned long *input_freqs, unsigned int num,
unsigned int clk_m_div, unsigned long *osc_freq,
unsigned long *pll_ref_freq);
void tegra_super_clk_gen4_init(void __iomem *clk_base,
void __iomem *pmc_base, struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *pll_params);
void tegra_super_clk_gen5_init(void __iomem *clk_base,
void __iomem *pmc_base, struct tegra_clk *tegra_clks,
struct tegra_clk_pll_params *pll_params);
#ifdef CONFIG_TEGRA124_CLK_EMC
struct clk *tegra124_clk_register_emc(void __iomem *base, struct device_node *np,
spinlock_t *lock);
bool tegra124_clk_emc_driver_available(struct clk_hw *emc_hw);
#else
static inline struct clk *
tegra124_clk_register_emc(void __iomem *base, struct device_node *np,
spinlock_t *lock)
{
return NULL;
}
static inline bool tegra124_clk_emc_driver_available(struct clk_hw *emc_hw)
{
return false;
}
#endif
void tegra114_clock_tune_cpu_trimmers_high(void);
void tegra114_clock_tune_cpu_trimmers_low(void);
void tegra114_clock_tune_cpu_trimmers_init(void);
typedef void (*tegra_clk_apply_init_table_func)(void);
extern tegra_clk_apply_init_table_func tegra_clk_apply_init_table;
int tegra_pll_wait_for_lock(struct tegra_clk_pll *pll);
u16 tegra_pll_get_fixed_mdiv(struct clk_hw *hw, unsigned long input_rate);
int tegra_pll_p_div_to_hw(struct tegra_clk_pll *pll, u8 p_div);
int div_frac_get(unsigned long rate, unsigned parent_rate, u8 width,
u8 frac_width, u8 flags);
void tegra_clk_osc_resume(void __iomem *clk_base);
void tegra_clk_set_pllp_out_cpu(bool enable);
void tegra_clk_periph_suspend(void);
void tegra_clk_periph_resume(void);
/* Combined read fence with delay */
#define fence_udelay(delay, reg) \
do { \
readl(reg); \
udelay(delay); \
} while (0)
bool tegra20_clk_emc_driver_available(struct clk_hw *emc_hw);
struct clk *tegra20_clk_register_emc(void __iomem *ioaddr, bool low_jitter);
struct clk *tegra210_clk_register_emc(struct device_node *np,
void __iomem *regs);
struct clk *tegra_clk_dev_register(struct clk_hw *hw);
#endif /* TEGRA_CLK_H */
+142
View File
@@ -0,0 +1,142 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Utility functions for parsing Tegra CVB voltage tables
*
* Copyright (C) 2012-2019 NVIDIA Corporation. All rights reserved.
*/
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/pm_opp.h>
#include "cvb.h"
/* cvb_mv = ((c2 * speedo / s_scale + c1) * speedo / s_scale + c0) */
static inline int get_cvb_voltage(int speedo, int s_scale,
const struct cvb_coefficients *cvb)
{
int mv;
/* apply only speedo scale: output mv = cvb_mv * v_scale */
mv = DIV_ROUND_CLOSEST(cvb->c2 * speedo, s_scale);
mv = DIV_ROUND_CLOSEST((mv + cvb->c1) * speedo, s_scale) + cvb->c0;
return mv;
}
static int round_cvb_voltage(int mv, int v_scale,
const struct rail_alignment *align)
{
/* combined: apply voltage scale and round to cvb alignment step */
int uv;
int step = (align->step_uv ? : 1000) * v_scale;
int offset = align->offset_uv * v_scale;
uv = max(mv * 1000, offset) - offset;
uv = DIV_ROUND_UP(uv, step) * align->step_uv + align->offset_uv;
return uv / 1000;
}
enum {
DOWN,
UP
};
static int round_voltage(int mv, const struct rail_alignment *align, int up)
{
if (align->step_uv) {
int uv;
uv = max(mv * 1000, align->offset_uv) - align->offset_uv;
uv = (uv + (up ? align->step_uv - 1 : 0)) / align->step_uv;
return (uv * align->step_uv + align->offset_uv) / 1000;
}
return mv;
}
static int build_opp_table(struct device *dev, const struct cvb_table *table,
struct rail_alignment *align,
int speedo_value, unsigned long max_freq)
{
int i, ret, dfll_mv, min_mv, max_mv;
min_mv = round_voltage(table->min_millivolts, align, UP);
max_mv = round_voltage(table->max_millivolts, align, DOWN);
for (i = 0; i < MAX_DVFS_FREQS; i++) {
const struct cvb_table_freq_entry *entry = &table->entries[i];
if (!entry->freq || (entry->freq > max_freq))
break;
dfll_mv = get_cvb_voltage(speedo_value, table->speedo_scale,
&entry->coefficients);
dfll_mv = round_cvb_voltage(dfll_mv, table->voltage_scale,
align);
dfll_mv = clamp(dfll_mv, min_mv, max_mv);
ret = dev_pm_opp_add(dev, entry->freq, dfll_mv * 1000);
if (ret)
return ret;
}
return 0;
}
/**
* tegra_cvb_add_opp_table - build OPP table from Tegra CVB tables
* @dev: the struct device * for which the OPP table is built
* @tables: array of CVB tables
* @count: size of the previously mentioned array
* @align: parameters of the regulator step and offset
* @process_id: process id of the HW module
* @speedo_id: speedo id of the HW module
* @speedo_value: speedo value of the HW module
* @max_freq: highest safe clock rate
*
* On Tegra, a CVB table encodes the relationship between operating voltage
* and safe maximal frequency for a given module (e.g. GPU or CPU). This
* function calculates the optimal voltage-frequency operating points
* for the given arguments and exports them via the OPP library for the
* given @dev. Returns a pointer to the struct cvb_table that matched
* or an ERR_PTR on failure.
*/
const struct cvb_table *
tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *tables,
size_t count, struct rail_alignment *align,
int process_id, int speedo_id, int speedo_value,
unsigned long max_freq)
{
size_t i;
int ret;
for (i = 0; i < count; i++) {
const struct cvb_table *table = &tables[i];
if (table->speedo_id != -1 && table->speedo_id != speedo_id)
continue;
if (table->process_id != -1 && table->process_id != process_id)
continue;
ret = build_opp_table(dev, table, align, speedo_value,
max_freq);
return ret ? ERR_PTR(ret) : table;
}
return ERR_PTR(-EINVAL);
}
void tegra_cvb_remove_opp_table(struct device *dev,
const struct cvb_table *table,
unsigned long max_freq)
{
unsigned int i;
for (i = 0; i < MAX_DVFS_FREQS; i++) {
const struct cvb_table_freq_entry *entry = &table->entries[i];
if (!entry->freq || (entry->freq > max_freq))
break;
dev_pm_opp_remove(dev, entry->freq);
}
}
+60
View File
@@ -0,0 +1,60 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Utility functions for parsing Tegra CVB voltage tables
*/
#ifndef __DRIVERS_CLK_TEGRA_CVB_H
#define __DRIVERS_CLK_TEGRA_CVB_H
#include <linux/types.h>
struct device;
#define MAX_DVFS_FREQS 40
struct rail_alignment {
int offset_uv;
int step_uv;
};
struct cvb_coefficients {
int c0;
int c1;
int c2;
};
struct cvb_table_freq_entry {
unsigned long freq;
struct cvb_coefficients coefficients;
};
struct cvb_cpu_dfll_data {
u32 tune0_low;
u32 tune0_high;
u32 tune1;
unsigned int tune_high_min_millivolts;
};
struct cvb_table {
int speedo_id;
int process_id;
int min_millivolts;
int max_millivolts;
int speedo_scale;
int voltage_scale;
struct cvb_table_freq_entry entries[MAX_DVFS_FREQS];
struct cvb_cpu_dfll_data cpu_dfll_data;
};
const struct cvb_table *
tegra_cvb_add_opp_table(struct device *dev, const struct cvb_table *cvb_tables,
size_t count, struct rail_alignment *align,
int process_id, int speedo_id, int speedo_value,
unsigned long max_freq);
void tegra_cvb_remove_opp_table(struct device *dev,
const struct cvb_table *table,
unsigned long max_freq);
#endif