diff options
author | Colin Cross <ccross@android.com> | 2010-12-08 19:54:37 -0800 |
---|---|---|
committer | Colin Cross <ccross@android.com> | 2010-12-08 19:54:37 -0800 |
commit | 42907f1736fe39cdf39b5f583fcd6b9e4e257b18 (patch) | |
tree | 8f13b9c7990bb6f5c39b2869e6829c72153d0ee0 | |
parent | 2bc50e6e810abc002ccb32e46f36d20114d0b249 (diff) | |
parent | 85f7f645fb1c386b7b01044f2402587d9beda517 (diff) |
Merge branch 'linux-tegra-2.6.36' into android-tegra-2.6.36
-rw-r--r-- | arch/arm/mach-tegra/Kconfig | 4 | ||||
-rw-r--r-- | arch/arm/mach-tegra/Makefile | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/clock.c | 12 | ||||
-rw-r--r-- | arch/arm/mach-tegra/cpu-tegra.c | 21 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_clocks.c | 70 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_dvfs.c | 1 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_emc.c | 172 | ||||
-rw-r--r-- | arch/arm/mach-tegra/tegra2_emc.h | 27 | ||||
-rw-r--r-- | drivers/media/video/tegra/avp/avp_svc.c | 24 | ||||
-rw-r--r-- | drivers/video/tegra/dc/dc.c | 23 | ||||
-rw-r--r-- | drivers/video/tegra/dc/dc_priv.h | 1 |
11 files changed, 352 insertions, 4 deletions
diff --git a/arch/arm/mach-tegra/Kconfig b/arch/arm/mach-tegra/Kconfig index ef028cd62734..8269eea7ee77 100644 --- a/arch/arm/mach-tegra/Kconfig +++ b/arch/arm/mach-tegra/Kconfig @@ -75,6 +75,10 @@ config TEGRA_FIQ_DEBUGGER endif +config TEGRA_EMC_SCALING_ENABLE + bool "Enable scaling the memory frequency" + default n + config TEGRA_CPU_DVFS bool "Enable voltage scaling on Tegra CPU" default y diff --git a/arch/arm/mach-tegra/Makefile b/arch/arm/mach-tegra/Makefile index 6de6bc4c762e..9b8bdf9f5625 100644 --- a/arch/arm/mach-tegra/Makefile +++ b/arch/arm/mach-tegra/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_dvfs.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_fuse.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += suspend-t2.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_save.o +obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra2_emc.o obj-$(CONFIG_CPU_V7) += cortex-a9.o obj-$(CONFIG_ARCH_TEGRA_2x_SOC) += pinmux-t2-tables.o diff --git a/arch/arm/mach-tegra/clock.c b/arch/arm/mach-tegra/clock.c index 124af0f78782..e3936af38356 100644 --- a/arch/arm/mach-tegra/clock.c +++ b/arch/arm/mach-tegra/clock.c @@ -350,6 +350,7 @@ int clk_set_rate(struct clk *c, unsigned long rate) int ret = 0; unsigned long flags; unsigned long old_rate; + long new_rate; clk_lock_save(c, flags); @@ -363,6 +364,17 @@ int clk_set_rate(struct clk *c, unsigned long rate) if (rate > c->max_rate) rate = c->max_rate; + if (c->ops && c->ops->round_rate) { + new_rate = c->ops->round_rate(c, rate); + + if (new_rate < 0) { + ret = new_rate; + goto out; + } + + rate = new_rate; + } + if (clk_is_auto_dvfs(c) && rate > old_rate && c->refcnt > 0) { ret = tegra_dvfs_set_rate(c, rate); if (ret) diff --git a/arch/arm/mach-tegra/cpu-tegra.c b/arch/arm/mach-tegra/cpu-tegra.c index 1be794d015cf..d8c103e8964c 100644 --- a/arch/arm/mach-tegra/cpu-tegra.c +++ b/arch/arm/mach-tegra/cpu-tegra.c @@ -59,6 +59,7 @@ static struct cpufreq_frequency_table freq_table[] = { #define NUM_CPUS 2 static struct clk *cpu_clk; +static struct clk *emc_clk; static struct workqueue_struct *workqueue; @@ -115,6 +116,17 @@ static int tegra_update_cpu_speed(unsigned long rate) if (freqs.old == freqs.new) return ret; + /* + * Vote on memory bus frequency based on cpu frequency + * This sets the minimum frequency, display or avp may request higher + */ + if (rate >= 816000) + clk_set_rate(emc_clk, 600000000); /* cpu 816 MHz, emc max */ + else if (rate >= 456000) + clk_set_rate(emc_clk, 300000000); /* cpu 456 MHz, emc 150Mhz */ + else + clk_set_rate(emc_clk, 100000000); /* emc 50Mhz */ + for_each_online_cpu(freqs.cpu) cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); @@ -336,6 +348,13 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) if (IS_ERR(cpu_clk)) return PTR_ERR(cpu_clk); + emc_clk = clk_get_sys("cpu", "emc"); + if (IS_ERR(emc_clk)) { + clk_put(cpu_clk); + return PTR_ERR(emc_clk); + } + clk_enable(emc_clk); + cpufreq_frequency_table_cpuinfo(policy, freq_table); cpufreq_frequency_table_get_attr(freq_table, policy->cpu); policy->cur = tegra_getspeed(policy->cpu); @@ -358,6 +377,8 @@ static int tegra_cpu_init(struct cpufreq_policy *policy) static int tegra_cpu_exit(struct cpufreq_policy *policy) { cpufreq_frequency_table_cpuinfo(policy, freq_table); + clk_disable(emc_clk); + clk_put(emc_clk); clk_put(cpu_clk); return 0; } diff --git a/arch/arm/mach-tegra/tegra2_clocks.c b/arch/arm/mach-tegra/tegra2_clocks.c index d3bd446289dd..eef598e456f1 100644 --- a/arch/arm/mach-tegra/tegra2_clocks.c +++ b/arch/arm/mach-tegra/tegra2_clocks.c @@ -32,6 +32,7 @@ #include "clock.h" #include "fuse.h" +#include "tegra2_emc.h" #define RST_DEVICES 0x004 #define RST_DEVICES_SET 0x300 @@ -1021,6 +1022,53 @@ static struct clk_ops tegra_periph_clk_ops = { .reset = &tegra2_periph_clk_reset, }; +/* External memory controller clock ops */ +static void tegra2_emc_clk_init(struct clk *c) +{ + tegra2_periph_clk_init(c); + c->max_rate = clk_get_rate_locked(c); +} + +static long tegra2_emc_clk_round_rate(struct clk *c, unsigned long rate) +{ + long new_rate = rate; + + new_rate = tegra_emc_round_rate(new_rate); + if (new_rate < 0) + return c->max_rate; + + BUG_ON(new_rate != tegra2_periph_clk_round_rate(c, new_rate)); + + return new_rate; +} + +static int tegra2_emc_clk_set_rate(struct clk *c, unsigned long rate) +{ + int ret; + /* The Tegra2 memory controller has an interlock with the clock + * block that allows memory shadowed registers to be updated, + * and then transfer them to the main registers at the same + * time as the clock update without glitches. */ + ret = tegra_emc_set_rate(rate); + if (ret < 0) + return ret; + + ret = tegra2_periph_clk_set_rate(c, rate); + udelay(1); + + return ret; +} + +static struct clk_ops tegra_emc_clk_ops = { + .init = &tegra2_emc_clk_init, + .enable = &tegra2_periph_clk_enable, + .disable = &tegra2_periph_clk_disable, + .set_parent = &tegra2_periph_clk_set_parent, + .set_rate = &tegra2_emc_clk_set_rate, + .round_rate = &tegra2_emc_clk_round_rate, + .reset = &tegra2_periph_clk_reset, +}; + /* Clock doubler ops */ static void tegra2_clk_double_init(struct clk *c) { @@ -1157,7 +1205,8 @@ static void tegra_clk_shared_bus_update(struct clk *bus) rate = max(c->u.shared_bus_user.rate, rate); } - clk_set_rate(bus, rate); + if (rate != clk_get_rate(bus)) + clk_set_rate(bus, rate); }; static void tegra_clk_shared_bus_init(struct clk *c) @@ -1839,6 +1888,18 @@ static struct clk_mux_sel mux_clk_32k[] = { { 0, 0}, }; +static struct clk tegra_clk_emc = { + .name = "emc", + .ops = &tegra_emc_clk_ops, + .reg = 0x19c, + .max_rate = 800000000, + .inputs = mux_pllm_pllc_pllp_clkm, + .flags = MUX | DIV_U71 | PERIPH_EMC_ENB, + .u.periph = { + .clk_num = 57, + }, +}; + #define PERIPH_CLK(_name, _dev, _con, _clk_num, _reg, _max, _inputs, _flags) \ { \ .name = _name, \ @@ -1927,13 +1988,17 @@ struct clk tegra_list_clks[] = { PERIPH_CLK("usbd", "fsl-tegra-udc", NULL, 22, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ PERIPH_CLK("usb2", "tegra-ehci.1", NULL, 58, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ PERIPH_CLK("usb3", "tegra-ehci.2", NULL, 59, 0, 480000000, mux_clk_m, 0), /* requires min voltage */ - PERIPH_CLK("emc", "emc", NULL, 57, 0x19c, 800000000, mux_pllm_pllc_pllp_clkm, MUX | DIV_U71 | PERIPH_EMC_ENB), PERIPH_CLK("dsi", "dsi", NULL, 48, 0, 500000000, mux_plld, 0), /* scales with voltage */ PERIPH_CLK("csi", "tegra_camera", "csi", 52, 0, 72000000, mux_pllp_out3, 0), PERIPH_CLK("isp", "tegra_camera", "isp", 23, 0, 150000000, mux_clk_m, 0), /* same frequency as VI */ PERIPH_CLK("csus", "tegra_camera", "csus", 92, 0, 150000000, mux_clk_m, PERIPH_NO_RESET), SHARED_CLK("avp.sclk", "tegra-avp", "sclk", &tegra_clk_sclk), + SHARED_CLK("avp.emc", "tegra-avp", "emc", &tegra_clk_emc), + SHARED_CLK("cpu.emc", "cpu", "emc", &tegra_clk_emc), + SHARED_CLK("disp1.emc", "tegradc.0", "emc", &tegra_clk_emc), + SHARED_CLK("disp2.emc", "tegradc.1", "emc", &tegra_clk_emc), + SHARED_CLK("hdmi.emc", "hdmi", "emc", &tegra_clk_emc), }; #define CLK_DUPLICATE(_name, _dev, _con) \ @@ -2008,6 +2073,7 @@ struct clk *tegra_ptr_clks[] = { &tegra_clk_virtual_cpu, &tegra_clk_blink, &tegra_clk_cop, + &tegra_clk_emc, }; static void tegra2_init_one_clock(struct clk *c) diff --git a/arch/arm/mach-tegra/tegra2_dvfs.c b/arch/arm/mach-tegra/tegra2_dvfs.c index 1bc1c4dce0d2..b58a7d2ef92d 100644 --- a/arch/arm/mach-tegra/tegra2_dvfs.c +++ b/arch/arm/mach-tegra/tegra2_dvfs.c @@ -150,6 +150,7 @@ static struct dvfs dvfs_init[] = { CPU_DVFS("cpu", 3, MHZ, 730, 760, 845, 845, 1000), /* Core voltages (mV): 950, 1000, 1100, 1200, 1275 */ + CORE_DVFS("emc", 1, KHZ, 57000, 333000, 333000, 666000, 666000), #if 0 /* diff --git a/arch/arm/mach-tegra/tegra2_emc.c b/arch/arm/mach-tegra/tegra2_emc.c new file mode 100644 index 000000000000..bd4fa27b2086 --- /dev/null +++ b/arch/arm/mach-tegra/tegra2_emc.c @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.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/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> + +#include <mach/iomap.h> + +#include "tegra2_emc.h" + +#ifdef CONFIG_TEGRA_EMC_SCALING_ENABLE +static bool emc_enable = true; +#else +static bool emc_enable; +#endif +module_param(emc_enable, bool, 0644); + +static void __iomem *emc = IO_ADDRESS(TEGRA_EMC_BASE); +static const struct tegra_emc_table *tegra_emc_table; +static int tegra_emc_table_size; + +static inline void emc_writel(u32 val, unsigned long addr) +{ + writel(val, emc + addr); +} + +static inline u32 emc_readl(unsigned long addr) +{ + return readl(emc + 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) +{ + int i; + int best = -1; + unsigned long distance = ULONG_MAX; + + if (!tegra_emc_table) + return -EINVAL; + + if (!emc_enable) + return -EINVAL; + + 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 < tegra_emc_table_size; i++) { + if (tegra_emc_table[i].rate >= rate && + (tegra_emc_table[i].rate - rate) < distance) { + distance = tegra_emc_table[i].rate - rate; + best = i; + } + } + + if (best < 0) + return -EINVAL; + + pr_debug("%s: using %lu\n", __func__, tegra_emc_table[best].rate); + + return tegra_emc_table[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) +{ + int i; + int j; + + if (!tegra_emc_table) + return -EINVAL; + + /* 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 < tegra_emc_table_size; i++) + if (tegra_emc_table[i].rate == rate) + break; + + if (i >= tegra_emc_table_size) + return -EINVAL; + + pr_debug("%s: setting to %lu\n", __func__, rate); + + for (j = 0; j < TEGRA_EMC_NUM_REGS; j++) + emc_writel(tegra_emc_table[i].regs[j], emc_reg_addr[j]); + + emc_readl(tegra_emc_table[i].regs[TEGRA_EMC_NUM_REGS - 1]); + + return 0; +} + +void tegra_init_emc(const struct tegra_emc_table *table, int table_size) +{ + tegra_emc_table = table; + tegra_emc_table_size = table_size; +} diff --git a/arch/arm/mach-tegra/tegra2_emc.h b/arch/arm/mach-tegra/tegra2_emc.h new file mode 100644 index 000000000000..3515e57fd0d9 --- /dev/null +++ b/arch/arm/mach-tegra/tegra2_emc.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.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. + * + */ + +#define TEGRA_EMC_NUM_REGS 46 + +struct tegra_emc_table { + unsigned long rate; + u32 regs[TEGRA_EMC_NUM_REGS]; +}; + +int tegra_emc_set_rate(unsigned long rate); +long tegra_emc_round_rate(unsigned long rate); +void tegra_init_emc(const struct tegra_emc_table *table, int table_size); diff --git a/drivers/media/video/tegra/avp/avp_svc.c b/drivers/media/video/tegra/avp/avp_svc.c index 57cd8019c305..2eed2891e556 100644 --- a/drivers/media/video/tegra/avp/avp_svc.c +++ b/drivers/media/video/tegra/avp/avp_svc.c @@ -82,6 +82,7 @@ struct avp_svc_info { struct avp_clk clks[NUM_CLK_REQUESTS]; /* used for dvfs */ struct clk *sclk; + struct clk *emcclk; struct mutex clk_lock; @@ -352,6 +353,7 @@ static void do_svc_module_clock(struct avp_svc_info *avp_svc, aclk = &avp_svc->clks[mod->clk_req]; if (msg->enable) { if (aclk->refcnt++ == 0) { + clk_enable(avp_svc->emcclk); clk_enable(avp_svc->sclk); clk_enable(aclk->clk); } @@ -362,6 +364,7 @@ static void do_svc_module_clock(struct avp_svc_info *avp_svc, } else if (--aclk->refcnt == 0) { clk_disable(aclk->clk); clk_disable(avp_svc->sclk); + clk_disable(avp_svc->emcclk); } } mutex_unlock(&avp_svc->clk_lock); @@ -631,8 +634,9 @@ void avp_svc_stop(struct avp_svc_info *avp_svc) pr_info("%s: remote left clock '%s' on\n", __func__, aclk->mod->name); clk_disable(aclk->clk); - /* sclk was enabled once for every clock */ + /* sclk/emcclk was enabled once for every clock */ clk_disable(avp_svc->sclk); + clk_disable(avp_svc->emcclk); } aclk->refcnt = 0; } @@ -682,6 +686,21 @@ struct avp_svc_info *avp_svc_init(struct platform_device *pdev, ret = -ENOENT; goto err_get_clks; } + + avp_svc->emcclk = clk_get(&pdev->dev, "emc"); + if (IS_ERR(avp_svc->emcclk)) { + pr_err("avp_svc: Couldn't get emcclk for dvfs\n"); + ret = -ENOENT; + goto err_get_clks; + } + + /* + * The emc is a shared clock, it will be set to the highest + * requested rate from any user. Set the rate to ULONG_MAX to + * always request the max rate whenever this request is enabled + */ + clk_set_rate(avp_svc->emcclk, ULONG_MAX); + avp_svc->rpc_node = rpc_node; mutex_init(&avp_svc->clk_lock); @@ -694,6 +713,8 @@ err_get_clks: clk_put(avp_svc->clks[i].clk); if (!IS_ERR_OR_NULL(avp_svc->sclk)) clk_put(avp_svc->sclk); + if (!IS_ERR_OR_NULL(avp_svc->emcclk)) + clk_put(avp_svc->emcclk); err_alloc: return ERR_PTR(ret); } @@ -705,6 +726,7 @@ void avp_svc_destroy(struct avp_svc_info *avp_svc) for (i = 0; i < NUM_CLK_REQUESTS; i++) clk_put(avp_svc->clks[i].clk); clk_put(avp_svc->sclk); + clk_put(avp_svc->emcclk); kfree(avp_svc); } diff --git a/drivers/video/tegra/dc/dc.c b/drivers/video/tegra/dc/dc.c index 5c65e3c1a61a..3c3a4754b7dc 100644 --- a/drivers/video/tegra/dc/dc.c +++ b/drivers/video/tegra/dc/dc.c @@ -967,6 +967,7 @@ static bool _tegra_dc_enable(struct tegra_dc *dc) tegra_dc_setup_clk(dc, dc->clk); clk_enable(dc->clk); + clk_enable(dc->emc_clk); enable_irq(dc->irq); tegra_dc_init(dc); @@ -997,6 +998,7 @@ static void _tegra_dc_disable(struct tegra_dc *dc) if (dc->out_ops && dc->out_ops->disable) dc->out_ops->disable(dc); + clk_disable(dc->emc_clk); clk_disable(dc->clk); tegra_dvfs_set_rate(dc->clk, 0); @@ -1029,6 +1031,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev) { struct tegra_dc *dc; struct clk *clk; + struct clk *emc_clk; struct resource *res; struct resource *base_res; struct resource *fb_mem = NULL; @@ -1085,7 +1088,22 @@ static int tegra_dc_probe(struct nvhost_device *ndev) goto err_iounmap_reg; } + emc_clk = clk_get(&ndev->dev, "emc"); + if (IS_ERR_OR_NULL(emc_clk)) { + dev_err(&ndev->dev, "can't get emc clock\n"); + ret = -ENOENT; + goto err_put_clk; + } + + /* + * The emc is a shared clock, it will be set to the highest + * requested rate from any user. Set the rate to ULONG_MAX to + * always request the max rate whenever this request is enabled + */ + clk_set_rate(emc_clk, ULONG_MAX); + dc->clk = clk; + dc->emc_clk = emc_clk; dc->base_res = base_res; dc->base = base; dc->irq = irq; @@ -1108,7 +1126,7 @@ static int tegra_dc_probe(struct nvhost_device *ndev) dev_name(&ndev->dev), dc)) { dev_err(&ndev->dev, "request_irq %d failed\n", irq); ret = -EBUSY; - goto err_put_clk; + goto err_put_emc_clk; } /* hack to ballence enable_irq calls in _tegra_dc_enable() */ @@ -1158,6 +1176,8 @@ static int tegra_dc_probe(struct nvhost_device *ndev) err_free_irq: free_irq(irq, dc); +err_put_emc_clk: + clk_put(emc_clk); err_put_clk: clk_put(clk); err_iounmap_reg: @@ -1187,6 +1207,7 @@ static int tegra_dc_remove(struct nvhost_device *ndev) _tegra_dc_disable(dc); free_irq(dc->irq, dc); + clk_put(dc->emc_clk); clk_put(dc->clk); iounmap(dc->base); if (dc->fb_mem) diff --git a/drivers/video/tegra/dc/dc_priv.h b/drivers/video/tegra/dc/dc_priv.h index 7d0e340a6ee2..253d03f057d7 100644 --- a/drivers/video/tegra/dc/dc_priv.h +++ b/drivers/video/tegra/dc/dc_priv.h @@ -60,6 +60,7 @@ struct tegra_dc { int irq; struct clk *clk; + struct clk *emc_clk; bool enabled; |