Merge tag 'tegra-for-4.3-clk' of git://git.kernel.org/pub/scm/linux/kernel/git/tegra/linux into clk-next

clk: tegra: Changes for v4.3-rc1

This contains the DFLL driver needed to implement CPU frequency scaling
on Tegra.
This commit is contained in:
Stephen Boyd
2015-08-25 15:55:28 -07:00
13 changed files with 2396 additions and 9 deletions

View File

@@ -1,5 +1,6 @@
obj-y += clk.o
obj-y += clk-audio-sync.o
obj-y += clk-dfll.o
obj-y += clk-divider.o
obj-y += clk-periph.o
obj-y += clk-periph-gate.o
@@ -16,4 +17,6 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += clk-tegra20.o
obj-$(CONFIG_ARCH_TEGRA_3x_SOC) += clk-tegra30.o
obj-$(CONFIG_ARCH_TEGRA_114_SOC) += clk-tegra114.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124.o
obj-$(CONFIG_ARCH_TEGRA_124_SOC) += clk-tegra124-dfll-fcpu.o
obj-$(CONFIG_ARCH_TEGRA_132_SOC) += clk-tegra124.o
obj-y += cvb.o

1755
drivers/clk/tegra/clk-dfll.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
/*
* clk-dfll.h - prototypes and macros for the Tegra DFLL clocksource driver
* Copyright (C) 2013 NVIDIA Corporation. All rights reserved.
*
* Aleksandr Frid <afrid@nvidia.com>
* Paul Walmsley <pwalmsley@nvidia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#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>
/**
* struct tegra_dfll_soc_data - SoC-specific hooks/integration for the DFLL driver
* @opp_dev: struct device * that holds the OPP table for the DFLL
* @min_millivolts: minimum voltage (in mV) that the DFLL can operate
* @tune0_low: DFLL tuning register 0 (low voltage range)
* @tune0_high: DFLL tuning register 0 (high voltage range)
* @tune1: DFLL tuning register 1
* @assert_dvco_reset: fn ptr to place the DVCO in reset
* @deassert_dvco_reset: fn ptr to release the DVCO reset
* @set_clock_trimmers_high: fn ptr to tune clock trimmers for high voltage
* @set_clock_trimmers_low: fn ptr to tune clock trimmers for low voltage
*/
struct tegra_dfll_soc_data {
struct device *dev;
unsigned int min_millivolts;
u32 tune0_low;
u32 tune0_high;
u32 tune1;
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);
int tegra_dfll_unregister(struct platform_device *pdev);
int tegra_dfll_runtime_suspend(struct device *dev);
int tegra_dfll_runtime_resume(struct device *dev);
#endif /* __DRIVERS_CLK_TEGRA_CLK_DFLL_H */

View File

