/* * Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include "clk.h" static int cbus_switch_one(struct clk *client, struct clk *p) { int ret = 0; unsigned long old_parent_rate, new_parent_rate, current_rate; current_rate = clk_get_rate(client); old_parent_rate = clk_get_rate(clk_get_parent(client)); new_parent_rate = clk_get_rate(p); if (new_parent_rate > old_parent_rate) { u64 temp_rate; /* * In order to not overclocking the IP block when changing the * parent, we set the divider to a value which will give us an * allowed rate when the new parent is selected. */ temp_rate = DIV_ROUND_UP_ULL((u64)clk_get_rate(client) * (u64)old_parent_rate, new_parent_rate); ret = clk_set_rate(client, temp_rate); if (ret) { pr_err("failed to set %s rate to %llu: %d\n", __clk_get_name(client), temp_rate, ret); return ret; } } ret = clk_set_parent(client, p); if (ret) { pr_err("failed to set %s parent to %s: %d\n", __clk_get_name(client), __clk_get_name(p), ret); return ret; } clk_set_rate(client, current_rate); return ret; } static int cbus_backup(struct clk_hw *hw) { int ret = 0; struct tegra_clk_cbus_shared *cbus = to_clk_cbus_shared(hw); struct tegra_clk_cbus_shared *user; list_for_each_entry(user, &cbus->shared_bus_list, u.shared_bus_user.node) { struct clk *client = user->u.shared_bus_user.client; if (client && __clk_is_enabled(client) && (clk_get_parent(client) == clk_get_parent(hw->clk))) { ret = cbus_switch_one(client, cbus->shared_bus_backup); if (ret) break; } } return ret; } static void cbus_restore(struct clk_hw *hw) { struct tegra_clk_cbus_shared *user; struct tegra_clk_cbus_shared *cbus = to_clk_cbus_shared(hw); list_for_each_entry(user, &cbus->shared_bus_list, u.shared_bus_user.node) { struct clk *client = user->u.shared_bus_user.client; if (client) { cbus_switch_one(client, clk_get_parent(hw->clk)); clk_set_rate(client, user->u.shared_bus_user.rate); } } } static int clk_cbus_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { int ret; struct clk *parent; struct tegra_clk_cbus_shared *cbus = to_clk_cbus_shared(hw); if (cbus->rate_updating) return 0; if (rate == 0) return 0; cbus->rate_updating = true; parent = clk_get_parent(hw->clk); if (!parent) { cbus->rate_updating = false; return -EINVAL; } ret = clk_prepare_enable(parent); if (ret) { cbus->rate_updating = false; pr_err("%s: failed to enable %s clock: %d\n", __func__, __clk_get_name(hw->clk), ret); return ret; } ret = cbus_backup(hw); if (ret) goto out; ret = clk_set_rate(parent, rate); if (ret) { pr_err("%s: failed to set %s clock rate %lu: %d\n", __func__, __clk_get_name(hw->clk), rate, ret); goto out; } cbus_restore(hw); out: cbus->rate_updating = false; clk_disable_unprepare(parent); return ret; } static long clk_cbus_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct tegra_clk_cbus_shared *cbus = to_clk_cbus_shared(hw); unsigned long *freqs; int ret, i, num_freqs = 0; ret = tegra_dvfs_get_freqs(hw->clk, &freqs, &num_freqs); if (ret < 0 || !num_freqs) { struct clk *parent; long new_rate; parent = clk_get_parent(hw->clk); if (IS_ERR(parent)) { pr_err("no parent for %s\n", __clk_get_name(hw->clk)); return *parent_rate; } else { new_rate = clk_round_rate(parent, rate); if (new_rate < 0) return *parent_rate; else return new_rate; } } if (!cbus->min_rate) { cbus->min_rate = freqs[0]; WARN_ON(!cbus->min_rate); } rate = max(rate, cbus->min_rate); for (i = 0; i < num_freqs - 1; i++) { if (freqs[i] >= rate) break; } return freqs[i]; } static unsigned long clk_cbus_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { return clk_get_rate(clk_get_parent(hw->clk)); } static unsigned long _clk_cap_shared_bus(struct clk *c, unsigned long rate, unsigned long ceiling) { ceiling = clk_round_rate(c, ceiling); rate = min(rate, ceiling); return rate; } static int _clk_shared_bus_update(struct clk *bus) { struct tegra_clk_cbus_shared *cbus = to_clk_cbus_shared(__clk_get_hw(bus)); struct tegra_clk_cbus_shared *c; unsigned long override_rate = 0; unsigned long top_rate = 0; unsigned long rate = cbus->min_rate; unsigned long bw = 0; unsigned long iso_bw = 0; unsigned long ceiling = cbus->max_rate; unsigned long ceiling_but_iso = cbus->max_rate; unsigned long usage_flags = 0; list_for_each_entry(c, &cbus->shared_bus_list, u.shared_bus_user.node) { /* * Ignore requests from disabled floor, bw users, and * auto-users riding the bus. Always check the ceiling users * so we don't need to enable it for capping the bus rate. */ if (c->u.shared_bus_user.enabled || (c->u.shared_bus_user.mode == SHARED_CEILING)) { unsigned long request_rate = c->u.shared_bus_user.rate; usage_flags |= c->iso_usages; switch (c->u.shared_bus_user.mode) { case SHARED_ISO_BW: iso_bw += request_rate; if (iso_bw > cbus->max_rate) iso_bw = cbus->max_rate; case SHARED_BW: bw += request_rate; if (bw > cbus->max_rate) bw = cbus->max_rate; break; case SHARED_CEILING: if (request_rate) ceiling = min(request_rate, ceiling); break; case SHARED_OVERRIDE: if (override_rate == 0) override_rate = request_rate; break; case SHARED_AUTO: break; case SHARED_FLOOR: default: top_rate = max(request_rate, top_rate); rate = max(top_rate, rate); } } } /* * Keep the bus rate as its default rate when there is no SHARED_FLOOR * users enabled so we won't underrun the bus. */ if (!top_rate) rate = clk_get_rate(bus); if (!strcmp(__clk_get_name(bus), "emc_master")) { unsigned long iso_bw_min = 0; struct tegra_clk_emc *emc = to_clk_emc( __clk_get_hw(__clk_get_parent(bus))); if (!IS_ERR(emc) && emc->emc_ops->emc_apply_efficiency) { bw = emc->emc_ops->emc_apply_efficiency( bw, iso_bw, cbus->max_rate, usage_flags, &iso_bw_min); iso_bw_min = clk_round_rate(bus, iso_bw_min); } ceiling_but_iso = max(ceiling_but_iso, iso_bw_min); } rate = override_rate ? : max(rate, bw); ceiling = min(ceiling, ceiling_but_iso); ceiling = override_rate ? cbus->max_rate : ceiling; rate = _clk_cap_shared_bus(bus, rate, ceiling); return clk_set_rate(bus, rate); } static int clk_shared_prepare(struct clk_hw *hw) { int err = 0; struct tegra_clk_cbus_shared *shared = to_clk_cbus_shared(hw); shared->u.shared_bus_user.enabled = true; err = _clk_shared_bus_update(clk_get_parent(hw->clk)); if (!err && shared->u.shared_bus_user.client) err = clk_prepare_enable(shared->u.shared_bus_user.client); return err; } static void clk_shared_unprepare(struct clk_hw *hw) { struct tegra_clk_cbus_shared *shared = to_clk_cbus_shared(hw); if (shared->u.shared_bus_user.client) clk_disable_unprepare(shared->u.shared_bus_user.client); shared->u.shared_bus_user.enabled = false; _clk_shared_bus_update(clk_get_parent(hw->clk)); } static bool shared_clk_set_rate; static int clk_shared_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { struct tegra_clk_cbus_shared *shared = to_clk_cbus_shared(hw); int err; if (shared_clk_set_rate) return 0; shared_clk_set_rate = true; shared->u.shared_bus_user.rate = rate; err = _clk_shared_bus_update(clk_get_parent(hw->clk)); shared_clk_set_rate = false; return err; } static long clk_shared_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct tegra_clk_cbus_shared *shared = to_clk_cbus_shared(hw); struct tegra_clk_cbus_shared *parent_cbus; struct clk *parent; int ret; parent = clk_get_parent(hw->clk); parent_cbus = to_clk_cbus_shared(__clk_get_hw(parent)); /* * Defer rounding requests until aggregated. BW users must not be * rounded at all, others just clipped to bus range (some clients * may use round api to find limits) */ if (shared->u.shared_bus_user.mode != SHARED_BW) { if (!parent_cbus->max_rate) { ret = clk_round_rate(parent, ULONG_MAX); if (!IS_ERR_VALUE(ret)) parent_cbus->max_rate = ret; } if (rate > parent_cbus->max_rate) rate = parent_cbus->max_rate; else if (rate < parent_cbus->min_rate) rate = parent_cbus->min_rate; } return rate; } static unsigned long clk_shared_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { struct tegra_clk_cbus_shared *shared = to_clk_cbus_shared(hw); return shared->u.shared_bus_user.rate; } static int clk_system_set_rate(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate) { int err = 0; struct tegra_clk_cbus_shared *system = to_clk_cbus_shared(hw); struct clk *pclk = system->u.system.pclk->clk, *parent; if (system->rate_updating) return 0; system->rate_updating = true; if (rate >= clk_round_rate(pclk, 0) * 2) { unsigned long hclk_rate; hclk_rate = clk_get_rate(system->u.system.hclk->clk); err = clk_set_rate(pclk, DIV_ROUND_UP(hclk_rate, 2)); if (err) { pr_err("failed to set %s rate to %lu: %d\n", __clk_get_name(pclk), DIV_ROUND_UP(hclk_rate, 2), err); system->rate_updating = false; return err; } } if (rate <= system->u.system.threshold) parent = system->u.system.sclk_low->clk; else parent = system->u.system.sclk_high->clk; err = clk_set_rate(parent, rate); if (err) { pr_err("Failed to set sclk source %s to %lu\n", __clk_get_name(parent), rate); system->rate_updating = false; return err; } err = clk_set_parent(clk_get_parent(hw->clk), parent); if (err) { pr_err("Failed to switch sclk source to %s\n", __clk_get_name(parent)); system->rate_updating = false; return err; } if (rate < clk_round_rate(pclk, 0) * 2) { unsigned long hclk_rate; hclk_rate = clk_get_rate(system->u.system.hclk->clk); err = clk_set_rate(pclk, hclk_rate); if (err) pr_err("failed to set %s rate to %lu: %d\n", __clk_get_name(pclk), hclk_rate, err); } system->rate_updating = false; return err; } static long clk_system_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate) { struct clk *new_parent; unsigned long round_rate; struct tegra_clk_cbus_shared *system = to_clk_cbus_shared(hw); rate = max(rate, system->min_rate); if (rate <= system->u.system.threshold) new_parent = system->u.system.sclk_low->clk; else new_parent = system->u.system.sclk_high->clk; round_rate = clk_round_rate(new_parent, rate); if (new_parent == system->u.system.sclk_high->clk) { if (round_rate <= system->u.system.threshold) round_rate = system->u.system.threshold; } return round_rate; } static unsigned long clk_system_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) { return parent_rate; } static int clk_shared_master_prepare(struct clk_hw *hw) { struct tegra_clk_cbus_shared *master = to_clk_cbus_shared(hw); master->prepared = true; return tegra_dvfs_set_rate(hw->clk, clk_get_rate(hw->clk)); } static void clk_shared_master_unprepare(struct clk_hw *hw) { struct tegra_clk_cbus_shared *master = to_clk_cbus_shared(hw); tegra_dvfs_set_rate(hw->clk, 0); master->prepared = false; } static int clk_shared_master_is_prepared(struct clk_hw *hw) { struct tegra_clk_cbus_shared *master = to_clk_cbus_shared(hw); if (master->prepared) return true; /* In case the clock is used to determine the required voltage */ return tegra_dvfs_get_rate(hw->clk) != 0; } static const struct clk_ops tegra_clk_system_ops = { .recalc_rate = clk_system_recalc_rate, .round_rate = clk_system_round_rate, .set_rate = clk_system_set_rate, .prepare = clk_shared_master_prepare, .unprepare = clk_shared_master_unprepare, .is_prepared = clk_shared_master_is_prepared, }; static const struct clk_ops tegra_clk_cbus_ops = { .recalc_rate = clk_cbus_recalc_rate, .round_rate = clk_cbus_round_rate, .set_rate = clk_cbus_set_rate, .prepare = clk_shared_master_prepare, .unprepare = clk_shared_master_unprepare, .is_prepared = clk_shared_master_is_prepared, }; static const struct clk_ops tegra_clk_shared_ops = { .prepare = clk_shared_prepare, .unprepare = clk_shared_unprepare, .set_rate = clk_shared_set_rate, .round_rate = clk_shared_round_rate, .recalc_rate = clk_shared_recalc_rate, }; static const struct clk_ops tegra_clk_shared_master_ops = { .prepare = clk_shared_master_prepare, .unprepare = clk_shared_master_unprepare, .is_prepared = clk_shared_master_is_prepared, }; struct clk *tegra_clk_register_sbus_cmplx(const char *name, const char *parent, unsigned long flags, const char *pclk, const char *hclk, const char *sclk_low, const char *sclk_high, unsigned long threshold, unsigned long min_rate, unsigned long max_rate) { struct clk *parent_clk, *c; struct clk_init_data init; struct tegra_clk_cbus_shared *system; system = kzalloc(sizeof(*system), GFP_KERNEL); if (!system) return ERR_PTR(-ENOMEM); parent_clk = __clk_lookup(parent); if (IS_ERR(parent_clk)) { kfree(system); return parent_clk; } c = __clk_lookup(pclk); if (IS_ERR(c)) { kfree(system); return c; } system->u.system.pclk = __clk_get_hw(c); c = __clk_lookup(hclk); if (IS_ERR(c)) { kfree(system); return c; } system->u.system.hclk = __clk_get_hw(c); c = __clk_lookup(sclk_low); if (IS_ERR(c)) { kfree(system); return c; } system->u.system.sclk_low = __clk_get_hw(c); c = __clk_lookup(sclk_high); if (IS_ERR(c)) { kfree(system); return c; } system->u.system.sclk_high = __clk_get_hw(c); system->u.system.threshold = threshold; system->min_rate = min_rate; system->max_rate = max_rate; INIT_LIST_HEAD(&system->shared_bus_list); flags |= CLK_GET_RATE_NOCACHE; init.name = name; init.ops = &tegra_clk_system_ops; init.flags = flags; init.parent_names = &parent; init.num_parents = 1; system->hw.init = &init; return clk_register(NULL, &system->hw); } struct clk *tegra_clk_register_cbus(const char *name, const char *parent, unsigned long flags, const char *backup, unsigned long min_rate, unsigned long max_rate) { struct tegra_clk_cbus_shared *cbus; struct clk_init_data init; struct clk *backup_clk; cbus = kzalloc(sizeof(*cbus), GFP_KERNEL); if (!cbus) return ERR_PTR(-ENOMEM); backup_clk = __clk_lookup(backup); if (IS_ERR(backup_clk)) { kfree(cbus); return backup_clk; } cbus->shared_bus_backup = backup_clk; cbus->min_rate = min_rate; cbus->max_rate = max_rate; INIT_LIST_HEAD(&cbus->shared_bus_list); flags |= CLK_GET_RATE_NOCACHE; init.name = name; init.ops = &tegra_clk_cbus_ops; init.flags = flags; init.parent_names = &parent; init.num_parents = 1; /* Data in .init is copied by clk_register(), so stack variable OK */ cbus->hw.init = &init; return clk_register(NULL, &cbus->hw); } struct clk *tegra_clk_register_shared(const char *name, const char **parent, u8 num_parents, unsigned long flags, unsigned long usages, enum shared_bus_users_mode mode, const char *client) { struct tegra_clk_cbus_shared *shared; struct clk_init_data init; struct tegra_clk_cbus_shared *parent_cbus; struct clk *client_clk, *parent_clk; if (num_parents > 2) return ERR_PTR(-EINVAL); parent_clk = __clk_lookup(parent[0]); if (IS_ERR(parent_clk)) return parent_clk; parent_cbus = to_clk_cbus_shared(__clk_get_hw(parent_clk)); shared = kzalloc(sizeof(*shared), GFP_KERNEL); if (!shared) return ERR_PTR(-ENOMEM); if (client) { client_clk = __clk_lookup(client); if (IS_ERR(client_clk)) { kfree(shared); return client_clk; } shared->u.shared_bus_user.client = client_clk; shared->magic = TEGRA_CLK_SHARED_MAGIC; } shared->u.shared_bus_user.mode = mode; if (mode == SHARED_CEILING) shared->u.shared_bus_user.rate = parent_cbus->max_rate; else shared->u.shared_bus_user.rate = clk_get_rate(parent_clk); shared->flags = flags; shared->iso_usages = usages; if (num_parents > 1) { struct clk *c = __clk_lookup(parent[1]); if (!IS_ERR(c)) { shared->u.shared_bus_user.inputs[0] = parent_clk; shared->u.shared_bus_user.inputs[1] = c; } } shared->max_rate = parent_cbus->max_rate; INIT_LIST_HEAD(&shared->u.shared_bus_user.node); list_add_tail(&shared->u.shared_bus_user.node, &parent_cbus->shared_bus_list); flags |= CLK_GET_RATE_NOCACHE; init.name = name; init.ops = &tegra_clk_shared_ops; init.flags = flags; init.parent_names = parent; init.num_parents = 1; /* Data in .init is copied by clk_register(), so stack variable OK */ shared->hw.init = &init; return clk_register(NULL, &shared->hw); } /* * Not all shared clocks have a cbus clock as parent. The parent clock however * provides the head of the shared clock list. This clock provides a placeholder * for the head. */ struct clk *tegra_clk_register_shared_master(const char *name, const char *parent, unsigned long flags, unsigned long min_rate, unsigned long max_rate) { struct tegra_clk_cbus_shared *master; struct clk_init_data init; master = kzalloc(sizeof(*master), GFP_KERNEL); if (!master) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&master->shared_bus_list); flags |= CLK_SET_RATE_PARENT | CLK_GET_RATE_NOCACHE; init.name = name; init.ops = &tegra_clk_shared_master_ops; init.flags = flags; init.parent_names = &parent; init.num_parents = 1; master->min_rate = min_rate; master->max_rate = max_rate; /* Data in .init is copied by clk_register(), so stack variable OK */ master->hw.init = &init; return clk_register(NULL, &master->hw); }