/* * clkt_clksel.c - OMAP2/3/4 clksel clock functions * * Copyright (C) 2005-2008 Texas Instruments, Inc. * Copyright (C) 2004-2010 Nokia Corporation * * Contacts: * Richard Woodruff * Paul Walmsley * * 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. * * * clksel clocks are clocks that do not have a fixed parent, or that * can divide their parent's rate, or possibly both at the same time, based * on the contents of a hardware register bitfield. * * All of the various mux and divider settings can be encoded into * struct clksel* data structures, and then these can be autogenerated * from some hardware database for each new chip generation. This * should avoid the need to write, review, and validate a lot of new * clock code for each new chip, since it can be exported from the SoC * design flow. This is now done on OMAP4. * * The fusion of mux and divider clocks is a software creation. In * hardware reality, the multiplexer (parent selection) and the * divider exist separately. XXX At some point these clksel clocks * should be split into "divider" clocks and "mux" clocks to better * match the hardware. * * (The name "clksel" comes from the name of the corresponding * register field in the OMAP2/3 family of SoCs.) * * XXX Currently these clocks are only used in the OMAP2/3/4 code, but * many of the OMAP1 clocks should be convertible to use this * mechanism. */ #undef DEBUG #include #include #include #include #include #include "clock.h" /* Private functions */ /** * _get_clksel_by_parent() - return clksel struct for a given clk & parent * @clk: OMAP struct clk ptr to inspect * @src_clk: OMAP struct clk ptr of the parent clk to search for * * Scan the struct clksel array associated with the clock to find * the element associated with the supplied parent clock address. * Returns a pointer to the struct clksel on success or NULL on error. */ static const struct clksel *_get_clksel_by_parent(struct clk_hw_omap *clk, struct clk *src_clk) { const struct clksel *clks; if (!src_clk) return NULL; for (clks = clk->clksel; clks->parent; clks++) if (clks->parent == src_clk) break; /* Found the requested parent */ if (!clks->parent) { /* This indicates a data problem */ WARN(1, "clock: %s: could not find parent clock %s in clksel array\n", __clk_get_name(clk->hw.clk), __clk_get_name(src_clk)); return NULL; } return clks; } /** * _write_clksel_reg() - program a clock's clksel register in hardware * @clk: struct clk * to program * @v: clksel bitfield value to program (with LSB at bit 0) * * Shift the clksel register bitfield value @v to its appropriate * location in the clksel register and write it in. This function * will ensure that the write to the clksel_reg reaches its * destination before returning -- important since PRM and CM register * accesses can be quite slow compared to ARM cycles -- but does not * take into account any time the hardware might take to switch the * clock source. */ static void _write_clksel_reg(struct clk_hw_omap *clk, u32 field_val) { u32 v; v = __raw_readl(clk->clksel_reg); v &= ~clk->clksel_mask; v |= field_val << __ffs(clk->clksel_mask); __raw_writel(v, clk->clksel_reg); v = __raw_readl(clk->clksel_reg); /* OCP barrier */ } /** * _clksel_to_divisor() - turn clksel field value into integer divider * @clk: OMAP struct clk to use * @field_val: register field value to find * * Given a struct clk of a rate-selectable clksel clock, and a register field * value to search for, find the corresponding clock divisor. The register * field value should be pre-masked and shifted down so the LSB is at bit 0 * before calling. Returns 0 on error or returns the actual integer divisor * upon success. */ static u32 _clksel_to_divisor(struct clk_hw_omap *clk, u32 field_val) { const struct clksel *clks; const struct clksel_rate *clkr; struct clk *parent; parent = __clk_get_parent(clk->hw.clk); clks = _get_clksel_by_parent(clk, parent); if (!clks) return 0; for (clkr = clks->rates; clkr->div; clkr++) { if (!(clkr->flags & cpu_mask)) continue; if (clkr->val == field_val) break; } if (!clkr->div) { /* This indicates a data error */ WARN(1, "clock: %s: could not find fieldval %d for parent %s\n", __clk_get_name(clk->hw.clk), field_val, __clk_get_name(parent)); return 0; } return clkr->div; } /** * _divisor_to_clksel() - turn clksel integer divisor into a field value * @clk: OMAP struct clk to use * @div: integer divisor to search for * * Given a struct clk of a rate-selectable clksel clock, and a clock * divisor, find the corresponding register field value. Returns the * register field value _before_ left-shifting (i.e., LSB is at bit * 0); or returns 0xFFFFFFFF (~0) upon error. */ static u32 _divisor_to_clksel(struct clk_hw_omap *clk, u32 div) { const struct clksel *clks; const struct clksel_rate *clkr; struct clk *parent; /* should never happen */ WARN_ON(div == 0); parent = __clk_get_parent(clk->hw.clk); clks = _get_clksel_by_parent(clk, parent); if (!clks) return ~0; for (clkr = clks->rates; clkr->div; clkr++) { if (!(clkr->flags & cpu_mask)) continue; if (clkr->div == div) break; } if (!clkr->div) { pr_err("clock: %s: could not find divisor %d for parent %s\n", __clk_get_name(clk->hw.clk), div, __clk_get_name(parent)); return ~0; } return clkr->val; } /** * _read_divisor() - get current divisor applied to parent clock (from hdwr) * @clk: OMAP struct clk to use. * * Read the current divisor register value for @clk that is programmed * into the hardware, convert it into the actual divisor value, and * return it; or return 0 on error. */ static u32 _read_divisor(struct clk_hw_omap *clk) {