@@ -43,7 +43,9 @@ static const char *sclk_parents[] = { "clk_m", "pll_c_out1", "pll_p_out4",
static const char *cclk_g_parents[] = { "clk_m", "pll_c", "clk_32k", "pll_m",
"pll_p", "pll_p_out4", "unused",
"unused", "pll_x" };
"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",

View File

@@ -0,0 +1,166 @@
/*
* Tegra124 DFLL FCPU clock source driver
*
* Copyright (C) 2012-2014 NVIDIA Corporation. All rights reserved.
*
* Aleksandr Frid <afrid@nvidia.com>
* Paul Walmsley <pwalmsley@nvidia.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/cpu.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <soc/tegra/fuse.h>
#include "clk.h"
#include "clk-dfll.h"
#include "cvb.h"
/* Maximum CPU frequency, indexed by CPU speedo id */
static const unsigned long 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,
.alignment = {
.step_uv = 10000, /* 10mV */
},
.speedo_scale = 100,
.voltage_scale = 1000,
.cvb_table = {
{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} },
{0, { 0, 0, 0} },
},
.cpu_dfll_data = {
.tune0_low = 0x005020ff,
.tune0_high = 0x005040ff,
.tune1 = 0x00000060,
}
},
};
static int tegra124_dfll_fcpu_probe(struct platform_device *pdev)
{
int process_id, speedo_id, speedo_value;
struct tegra_dfll_soc_data *soc;
const struct cvb_table *cvb;
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 >= ARRAY_SIZE(cpu_max_freq_table)) {
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;
}
cvb = tegra_cvb_build_opp_table(tegra124_cpu_cvb_tables,
ARRAY_SIZE(tegra124_cpu_cvb_tables),
process_id, speedo_id, speedo_value,
cpu_max_freq_table[speedo_id],
soc->dev);
if (IS_ERR(cvb)) {
dev_err(&pdev->dev, "couldn't build OPP table: %ld\n",
PTR_ERR(cvb));
return PTR_ERR(cvb);
}
soc->min_millivolts = cvb->min_millivolts;
soc->tune0_low = cvb->cpu_dfll_data.tune0_low;
soc->tune0_high = cvb->cpu_dfll_data.tune0_high;
soc->tune1 = cvb->cpu_dfll_data.tune1;
return tegra_dfll_register(pdev, soc);
}
static const struct of_device_id tegra124_dfll_fcpu_of_match[] = {
{ .compatible = "nvidia,tegra124-dfll", },
{ },
};
MODULE_DEVICE_TABLE(of, tegra124_dfll_fcpu_of_match);
static const struct dev_pm_ops tegra124_dfll_pm_ops = {
SET_RUNTIME_PM_OPS(tegra_dfll_runtime_suspend,
tegra_dfll_runtime_resume, NULL)
};
static struct platform_driver tegra124_dfll_fcpu_driver = {
.probe = tegra124_dfll_fcpu_probe,
.remove = tegra_dfll_unregister,
.driver = {
.name = "tegra124-dfll",
.of_match_table = tegra124_dfll_fcpu_of_match,
.pm = &tegra124_dfll_pm_ops,
},
};
static int __init tegra124_dfll_fcpu_init(void)
{
return platform_driver_register(&tegra124_dfll_fcpu_driver);
}
module_init(tegra124_dfll_fcpu_init);
static void __exit tegra124_dfll_fcpu_exit(void)
{
platform_driver_unregister(&tegra124_dfll_fcpu_driver);
}
module_exit(tegra124_dfll_fcpu_exit);
MODULE_DESCRIPTION("Tegra124 DFLL clock source driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Aleksandr Frid <afrid@nvidia.com>");
MODULE_AUTHOR("Paul Walmsley <pwalmsley@nvidia.com>");

View File

