📄 clock_34xx.c
字号:
/* * linux/arch/arm/mach-omap2/clock.c * * OMAP34XX clock framework * * Copyright (C) 2007 Texas Instruments Inc. * Karthik Dasu <karthik-dp@ti.com> * * Based on omap2 clock.c Copyright (C) 2005 Texas Instruments Inc * Richard Woodruff <r-woodruff2@ti.com> * Cleaned up and modified to use omap shared clock framework by * Tony Lindgren <tony@atomide.com> * * Based on omap1 clock.c, Copyright (C) 2004 - 2005 Nokia corporation * Written by Tuukka Tikkanen <tuukka.tikkanen@elektrobit.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/module.h>#include <linux/kernel.h>#include <linux/device.h>#include <linux/list.h>#include <linux/errno.h>#include <linux/delay.h>#include <linux/clk.h>#include <linux/cpufreq.h>#include <asm/io.h>#include <asm/arch/clock.h>#include <asm/arch/sram.h>#include "prcm-regs.h"#include "memory.h"#include "clock_34xx.h"/* #define DEBUG_CLK 1 */# define DPRINTK(fmt, args...)static struct vdd_prcm_config *curr_vdd1_prcm_set;static struct vdd_prcm_config *curr_vdd2_prcm_set;static unsigned long compute_lpj(unsigned long ref, u_int div, u_int mult){ unsigned long new_jiffy_l, new_jiffy_h; /* * Recalculate loops_per_jiffy. We do it this way to * avoid math overflow on 32-bit machines. Maybe we * should make this architecture dependent? If you have * a better way of doing this, please replace! * * new = old * mult / div */ new_jiffy_h = ref / div; new_jiffy_l = (ref % div) / 100; new_jiffy_h *= mult; new_jiffy_l = new_jiffy_l * mult / div; return new_jiffy_h + new_jiffy_l * 100;}/*------------------------------------------------------------------------- * Omap3 specific clock functions *-------------------------------------------------------------------------*//** * omap3_followparent_recalc: Makes clock rate same as parent. * @clk: Target clock. * * Makes clock rate same as parent. If required, propagates the change. */static void omap3_followparent_recalc(struct clk *clk){ followparent_recalc(clk); if (clk->flags & RATE_PROPAGATES) propagate_rate(clk);}/** * omap3_followparent_recalc: Progagates the clock rate. * @clk: Target clock. */static void omap3_propagate_rate(struct clk *clk){ propagate_rate(clk);}/** * _omap3_clk_enable: Enable OMAP3 clock. * @clk: Clock to be enabled. * * Low level function to enable specified clock. */static int _omap3_clk_enable(struct clk *clk){ int ret = 0; DPRINTK("Clk: %s\n", clk->name); if (clk->flags & ALWAYS_ENABLED) return 0; if (clk == &sys_clkout1) ret = prcm_control_external_output_clock1(PRCM_ENABLE); if (clk == &sys_clkout2) ret = prcm_control_external_output_clock2(PRCM_ENABLE); if (clk->flags & F_CLK) { ret = prcm_clock_control(clk->prcmid, FCLK, PRCM_ENABLE, PRCM_ACCESS_CHK); } if (clk->flags & I_CLK) { ret = prcm_clock_control(clk->prcmid, ICLK, PRCM_ENABLE, PRCM_ACCESS_CHK); } DPRINTK("Done Clk: %s\n", clk->name); if (ret == PRCM_FAIL) return -EINVAL; else return 0;}/** * _omap3_clk_disable: Disable OMAP3 clock. * @clk: Clock to be disabled. * * Low level function to disable specified clock. */static void _omap3_clk_disable(struct clk *clk){ DPRINTK("Clk: %s\n", clk->name); if (clk->flags & ALWAYS_ENABLED) return; if (clk == &sys_clkout1) prcm_control_external_output_clock1(PRCM_DISABLE); if (clk == &sys_clkout2) prcm_control_external_output_clock2(PRCM_DISABLE); if (clk->flags & F_CLK) { prcm_clock_control(clk->prcmid, FCLK, PRCM_DISABLE, PRCM_NO_ACCESS_CHK); } if (clk->flags & I_CLK) { prcm_clock_control(clk->prcmid, ICLK, PRCM_DISABLE, PRCM_NO_ACCESS_CHK); } DPRINTK("Done Clk: %s\n", clk->name);}/** * omap3_clk_disable: Disable OMAP3 clock. * @clk: * * Decrement the use count for specified clock. Disable the clock, if * use count becomes 0. */static void omap3_clk_disable(struct clk *clk){ DPRINTK("Clock: %s\n", clk->name); if (clk->usecount > 0 && !(--clk->usecount)) { _omap3_clk_disable(clk); if (likely((u32) clk->parent)) omap3_clk_disable(clk->parent); } DPRINTK("Clock: %d usecount: %d\n", clk->name, clk->usecount);}/** * omap3_clk_enable: Enable OMAP3 clock. * @clk: * * Enable specified clock, if it isn't already. Increment the use count. */static int omap3_clk_enable(struct clk *clk){ int ret = 0; DPRINTK("Clock: %s\n", clk->name); if (clk->usecount++ == 0) { if (likely((u32) clk->parent)) ret = omap3_clk_enable(clk->parent); if (unlikely(ret != 0)) { clk->usecount--; return ret; } ret = _omap3_clk_enable(clk); if (unlikely(ret != 0) && clk->parent) { omap3_clk_disable(clk->parent); clk->usecount--; } } DPRINTK("Clock: %d usecount: %d\n", clk->name, clk->usecount); return ret;}/* Calls appropriate PRCM API to calculate rate of clock */static void omap3_clk_recalc(struct clk *clk){ u32 parent_rate, divider, ret, rate; parent_rate = clk->parent->rate; ret = PRCM_PASS; DPRINTK("Clock name: %s\n", clk->name); if (clk == &sys_ck) { clk->rate = prcm_get_system_clock_speed() * 1000; ret = PRCM_PASS; } if (clk == &mpu_ck) { ret = prcm_get_processor_speed(clk->prcmid, &rate); clk->rate = rate * 1000; } if (clk == &iva2_ck) { ret = prcm_get_processor_speed(clk->prcmid, &rate); clk->rate = rate * 1000; } if (clk->flags & DPLL_OUTPUT) { ret = prcm_get_dpll_rate(clk->prcmid, &rate); if (ret == PRCM_PASS) clk->rate = rate * 1000; } if (clk->flags & RATE_CKCTL) { ret = prcm_clksel_get_divider(clk->prcmid, ÷r); DPRINTK("Divider: %d\n", divider); if (ret == PRCM_PASS) clk->rate = clk->parent->rate / divider; } if (ret != PRCM_PASS) printk(KERN_ERR "Error in clk_recalc: %d,%s\n", ret, clk->name); DPRINTK("Rate: %lu\n", clk->rate); if (clk->flags & RATE_PROPAGATES) propagate_rate(clk);}/* Given a clock and a rate apply a clock specific rounding function *//* This function should be called only for those clocks which have * dividers and which are not part of a clock configuration. In case * it is called for these clocks, it returns the current rate of the clocks */static long omap3_clk_round_rate(struct clk *tclk, unsigned long rate){ u32 new_div = 0, mnoutput; int ret; int valid_rate, parent_rate; DPRINTK("Clock name: %s\n", tclk->name); if (tclk->flags & RATE_FIXED) return tclk->rate; /* For external mcbsp clock node, rate is supposed to be set by * the corresponding device driver. So round rate will return * the rate that is supposed to be set */ if (tclk == &ext_mcbsp_ck) { return rate; } if ((tclk->flags & VDD1_CONFIG_PARTICIPANT) || (tclk->flags & VDD2_CONFIG_PARTICIPANT)) /* If the clock is part of a clock configuration, it cannot be changed on the fly */ return tclk->rate; if (tclk->flags & DPLL_OUTPUT) { /* The only clocks for which DPLL OUTPUT can be changed on * the fly (by changing only MX dividers) are: * PRCM_DPLL3_M3X2_CLK, PRCM_DPLL4_M2X2_CLK, * PRCM_DPLL4_M3X2_CLK, PRCM_DPLL4_M4X2_CLK, * PRCM_DPLL4_M5X2_CLK, PRCM_DPLL4_M6X2_CLK */ ret = prcm_get_dpll_mn_output(tclk->prcmid, &mnoutput); /*mnoutput has CLKOUT = (Freq*m)/n+1 in case dpll is locked *or bypass clock if it is not locked */ if (ret != PRCM_PASS) /* Rate cannot be changed. Return original rate*/ return tclk->rate; ret = prcm_clksel_round_rate(tclk->prcmid, (2 * mnoutput), (rate / 1000), &new_div); if (ret == PRCM_PASS) { valid_rate = (2 * mnoutput * 1000) / new_div; DPRINTK("Valid rate: %d\n", valid_rate); return valid_rate; } else /* No acceptable divider - return original rate */ return tclk->rate; } if (tclk->flags & RATE_CKCTL) { parent_rate = tclk->parent->rate; ret = prcm_clksel_round_rate(tclk->prcmid, parent_rate, rate, &new_div); if (ret == PRCM_PASS) return parent_rate / new_div; else /* No acceptable divider - return original rate */ return tclk->rate; } if (tclk->round_rate != 0) return tclk->round_rate(tclk, rate); return tclk->rate;}/* Set the clock rate for a clock source */static int omap3_clk_set_rate(struct clk *clk, unsigned long rate){ int ret = -EINVAL; u32 validrate, parent_rate, mnoutput; u32 new_div = 0; DPRINTK("Clock name: %s\n", clk->name); /* For external mcbsp clock, the driver can set the rate using*/ /* clk_set_rate API */ if (clk == &ext_mcbsp_ck) { clk->rate = rate; clk->recalc(clk); return 0; } if (!(clk->flags & VDD1_CONFIG_PARTICIPANT) && !(clk->flags & VDD2_CONFIG_PARTICIPANT)) { if (clk->flags & DPLL_OUTPUT) { ret = prcm_get_dpll_mn_output(clk->prcmid, &mnoutput); /*mnoutput has CLKOUT = (Freq*m)/n+1 in case dpll *is locked or bypass clock if it is not locked */ if (ret != PRCM_PASS) /* Signal error */ return (-EINVAL); ret = prcm_clksel_round_rate(clk->prcmid, (2 * mnoutput), (rate / 1000), &new_div); if (ret == PRCM_PASS) { validrate = (2 * mnoutput * 1000) / new_div; DPRINTK("Valid rate: %d\n", validrate); if (validrate != rate) return (-EINVAL); } else /* No acceptable divider - signal error */ return (-EINVAL); ret = prcm_configure_dpll_divider(clk->prcmid, new_div); } else if (clk->flags & RATE_CKCTL) { parent_rate = clk->parent->rate; ret = prcm_clksel_round_rate(clk->prcmid, parent_rate, rate, &new_div); if (ret == PRCM_PASS) { validrate = parent_rate / new_div; DPRINTK("Valid rate: %d\n", validrate); if (validrate != rate) return (ret); } else return (-EINVAL); ret = prcm_clksel_set_divider(clk->prcmid, new_div); if (ret != PRCM_PASS)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -