aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-omap2/omap_hwmod.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-omap2/omap_hwmod.c')
-rw-r--r--arch/arm/mach-omap2/omap_hwmod.c376
1 files changed, 160 insertions, 216 deletions
diff --git a/arch/arm/mach-omap2/omap_hwmod.c b/arch/arm/mach-omap2/omap_hwmod.c
index 0da4f2ea76c4..3b47ded5fa0c 100644
--- a/arch/arm/mach-omap2/omap_hwmod.c
+++ b/arch/arm/mach-omap2/omap_hwmod.c
@@ -138,10 +138,10 @@
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/slab.h>
-#include <linux/bootmem.h>
#include <linux/cpu.h>
#include <linux/of.h>
#include <linux/of_address.h>
+#include <linux/bootmem.h>
#include <asm/system_misc.h>
@@ -183,6 +183,24 @@
#define MOD_CLK_MAX_NAME_LEN 32
/**
+ * struct clkctrl_provider - clkctrl provider mapping data
+ * @addr: base address for the provider
+ * @offset: base offset for the provider
+ * @clkdm: base clockdomain for provider
+ * @node: device node associated with the provider
+ * @link: list link
+ */
+struct clkctrl_provider {
+ u32 addr;
+ u16 offset;
+ struct clockdomain *clkdm;
+ struct device_node *node;
+ struct list_head link;
+};
+
+static LIST_HEAD(clkctrl_providers);
+
+/**
* struct omap_hwmod_soc_ops - fn ptrs for some SoC-specific operations
* @enable_module: function to enable a module (via MODULEMODE)
* @disable_module: function to disable a module (via MODULEMODE)
@@ -205,6 +223,8 @@ struct omap_hwmod_soc_ops {
void (*update_context_lost)(struct omap_hwmod *oh);
int (*get_context_lost)(struct omap_hwmod *oh);
int (*disable_direct_prcm)(struct omap_hwmod *oh);
+ u32 (*xlate_clkctrl)(struct omap_hwmod *oh,
+ struct clkctrl_provider *provider);
};
/* soc_ops: adapts the omap_hwmod code to the currently-booted SoC */
@@ -216,49 +236,12 @@ static LIST_HEAD(omap_hwmod_list);
/* mpu_oh: used to add/remove MPU initiator from sleepdep list */
static struct omap_hwmod *mpu_oh;
-/*
- * linkspace: ptr to a buffer that struct omap_hwmod_link records are
- * allocated from - used to reduce the number of small memory
- * allocations, which has a significant impact on performance
- */
-static struct omap_hwmod_link *linkspace;
-
-/*
- * free_ls, max_ls: array indexes into linkspace; representing the
- * next free struct omap_hwmod_link index, and the maximum number of
- * struct omap_hwmod_link records allocated (respectively)
- */
-static unsigned short free_ls, max_ls, ls_supp;
-
/* inited: set to true once the hwmod code is initialized */
static bool inited;
/* Private functions */
/**
- * _fetch_next_ocp_if - return the next OCP interface in a list
- * @p: ptr to a ptr to the list_head inside the ocp_if to return
- * @i: pointer to the index of the element pointed to by @p in the list
- *
- * Return a pointer to the struct omap_hwmod_ocp_if record
- * containing the struct list_head pointed to by @p, and increment
- * @p such that a future call to this routine will return the next
- * record.
- */
-static struct omap_hwmod_ocp_if *_fetch_next_ocp_if(struct list_head **p,
- int *i)
-{
- struct omap_hwmod_ocp_if *oi;
-
- oi = list_entry(*p, struct omap_hwmod_link, node)->ocp_if;
- *p = (*p)->next;
-
- *i = *i + 1;
-
- return oi;
-}
-
-/**
* _update_sysc_cache - return the module OCP_SYSCONFIG register, keep copy
* @oh: struct omap_hwmod *
*
@@ -728,6 +711,103 @@ static int _del_initiator_dep(struct omap_hwmod *oh, struct omap_hwmod *init_oh)
return clkdm_del_sleepdep(clkdm, init_clkdm);
}
+static const struct of_device_id ti_clkctrl_match_table[] __initconst = {
+ { .compatible = "ti,clkctrl" },
+ { }
+};
+
+static int _match_clkdm(struct clockdomain *clkdm, void *user)
+{
+ struct clkctrl_provider *provider = user;
+
+ if (clkdm_xlate_address(clkdm) == provider->addr) {
+ pr_debug("%s: Matched clkdm %s for addr %x (%s)\n", __func__,
+ clkdm->name, provider->addr,
+ provider->node->parent->name);
+ provider->clkdm = clkdm;
+
+ return -1;
+ }
+
+ return 0;
+}
+
+static int _setup_clkctrl_provider(struct device_node *np)
+{
+ const __be32 *addrp;
+ struct clkctrl_provider *provider;
+
+ provider = memblock_virt_alloc(sizeof(*provider), 0);
+ if (!provider)
+ return -ENOMEM;
+
+ addrp = of_get_address(np, 0, NULL, NULL);
+ provider->addr = (u32)of_translate_address(np, addrp);
+ provider->offset = provider->addr & 0xff;
+ provider->addr &= ~0xff;
+ provider->node = np;
+
+ clkdm_for_each(_match_clkdm, provider);
+
+ if (!provider->clkdm) {
+ pr_err("%s: nothing matched for node %s (%x)\n",
+ __func__, np->parent->name, provider->addr);
+ memblock_free_early(__pa(provider), sizeof(*provider));
+ return -EINVAL;
+ }
+
+ list_add(&provider->link, &clkctrl_providers);
+
+ return 0;
+}
+
+static int _init_clkctrl_providers(void)
+{
+ struct device_node *np;
+ int ret = 0;
+
+ for_each_matching_node(np, ti_clkctrl_match_table) {
+ ret = _setup_clkctrl_provider(np);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static u32 _omap4_xlate_clkctrl(struct omap_hwmod *oh,
+ struct clkctrl_provider *provider)
+{
+ return oh->prcm.omap4.clkctrl_offs -
+ provider->offset - provider->clkdm->clkdm_offs;
+}
+
+static struct clk *_lookup_clkctrl_clk(struct omap_hwmod *oh)
+{
+ struct clkctrl_provider *provider;
+ struct clk *clk;
+
+ if (!soc_ops.xlate_clkctrl)
+ return NULL;
+
+ list_for_each_entry(provider, &clkctrl_providers, link) {
+ if (provider->clkdm == oh->clkdm) {
+ struct of_phandle_args clkspec;
+
+ clkspec.np = provider->node;
+ clkspec.args_count = 2;
+ clkspec.args[0] = soc_ops.xlate_clkctrl(oh, provider);
+ clkspec.args[1] = 0;
+
+ clk = of_clk_get_from_provider(&clkspec);
+
+ return clk;
+ }
+ }
+
+ return NULL;
+}
+
/**
* _init_main_clk - get a struct clk * for the the hwmod's main functional clk
* @oh: struct omap_hwmod *
@@ -739,22 +819,16 @@ static int _del_initiator_dep(struct omap_hwmod *oh, struct omap_hwmod *init_oh)
static int _init_main_clk(struct omap_hwmod *oh)
{
int ret = 0;
- char name[MOD_CLK_MAX_NAME_LEN];
- struct clk *clk;
- static const char modck[] = "_mod_ck";
+ struct clk *clk = NULL;
- if (strlen(oh->name) >= MOD_CLK_MAX_NAME_LEN - strlen(modck))
- pr_warn("%s: warning: cropping name for %s\n", __func__,
- oh->name);
+ clk = _lookup_clkctrl_clk(oh);
- strlcpy(name, oh->name, MOD_CLK_MAX_NAME_LEN - strlen(modck));
- strlcat(name, modck, MOD_CLK_MAX_NAME_LEN);
-
- clk = clk_get(NULL, name);
- if (!IS_ERR(clk)) {
+ if (!IS_ERR_OR_NULL(clk)) {
+ pr_debug("%s: mapped main_clk %s for %s\n", __func__,
+ __clk_get_name(clk), oh->name);
+ oh->main_clk = __clk_get_name(clk);
oh->_clk = clk;
soc_ops.disable_direct_prcm(oh);
- oh->main_clk = kstrdup(name, GFP_KERNEL);
} else {
if (!oh->main_clk)
return 0;
@@ -794,15 +868,10 @@ static int _init_main_clk(struct omap_hwmod *oh)
static int _init_interface_clks(struct omap_hwmod *oh)
{
struct omap_hwmod_ocp_if *os;
- struct list_head *p;
struct clk *c;
- int i = 0;
int ret = 0;
- p = oh->slave_ports.next;
-
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (!os->clk)
continue;
@@ -905,19 +974,13 @@ static void _disable_optional_clocks(struct omap_hwmod *oh)
static int _enable_clocks(struct omap_hwmod *oh)
{
struct omap_hwmod_ocp_if *os;
- struct list_head *p;
- int i = 0;
pr_debug("omap_hwmod: %s: enabling clocks\n", oh->name);
if (oh->_clk)
clk_enable(oh->_clk);
- p = oh->slave_ports.next;
-
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
-
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (os->_clk && (os->flags & OCPIF_SWSUP_IDLE))
clk_enable(os->_clk);
}
@@ -939,19 +1002,13 @@ static int _enable_clocks(struct omap_hwmod *oh)
static int _disable_clocks(struct omap_hwmod *oh)
{
struct omap_hwmod_ocp_if *os;
- struct list_head *p;
- int i = 0;
pr_debug("omap_hwmod: %s: disabling clocks\n", oh->name);
if (oh->_clk)
clk_disable(oh->_clk);
- p = oh->slave_ports.next;
-
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
-
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (os->_clk && (os->flags & OCPIF_SWSUP_IDLE))
clk_disable(os->_clk);
}
@@ -1190,16 +1247,11 @@ static int _get_sdma_req_by_name(struct omap_hwmod *oh, const char *name,
static int _get_addr_space_by_name(struct omap_hwmod *oh, const char *name,
u32 *pa_start, u32 *pa_end)
{
- int i, j;
+ int j;
struct omap_hwmod_ocp_if *os;
- struct list_head *p = NULL;
bool found = false;
- p = oh->slave_ports.next;
-
- i = 0;
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (!os->addr)
return -ENOENT;
@@ -1239,18 +1291,13 @@ static int _get_addr_space_by_name(struct omap_hwmod *oh, const char *name,
static void __init _save_mpu_port_index(struct omap_hwmod *oh)
{
struct omap_hwmod_ocp_if *os = NULL;
- struct list_head *p;
- int i = 0;
if (!oh)
return;
oh->_int_flags |= _HWMOD_NO_MPU_PORT;
- p = oh->slave_ports.next;
-
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (os->user & OCP_USER_MPU) {
oh->_mpu_port = os;
oh->_int_flags &= ~_HWMOD_NO_MPU_PORT;
@@ -1393,7 +1440,7 @@ static void _enable_sysc(struct omap_hwmod *oh)
*/
if ((oh->flags & HWMOD_SET_DEFAULT_CLOCKACT) &&
(sf & SYSC_HAS_CLOCKACTIVITY))
- _set_clockactivity(oh, oh->class->sysc->clockact, &v);
+ _set_clockactivity(oh, CLOCKACT_TEST_ICLK, &v);
_write_sysconfig(v, oh);
@@ -1547,13 +1594,13 @@ static int _init_clkdm(struct omap_hwmod *oh)
* _init_clocks - clk_get() all clocks associated with this hwmod. Retrieve as
* well the clockdomain.
* @oh: struct omap_hwmod *
- * @data: not used; pass NULL
+ * @np: device_node mapped to this hwmod
*
* Called by omap_hwmod_setup_*() (after omap2_clk_init()).
* Resolves all clock names embedded in the hwmod. Returns 0 on
* success, or a negative error code on failure.
*/
-static int _init_clocks(struct omap_hwmod *oh, void *data)
+static int _init_clocks(struct omap_hwmod *oh, struct device_node *np)
{
int ret = 0;
@@ -2092,7 +2139,7 @@ static int _enable(struct omap_hwmod *oh)
r = (soc_ops.wait_target_ready) ? soc_ops.wait_target_ready(oh) :
-EINVAL;
- if (oh->clkdm)
+ if (oh->clkdm && !(oh->flags & HWMOD_CLKDM_NOAUTO))
clkdm_allow_idle(oh->clkdm);
if (!r) {
@@ -2149,7 +2196,12 @@ static int _idle(struct omap_hwmod *oh)
_idle_sysc(oh);
_del_initiator_dep(oh, mpu_oh);
- if (oh->clkdm)
+ /*
+ * If HWMOD_CLKDM_NOAUTO is set then we don't
+ * deny idle the clkdm again since idle was already denied
+ * in _enable()
+ */
+ if (oh->clkdm && !(oh->flags & HWMOD_CLKDM_NOAUTO))
clkdm_deny_idle(oh->clkdm);
if (oh->flags & HWMOD_BLOCK_WFI)
@@ -2394,24 +2446,21 @@ static int __init _init(struct omap_hwmod *oh, void *data)
{
int r, index;
struct device_node *np = NULL;
+ struct device_node *bus;
if (oh->_state != _HWMOD_STATE_REGISTERED)
return 0;
- if (of_have_populated_dt()) {
- struct device_node *bus;
-
- bus = of_find_node_by_name(NULL, "ocp");
- if (!bus)
- return -ENODEV;
+ bus = of_find_node_by_name(NULL, "ocp");
+ if (!bus)
+ return -ENODEV;
- r = of_dev_hwmod_lookup(bus, oh, &index, &np);
- if (r)
- pr_debug("omap_hwmod: %s missing dt data\n", oh->name);
- else if (np && index)
- pr_warn("omap_hwmod: %s using broken dt data from %s\n",
- oh->name, np->name);
- }
+ r = of_dev_hwmod_lookup(bus, oh, &index, &np);
+ if (r)
+ pr_debug("omap_hwmod: %s missing dt data\n", oh->name);
+ else if (np && index)
+ pr_warn("omap_hwmod: %s using broken dt data from %s\n",
+ oh->name, np->name);
r = _init_mpu_rt_base(oh, NULL, index, np);
if (r < 0) {
@@ -2420,7 +2469,7 @@ static int __init _init(struct omap_hwmod *oh, void *data)
return 0;
}
- r = _init_clocks(oh, NULL);
+ r = _init_clocks(oh, np);
if (r < 0) {
WARN(1, "omap_hwmod: %s: couldn't init clocks\n", oh->name);
return -EINVAL;
@@ -2451,15 +2500,11 @@ static int __init _init(struct omap_hwmod *oh, void *data)
static void __init _setup_iclk_autoidle(struct omap_hwmod *oh)
{
struct omap_hwmod_ocp_if *os;
- struct list_head *p;
- int i = 0;
+
if (oh->_state != _HWMOD_STATE_INITIALIZED)
return;
- p = oh->slave_ports.next;
-
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node) {
if (!os->_clk)
continue;
@@ -2657,7 +2702,6 @@ static int __init _register(struct omap_hwmod *oh)
list_add_tail(&oh->node, &omap_hwmod_list);
- INIT_LIST_HEAD(&oh->master_ports);
INIT_LIST_HEAD(&oh->slave_ports);
spin_lock_init(&oh->_lock);
lockdep_set_class(&oh->_lock, &oh->hwmod_key);
@@ -2675,49 +2719,10 @@ static int __init _register(struct omap_hwmod *oh)
}
/**
- * _alloc_links - return allocated memory for hwmod links
- * @ml: pointer to a struct omap_hwmod_link * for the master link
- * @sl: pointer to a struct omap_hwmod_link * for the slave link
- *
- * Return pointers to two struct omap_hwmod_link records, via the
- * addresses pointed to by @ml and @sl. Will first attempt to return
- * memory allocated as part of a large initial block, but if that has
- * been exhausted, will allocate memory itself. Since ideally this
- * second allocation path will never occur, the number of these
- * 'supplemental' allocations will be logged when debugging is
- * enabled. Returns 0.
- */
-static int __init _alloc_links(struct omap_hwmod_link **ml,
- struct omap_hwmod_link **sl)
-{
- unsigned int sz;
-
- if ((free_ls + LINKS_PER_OCP_IF) <= max_ls) {
- *ml = &linkspace[free_ls++];
- *sl = &linkspace[free_ls++];
- return 0;
- }
-
- sz = sizeof(struct omap_hwmod_link) * LINKS_PER_OCP_IF;
-
- *sl = NULL;
- *ml = memblock_virt_alloc(sz, 0);
-
- *sl = (void *)(*ml) + sizeof(struct omap_hwmod_link);
-
- ls_supp++;
- pr_debug("omap_hwmod: supplemental link allocations needed: %d\n",
- ls_supp * LINKS_PER_OCP_IF);
-
- return 0;
-};
-
-/**
* _add_link - add an interconnect between two IP blocks
* @oi: pointer to a struct omap_hwmod_ocp_if record
*
- * Add struct omap_hwmod_link records connecting the master IP block
- * specified in @oi->master to @oi, and connecting the slave IP block
+ * Add struct omap_hwmod_link records connecting the slave IP block
* specified in @oi->slave to @oi. This code is assumed to run before
* preemption or SMP has been enabled, thus avoiding the need for
* locking in this code. Changes to this assumption will require
@@ -2725,19 +2730,10 @@ static int __init _alloc_links(struct omap_hwmod_link **ml,
*/
static int __init _add_link(struct omap_hwmod_ocp_if *oi)
{
- struct omap_hwmod_link *ml, *sl;
-
pr_debug("omap_hwmod: %s -> %s: adding link\n", oi->master->name,
oi->slave->name);
- _alloc_links(&ml, &sl);
-
- ml->ocp_if = oi;
- list_add(&ml->node, &oi->master->master_ports);
- oi->master->masters_cnt++;
-
- sl->ocp_if = oi;
- list_add(&sl->node, &oi->slave->slave_ports);
+ list_add(&oi->node, &oi->slave->slave_ports);
oi->slave->slaves_cnt++;
return 0;
@@ -2784,45 +2780,6 @@ static int __init _register_link(struct omap_hwmod_ocp_if *oi)
return 0;
}
-/**
- * _alloc_linkspace - allocate large block of hwmod links
- * @ois: pointer to an array of struct omap_hwmod_ocp_if records to count
- *
- * Allocate a large block of struct omap_hwmod_link records. This
- * improves boot time significantly by avoiding the need to allocate
- * individual records one by one. If the number of records to
- * allocate in the block hasn't been manually specified, this function
- * will count the number of struct omap_hwmod_ocp_if records in @ois
- * and use that to determine the allocation size. For SoC families
- * that require multiple list registrations, such as OMAP3xxx, this
- * estimation process isn't optimal, so manual estimation is advised
- * in those cases. Returns -EEXIST if the allocation has already occurred
- * or 0 upon success.
- */
-static int __init _alloc_linkspace(struct omap_hwmod_ocp_if **ois)
-{
- unsigned int i = 0;
- unsigned int sz;
-
- if (linkspace) {
- WARN(1, "linkspace already allocated\n");
- return -EEXIST;
- }
-
- if (max_ls == 0)
- while (ois[i++])
- max_ls += LINKS_PER_OCP_IF;
-
- sz = sizeof(struct omap_hwmod_link) * max_ls;
-
- pr_debug("omap_hwmod: %s: allocating %d byte linkspace (%d links)\n",
- __func__, sz, max_ls);
-
- linkspace = memblock_virt_alloc(sz, 0);
-
- return 0;
-}
-
/* Static functions intended only for use in soc_ops field function pointers */
/**
@@ -3180,13 +3137,6 @@ int __init omap_hwmod_register_links(struct omap_hwmod_ocp_if **ois)
if (ois[0] == NULL) /* Empty list */
return 0;
- if (!linkspace) {
- if (_alloc_linkspace(ois)) {
- pr_err("omap_hwmod: could not allocate link space\n");
- return -ENOMEM;
- }
- }
-
i = 0;
do {
r = _register_link(ois[i]);
@@ -3398,14 +3348,10 @@ int omap_hwmod_count_resources(struct omap_hwmod *oh, unsigned long flags)
ret += _count_sdma_reqs(oh);
if (flags & IORESOURCE_MEM) {
- int i = 0;
struct omap_hwmod_ocp_if *os;
- struct list_head *p = oh->slave_ports.next;
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node)
ret += _count_ocp_if_addr_spaces(os);
- }
}
return ret;
@@ -3424,7 +3370,6 @@ int omap_hwmod_count_resources(struct omap_hwmod *oh, unsigned long flags)
int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res)
{
struct omap_hwmod_ocp_if *os;
- struct list_head *p;
int i, j, mpu_irqs_cnt, sdma_reqs_cnt, addr_cnt;
int r = 0;
@@ -3454,11 +3399,7 @@ int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res)
r++;
}
- p = oh->slave_ports.next;
-
- i = 0;
- while (i < oh->slaves_cnt) {
- os = _fetch_next_ocp_if(&p, &i);
+ list_for_each_entry(os, &oh->slave_ports, node) {
addr_cnt = _count_ocp_if_addr_spaces(os);
for (j = 0; j < addr_cnt; j++) {
@@ -3890,6 +3831,7 @@ void __init omap_hwmod_init(void)
soc_ops.update_context_lost = _omap4_update_context_lost;
soc_ops.get_context_lost = _omap4_get_context_lost;
soc_ops.disable_direct_prcm = _omap4_disable_direct_prcm;
+ soc_ops.xlate_clkctrl = _omap4_xlate_clkctrl;
} else if (cpu_is_ti814x() || cpu_is_ti816x() || soc_is_am33xx() ||
soc_is_am43xx()) {
soc_ops.enable_module = _omap4_enable_module;
@@ -3904,6 +3846,8 @@ void __init omap_hwmod_init(void)
WARN(1, "omap_hwmod: unknown SoC type\n");
}
+ _init_clkctrl_providers();
+
inited = true;
}