@@ -23,6 +23,7 @@
#include <linux/export.h>
#include <linux/clk/tegra.h>
#include <dt-bindings/clock/tegra124-car.h>
#include <dt-bindings/reset/tegra124-car.h>
#include "clk.h"
#include "clk-id.h"
@@ -38,6 +39,9 @@
#define CLK_SOURCE_CSITE 0x1d4
#define CLK_SOURCE_EMC 0x19c
#define RST_DFLL_DVCO 0x2f4
#define DVFS_DFLL_RESET_SHIFT 0
#define PLLC_BASE 0x80
#define PLLC_OUT 0x84
#define PLLC_MISC2 0x88
@@ -93,6 +97,8 @@
#define PMC_PLLM_WB0_OVERRIDE 0x1dc
#define PMC_PLLM_WB0_OVERRIDE_2 0x2b0
#define CCLKG_BURST_POLICY 0x368
#define UTMIP_PLL_CFG2 0x488
#define UTMIP_PLL_CFG2_STABLE_COUNT(x) (((x) & 0xffff) << 6)
#define UTMIP_PLL_CFG2_ACTIVE_DLY_COUNT(x) (((x) & 0x3f) << 18)
@@ -125,6 +131,8 @@
#ifdef CONFIG_PM_SLEEP
static struct cpu_clk_suspend_context {
u32 clk_csite_src;
u32 cclkg_burst;
u32 cclkg_divider;
} tegra124_cpu_clk_sctx;
#endif
@@ -1318,12 +1326,22 @@ static void tegra124_cpu_clock_suspend(void)
tegra124_cpu_clk_sctx.clk_csite_src =
readl(clk_base + CLK_SOURCE_CSITE);
writel(3 << 30, clk_base + CLK_SOURCE_CSITE);
tegra124_cpu_clk_sctx.cclkg_burst =
readl(clk_base + CCLKG_BURST_POLICY);
tegra124_cpu_clk_sctx.cclkg_divider =
readl(clk_base + CCLKG_BURST_POLICY + 4);
}
static void tegra124_cpu_clock_resume(void)
{
writel(tegra124_cpu_clk_sctx.clk_csite_src,
clk_base + CLK_SOURCE_CSITE);
writel(tegra124_cpu_clk_sctx.cclkg_burst,
clk_base + CCLKG_BURST_POLICY);
writel(tegra124_cpu_clk_sctx.cclkg_divider,
clk_base + CCLKG_BURST_POLICY + 4);
}
#endif
@@ -1413,6 +1431,68 @@ static void __init tegra124_clock_apply_init_table(void)
tegra_init_from_table(tegra124_init_table, clks, TEGRA124_CLK_CLK_MAX);
}
/**
* tegra124_car_barrier - wait for pending writes to the CAR to complete
*
* Wait for any outstanding writes to the CAR MMIO space from this CPU
* to complete before continuing execution. No return value.
*/
static void tegra124_car_barrier(void)
{
readl_relaxed(clk_base + RST_DFLL_DVCO);
}
/**
* tegra124_clock_assert_dfll_dvco_reset - assert the DFLL's DVCO reset
*
* Assert the reset line of the DFLL's DVCO. No return value.
*/
void tegra124_clock_assert_dfll_dvco_reset(void)
{
u32 v;
v = readl_relaxed(clk_base + RST_DFLL_DVCO);
v |= (1 << DVFS_DFLL_RESET_SHIFT);
writel_relaxed(v, clk_base + RST_DFLL_DVCO);
tegra124_car_barrier();
}
/**
* tegra124_clock_deassert_dfll_dvco_reset - deassert the DFLL's DVCO reset
*
* Deassert the reset line of the DFLL's DVCO, allowing the DVCO to
* operate. No return value.
*/
void tegra124_clock_deassert_dfll_dvco_reset(void)
{
u32 v;
v = readl_relaxed(clk_base + RST_DFLL_DVCO);
v &= ~(1 << DVFS_DFLL_RESET_SHIFT);
writel_relaxed(v, clk_base + RST_DFLL_DVCO);
tegra124_car_barrier();
}
int tegra124_reset_assert(unsigned long id)
{
if (id == TEGRA124_RST_DFLL_DVCO)
tegra124_clock_assert_dfll_dvco_reset();
else
return -EINVAL;
return 0;
}
int tegra124_reset_deassert(unsigned long id)
{
if (id == TEGRA124_RST_DFLL_DVCO)
tegra124_clock_deassert_dfll_dvco_reset();
else
return -EINVAL;
return 0;
}
/**
* tegra132_clock_apply_init_table - initialize clocks on Tegra132 SoCs
*
@@ -1498,6 +1578,8 @@ static void __init tegra124_132_clock_init_post(struct device_node *np)
{
tegra_super_clk_gen4_init(clk_base, pmc_base, tegra124_clks,
&pll_x_params);
tegra_init_special_resets(1, tegra124_reset_assert,
tegra124_reset_deassert);
tegra_add_of_provider(np);
clks[TEGRA124_CLK_EMC] = tegra_clk_register_emc(clk_base, np,

View File

@@ -50,7 +50,6 @@
#define RST_DEVICES_L 0x004
#define RST_DEVICES_H 0x008
#define RST_DEVICES_U 0x00C
#define RST_DFLL_DVCO 0x2F4
#define RST_DEVICES_V 0x358
#define RST_DEVICES_W 0x35C
#define RST_DEVICES_X 0x28C
@@ -80,6 +79,11 @@ 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 struct tegra_clk_periph_regs periph_regs[] = {
[0] = {
.enb_reg = CLK_OUT_ENB_L,
@@ -153,19 +157,29 @@ static int tegra_clk_rst_assert(struct reset_controller_dev *rcdev,
*/
tegra_read_chipid();
writel_relaxed(BIT(id % 32),
clk_base + periph_regs[id / 32].rst_set_reg);
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 0;
return -EINVAL;
}
static int tegra_clk_rst_deassert(struct reset_controller_dev *rcdev,
unsigned long id)
{
writel_relaxed(BIT(id % 32),
clk_base + periph_regs[id / 32].rst_clr_reg);
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 0;
return -EINVAL;
}
struct tegra_clk_periph_regs *get_reg_bank(int clkid)
@@ -287,10 +301,19 @@ void __init tegra_add_of_provider(struct device_node *np)
of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data);
rst_ctlr.of_node = np;
rst_ctlr.nr_resets = periph_banks * 32;
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 __init tegra_register_devclks(struct tegra_devclk *dev_clks, int num)
{
int i;

View File

@@ -591,6 +591,9 @@ struct tegra_devclk {
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);

140
drivers/clk/tegra/cvb.c Normal file
View File

@@ -0,0 +1,140 @@
/*
* Utility functions for parsing Tegra CVB voltage tables
*
* Copyright (C) 2012-2014 NVIDIA Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#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(const struct cvb_table *d,
int speedo_value,
unsigned long max_freq,
struct device *opp_dev)
{
int i, ret, dfll_mv, min_mv, max_mv;
const struct cvb_table_freq_entry *table = NULL;
const struct rail_alignment *align = &d->alignment;
min_mv = round_voltage(d->min_millivolts, align, UP);
max_mv = round_voltage(d->max_millivolts, align, DOWN);
for (i = 0; i < MAX_DVFS_FREQS; i++) {
table = &d->cvb_table[i];
if (!table->freq || (table->freq > max_freq))
break;
/*
* FIXME after clk_round_rate/clk_determine_rate prototypes
* have been updated
*/
if (table->freq & (1<<31))
continue;
dfll_mv = get_cvb_voltage(
speedo_value, d->speedo_scale, &table->coefficients);
dfll_mv = round_cvb_voltage(dfll_mv, d->voltage_scale, align);
dfll_mv = clamp(dfll_mv, min_mv, max_mv);
ret = dev_pm_opp_add(opp_dev, table->freq, dfll_mv * 1000);
if (ret)
return ret;
}
return 0;
}
/**
* tegra_cvb_build_opp_table - build OPP table from Tegra CVB tables
* @cvb_tables: array of CVB tables
* @sz: size of the previously mentioned array
* @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_rate: highest safe clock rate
* @opp_dev: the struct device * for which the OPP table is built
*
* 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 @opp_dev. Returns a pointer to the struct cvb_table that matched
* or an ERR_PTR on failure.
*/
const struct cvb_table *tegra_cvb_build_opp_table(
const struct cvb_table *cvb_tables,
size_t sz, int process_id,
int speedo_id, int speedo_value,
unsigned long max_rate,
struct device *opp_dev)
{
int i, ret;
for (i = 0; i < sz; i++) {
const struct cvb_table *d = &cvb_tables[i];
if (d->speedo_id != -1 && d->speedo_id != speedo_id)
continue;
if (d->process_id != -1 && d->process_id != process_id)
continue;
ret = build_opp_table(d, speedo_value, max_rate, opp_dev);
return ret ? ERR_PTR(ret) : d;
}
return ERR_PTR(-EINVAL);
}

67
drivers/clk/tegra/cvb.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* Utility functions for parsing Tegra CVB voltage tables
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#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;
};
struct cvb_table {
int speedo_id;
int process_id;
int min_millivolts;
int max_millivolts;
struct rail_alignment alignment;
int speedo_scale;
int voltage_scale;
struct cvb_table_freq_entry cvb_table[MAX_DVFS_FREQS];
struct cvb_cpu_dfll_data cpu_dfll_data;
};
const struct cvb_table *tegra_cvb_build_opp_table(
const struct cvb_table *cvb_tables,
size_t sz, int process_id,
int speedo_id, int speedo_value,
unsigned long max_rate,
struct device *opp_dev);
#endif