forked from digetx/picasso-kernel
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Revert "ARM: tegra: remove tegra EMC scaling driver"
This reverts commit a7cbe92.
- Loading branch information
Showing
3 changed files
with
378 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,353 @@ | ||
/* | ||
* Copyright (C) 2011 Google, Inc. | ||
* | ||
* Author: | ||
* Colin Cross <ccross@android.com> | ||
* | ||
* This software is licensed under the terms of the GNU General Public | ||
* License version 2, as published by the Free Software Foundation, and | ||
* may be copied, distributed, and modified under those terms. | ||
* | ||
* 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/kernel.h> | ||
#include <linux/device.h> | ||
#include <linux/clk.h> | ||
#include <linux/err.h> | ||
#include <linux/io.h> | ||
#include <linux/module.h> | ||
#include <linux/of.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/platform_data/tegra_emc.h> | ||
#include <soc/tegra/fuse.h> | ||
|
||
#include "tegra2_emc.h" | ||
|
||
#define GMI_AD0 (1 << 4) | ||
#define GMI_AD1 (1 << 5) | ||
#define RAM_ID_MASK (GMI_AD0 | GMI_AD1) | ||
#define RAM_CODE_SHIFT 4 | ||
|
||
static bool emc_enable = true; | ||
|
||
module_param(emc_enable, bool, 0644); | ||
|
||
static struct platform_device *emc_pdev; | ||
static void __iomem *emc_regbase; | ||
|
||
static inline void emc_writel(u32 val, unsigned long addr) | ||
{ | ||
writel(val, emc_regbase + addr); | ||
} | ||
|
||
static inline u32 emc_readl(unsigned long addr) | ||
{ | ||
return readl(emc_regbase + addr); | ||
} | ||
|
||
static const unsigned long emc_reg_addr[TEGRA_EMC_NUM_REGS] = { | ||
0x2c, /* RC */ | ||
0x30, /* RFC */ | ||
0x34, /* RAS */ | ||
0x38, /* RP */ | ||
0x3c, /* R2W */ | ||
0x40, /* W2R */ | ||
0x44, /* R2P */ | ||
0x48, /* W2P */ | ||
0x4c, /* RD_RCD */ | ||
0x50, /* WR_RCD */ | ||
0x54, /* RRD */ | ||
0x58, /* REXT */ | ||
0x5c, /* WDV */ | ||
0x60, /* QUSE */ | ||
0x64, /* QRST */ | ||
0x68, /* QSAFE */ | ||
0x6c, /* RDV */ | ||
0x70, /* REFRESH */ | ||
0x74, /* BURST_REFRESH_NUM */ | ||
0x78, /* PDEX2WR */ | ||
0x7c, /* PDEX2RD */ | ||
0x80, /* PCHG2PDEN */ | ||
0x84, /* ACT2PDEN */ | ||
0x88, /* AR2PDEN */ | ||
0x8c, /* RW2PDEN */ | ||
0x90, /* TXSR */ | ||
0x94, /* TCKE */ | ||
0x98, /* TFAW */ | ||
0x9c, /* TRPAB */ | ||
0xa0, /* TCLKSTABLE */ | ||
0xa4, /* TCLKSTOP */ | ||
0xa8, /* TREFBW */ | ||
0xac, /* QUSE_EXTRA */ | ||
0x114, /* FBIO_CFG6 */ | ||
0xb0, /* ODT_WRITE */ | ||
0xb4, /* ODT_READ */ | ||
0x104, /* FBIO_CFG5 */ | ||
0x2bc, /* CFG_DIG_DLL */ | ||
0x2c0, /* DLL_XFORM_DQS */ | ||
0x2c4, /* DLL_XFORM_QUSE */ | ||
0x2e0, /* ZCAL_REF_CNT */ | ||
0x2e4, /* ZCAL_WAIT_CNT */ | ||
0x2a8, /* AUTO_CAL_INTERVAL */ | ||
0x2d0, /* CFG_CLKTRIM_0 */ | ||
0x2d4, /* CFG_CLKTRIM_1 */ | ||
0x2d8, /* CFG_CLKTRIM_2 */ | ||
}; | ||
|
||
/* Select the closest EMC rate that is higher than the requested rate */ | ||
long tegra_emc_round_rate(unsigned long rate) | ||
{ | ||
struct tegra_emc_pdata *pdata; | ||
int i; | ||
int best = -1; | ||
unsigned long distance = ULONG_MAX; | ||
|
||
if (!emc_pdev) | ||
return -EINVAL; | ||
|
||
pdata = emc_pdev->dev.platform_data; | ||
|
||
pr_debug("%s: %lu\n", __func__, rate); | ||
|
||
/* | ||
* The EMC clock rate is twice the bus rate, and the bus rate is | ||
* measured in kHz | ||
*/ | ||
rate = rate / 2 / 1000; | ||
|
||
for (i = 0; i < pdata->num_tables; i++) { | ||
if (pdata->tables[i].rate >= rate && | ||
(pdata->tables[i].rate - rate) < distance) { | ||
distance = pdata->tables[i].rate - rate; | ||
best = i; | ||
} | ||
} | ||
|
||
if (best < 0) | ||
return -EINVAL; | ||
|
||
pr_debug("%s: using %lu\n", __func__, pdata->tables[best].rate); | ||
|
||
return pdata->tables[best].rate * 2 * 1000; | ||
} | ||
|
||
/* | ||
* The EMC registers have shadow registers. When the EMC clock is updated | ||
* in the clock controller, the shadow registers are copied to the active | ||
* registers, allowing glitchless memory bus frequency changes. | ||
* This function updates the shadow registers for a new clock frequency, | ||
* and relies on the clock lock on the emc clock to avoid races between | ||
* multiple frequency changes | ||
*/ | ||
int tegra_emc_set_rate(unsigned long rate) | ||
{ | ||
struct tegra_emc_pdata *pdata; | ||
int i; | ||
int j; | ||
|
||
if (!emc_pdev) | ||
return -EINVAL; | ||
|
||
pdata = emc_pdev->dev.platform_data; | ||
|
||
/* | ||
* The EMC clock rate is twice the bus rate, and the bus rate is | ||
* measured in kHz | ||
*/ | ||
rate = rate / 2 / 1000; | ||
|
||
for (i = 0; i < pdata->num_tables; i++) | ||
if (pdata->tables[i].rate == rate) | ||
break; | ||
|
||
if (i >= pdata->num_tables) | ||
return -EINVAL; | ||
|
||
pr_debug("%s: setting to %lu\n", __func__, rate); | ||
|
||
for (j = 0; j < TEGRA_EMC_NUM_REGS; j++) | ||
emc_writel(pdata->tables[i].regs[j], emc_reg_addr[j]); | ||
|
||
emc_readl(pdata->tables[i].regs[TEGRA_EMC_NUM_REGS - 1]); | ||
|
||
return 0; | ||
} | ||
|
||
#ifdef CONFIG_OF | ||
static struct device_node *tegra_emc_ramcode_devnode(struct device_node *np, | ||
int bct_strapping) | ||
{ | ||
struct device_node *iter; | ||
u32 reg; | ||
|
||
for_each_child_of_node(np, iter) { | ||
if (of_property_read_u32(iter, "nvidia,ram-code", ®)) | ||
continue; | ||
if (reg == bct_strapping) | ||
return of_node_get(iter); | ||
} | ||
|
||
return NULL; | ||
} | ||
|
||
static struct tegra_emc_pdata *tegra_emc_dt_parse_pdata( | ||
struct platform_device *pdev) | ||
{ | ||
struct device_node *np = pdev->dev.of_node; | ||
struct device_node *tnp, *iter; | ||
struct tegra_emc_pdata *pdata; | ||
int bct_strapping; | ||
int ret, i, num_tables; | ||
|
||
if (!np) | ||
return NULL; | ||
|
||
if (of_find_property(np, "nvidia,use-ram-code", NULL)) { | ||
bct_strapping = | ||
(tegra_read_straps() & RAM_ID_MASK) >> RAM_CODE_SHIFT; | ||
tnp = tegra_emc_ramcode_devnode(np, bct_strapping); | ||
if (!tnp) | ||
dev_warn(&pdev->dev, | ||
"can't find emc table for ram-code 0x%02x\n", | ||
bct_strapping); | ||
} else | ||
tnp = of_node_get(np); | ||
|
||
if (!tnp) | ||
return NULL; | ||
|
||
num_tables = 0; | ||
for_each_child_of_node(tnp, iter) | ||
if (of_device_is_compatible(iter, "nvidia,tegra20-emc-table")) | ||
num_tables++; | ||
|
||
if (!num_tables) { | ||
pdata = NULL; | ||
goto out; | ||
} | ||
|
||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | ||
pdata->tables = devm_kzalloc(&pdev->dev, | ||
sizeof(*pdata->tables) * num_tables, | ||
GFP_KERNEL); | ||
|
||
i = 0; | ||
for_each_child_of_node(tnp, iter) { | ||
u32 prop; | ||
|
||
ret = of_property_read_u32(iter, "clock-frequency", &prop); | ||
if (ret) { | ||
dev_err(&pdev->dev, "no clock-frequency in %s\n", | ||
iter->full_name); | ||
continue; | ||
} | ||
pdata->tables[i].rate = prop; | ||
|
||
ret = of_property_read_u32_array(iter, "nvidia,emc-registers", | ||
pdata->tables[i].regs, | ||
TEGRA_EMC_NUM_REGS); | ||
if (ret) { | ||
dev_err(&pdev->dev, | ||
"malformed emc-registers property in %s\n", | ||
iter->full_name); | ||
continue; | ||
} | ||
|
||
i++; | ||
} | ||
pdata->num_tables = i; | ||
|
||
out: | ||
of_node_put(tnp); | ||
return pdata; | ||
} | ||
#else | ||
static struct tegra_emc_pdata *tegra_emc_dt_parse_pdata( | ||
struct platform_device *pdev) | ||
{ | ||
return NULL; | ||
} | ||
#endif | ||
|
||
static struct tegra_emc_pdata *tegra_emc_fill_pdata(struct platform_device *pdev) | ||
{ | ||
struct clk *c = clk_get_sys(NULL, "emc"); | ||
struct tegra_emc_pdata *pdata; | ||
unsigned long khz; | ||
int i; | ||
|
||
WARN_ON(pdev->dev.platform_data); | ||
BUG_ON(IS_ERR(c)); | ||
|
||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); | ||
pdata->tables = devm_kzalloc(&pdev->dev, sizeof(*pdata->tables), | ||
GFP_KERNEL); | ||
|
||
pdata->tables[0].rate = clk_get_rate(c) / 2 / 1000; | ||
|
||
for (i = 0; i < TEGRA_EMC_NUM_REGS; i++) | ||
pdata->tables[0].regs[i] = emc_readl(emc_reg_addr[i]); | ||
|
||
pdata->num_tables = 1; | ||
|
||
khz = pdata->tables[0].rate; | ||
dev_info(&pdev->dev, "no tables provided, using %ld kHz emc, " | ||
"%ld kHz mem\n", khz * 2, khz); | ||
|
||
return pdata; | ||
} | ||
|
||
static int tegra_emc_probe(struct platform_device *pdev) | ||
{ | ||
struct tegra_emc_pdata *pdata; | ||
struct resource *res; | ||
|
||
if (!emc_enable) { | ||
dev_err(&pdev->dev, "disabled per module parameter\n"); | ||
return -ENODEV; | ||
} | ||
|
||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
emc_regbase = devm_ioremap_resource(&pdev->dev, res); | ||
if (IS_ERR(emc_regbase)) | ||
return PTR_ERR(emc_regbase); | ||
|
||
pdata = pdev->dev.platform_data; | ||
|
||
if (!pdata) | ||
pdata = tegra_emc_dt_parse_pdata(pdev); | ||
|
||
if (!pdata) | ||
pdata = tegra_emc_fill_pdata(pdev); | ||
|
||
pdev->dev.platform_data = pdata; | ||
|
||
emc_pdev = pdev; | ||
|
||
return 0; | ||
} | ||
|
||
static struct of_device_id tegra_emc_of_match[] = { | ||
{ .compatible = "nvidia,tegra20-emc", }, | ||
{ }, | ||
}; | ||
|
||
static struct platform_driver tegra_emc_driver = { | ||
.driver = { | ||
.name = "tegra-emc", | ||
.owner = THIS_MODULE, | ||
.of_match_table = tegra_emc_of_match, | ||
}, | ||
.probe = tegra_emc_probe, | ||
}; | ||
|
||
static int __init tegra_emc_init(void) | ||
{ | ||
return platform_driver_register(&tegra_emc_driver); | ||
} | ||
device_initcall(tegra_emc_init); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* Copyright (C) 2011 Google, Inc. | ||
* | ||
* Author: | ||
* Colin Cross <ccross@android.com> | ||
* | ||
* This software is licensed under the terms of the GNU General Public | ||
* License version 2, as published by the Free Software Foundation, and | ||
* may be copied, distributed, and modified under those terms. | ||
* | ||
* 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 __MACH_TEGRA_TEGRA2_EMC_H_ | ||
#define __MACH_TEGRA_TEGRA2_EMC_H | ||
|
||
int tegra_emc_set_rate(unsigned long rate); | ||
long tegra_emc_round_rate(unsigned long rate); | ||
|
||
#endif |