diff options
Diffstat (limited to 'drivers/clk/msm/msm-clock-controller.c')
-rw-r--r-- | drivers/clk/msm/msm-clock-controller.c | 755 |
1 files changed, 755 insertions, 0 deletions
diff --git a/drivers/clk/msm/msm-clock-controller.c b/drivers/clk/msm/msm-clock-controller.c new file mode 100644 index 000000000000..cbe52555264d --- /dev/null +++ b/drivers/clk/msm/msm-clock-controller.c @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2014, 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only 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. + */ + +#define pr_fmt(fmt) "msmclock: %s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/regulator/consumer.h> +#include <linux/of.h> +#include <linux/hashtable.h> + +#include <linux/clk/msm-clk-provider.h> +#include <soc/qcom/msm-clock-controller.h> +#include <soc/qcom/clock-rpm.h> + +/* Protects list operations */ +static DEFINE_MUTEX(msmclk_lock); +static LIST_HEAD(msmclk_parser_list); +static u32 msmclk_debug; + +struct hitem { + struct hlist_node list; + phandle key; + void *ptr; +}; + +int of_property_count_phandles(struct device_node *np, char *propname) +{ + const __be32 *phandle; + int size; + + phandle = of_get_property(np, propname, &size); + return phandle ? (size / sizeof(*phandle)) : -EINVAL; +} +EXPORT_SYMBOL(of_property_count_phandles); + +int of_property_read_phandle_index(struct device_node *np, char *propname, + int index, phandle *p) +{ + const __be32 *phandle; + int size; + + phandle = of_get_property(np, propname, &size); + if ((!phandle) || (size < sizeof(*phandle) * (index + 1))) + return -EINVAL; + + *p = be32_to_cpup(phandle + index); + return 0; +} +EXPORT_SYMBOL(of_property_read_phandle_index); + +static int generic_vdd_parse_regulators(struct device *dev, + struct clk_vdd_class *vdd, struct device_node *np) +{ + int num_regulators, i, rc; + char *name = "qcom,regulators"; + + num_regulators = of_property_count_phandles(np, name); + if (num_regulators <= 0) { + dt_prop_err(np, name, "missing dt property\n"); + return -EINVAL; + } + + vdd->regulator = devm_kzalloc(dev, + sizeof(*vdd->regulator) * num_regulators, + GFP_KERNEL); + if (!vdd->regulator) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + for (i = 0; i < num_regulators; i++) { + phandle p; + rc = of_property_read_phandle_index(np, name, i, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + vdd->regulator[i] = msmclk_parse_phandle(dev, p); + if (IS_ERR(vdd->regulator[i])) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(vdd->regulator[i]); + } + } + + vdd->num_regulators = num_regulators; + return 0; +} + +static int generic_vdd_parse_levels(struct device *dev, + struct clk_vdd_class *vdd, struct device_node *np) +{ + int len, rc; + char *name = "qcom,uV-levels"; + + if (!of_find_property(np, name, &len)) { + dt_prop_err(np, name, "missing dt property\n"); + return -EINVAL; + } + + len /= sizeof(u32); + if (len % vdd->num_regulators) { + dt_err(np, "mismatch beween qcom,uV-levels and qcom,regulators dt properties\n"); + return -EINVAL; + } + + vdd->num_levels = len / vdd->num_regulators; + vdd->vdd_uv = devm_kzalloc(dev, len * sizeof(*vdd->vdd_uv), + GFP_KERNEL); + vdd->level_votes = devm_kzalloc(dev, + vdd->num_levels * sizeof(*vdd->level_votes), + GFP_KERNEL); + + if (!vdd->vdd_uv || !vdd->level_votes) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(np, name, vdd->vdd_uv, + vdd->num_levels * vdd->num_regulators); + if (rc) { + dt_prop_err(np, name, "unable to read u32 array\n"); + return -EINVAL; + } + + /* Optional Property */ + name = "qcom,uA-levels"; + if (!of_find_property(np, name, &len)) + return 0; + + len /= sizeof(u32); + if (len / vdd->num_regulators != vdd->num_levels) { + dt_err(np, "size of qcom,uA-levels and qcom,uV-levels must match\n"); + return -EINVAL; + } + + vdd->vdd_ua = devm_kzalloc(dev, len * sizeof(*vdd->vdd_ua), + GFP_KERNEL); + if (!vdd->vdd_ua) { + dt_err(np, "memory alloc failure\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(np, name, vdd->vdd_ua, + vdd->num_levels * vdd->num_regulators); + if (rc) { + dt_prop_err(np, name, "unable to read u32 array\n"); + return -EINVAL; + } + + return 0; +} + +static void *simple_vdd_class_dt_parser(struct device *dev, + struct device_node *np) +{ + struct clk_vdd_class *vdd; + int rc = 0; + + vdd = devm_kzalloc(dev, sizeof(*vdd), GFP_KERNEL); + if (!vdd) { + dev_err(dev, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + + mutex_init(&vdd->lock); + vdd->class_name = np->name; + + rc = generic_vdd_parse_regulators(dev, vdd, np); + rc |= generic_vdd_parse_levels(dev, vdd, np); + if (rc) { + dt_err(np, "unable to read vdd_class\n"); + return ERR_PTR(rc); + } + + return vdd; +} +MSMCLK_PARSER(simple_vdd_class_dt_parser, "qcom,simple-vdd-class", 0); + +static int generic_clk_parse_parents(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + phandle p; + char *name = "qcom,parent"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->parent = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->parent)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->parent); + } + + return 0; +} + +static int generic_clk_parse_vdd(struct device *dev, struct clk *c, + struct device_node *np) +{ + phandle p; + int rc; + char *name = "qcom,supply-group"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->vdd_class = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->vdd_class)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->vdd_class); + } + + return 0; +} + +static int generic_clk_parse_flags(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + char *name = "qcom,clk-flags"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_u32(np, name, &c->flags); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + return 0; +} + +static int generic_clk_parse_fmax(struct device *dev, struct clk *c, + struct device_node *np) +{ + u32 prop_len, i; + int rc; + char *name = "qcom,clk-fmax"; + + /* This property is optional */ + if (!of_find_property(np, name, &prop_len)) + return 0; + + if (!c->vdd_class) { + dt_err(np, "both qcom,clk-fmax and qcom,supply-group must be defined\n"); + return -EINVAL; + } + + prop_len /= sizeof(u32); + if (prop_len % 2) { + dt_prop_err(np, name, "bad length\n"); + return -EINVAL; + } + + /* Value at proplen - 2 is the index of the last entry in fmax array */ + rc = of_property_read_u32_index(np, name, prop_len - 2, &c->num_fmax); + c->num_fmax += 1; + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + c->fmax = devm_kzalloc(dev, sizeof(*c->fmax) * c->num_fmax, GFP_KERNEL); + if (!c->fmax) { + dev_err(dev, "memory alloc failure\n"); + return -ENOMEM; + } + + for (i = 0; i < prop_len; i += 2) { + u32 level, value; + rc = of_property_read_u32_index(np, name, i, &level); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + rc = of_property_read_u32_index(np, name, i + 1, &value); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + + if (level >= c->num_fmax) { + dt_prop_err(np, name, "must be sorted\n"); + return -EINVAL; + } + c->fmax[level] = value; + } + + return 0; +} + +static int generic_clk_add_lookup_tbl_entry(struct device *dev, struct clk *c) +{ + struct msmclk_data *drv = dev_get_drvdata(dev); + struct clk_lookup *cl; + + if (drv->clk_tbl_size >= drv->max_clk_tbl_size) { + dev_err(dev, "child node count should be > clock_count?\n"); + return -EINVAL; + } + + cl = drv->clk_tbl + drv->clk_tbl_size; + cl->clk = c; + drv->clk_tbl_size++; + return 0; +} + +static int generic_clk_parse_depends(struct device *dev, struct clk *c, + struct device_node *np) +{ + phandle p; + int rc; + char *name = "qcom,depends"; + + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_phandle_index(np, name, 0, &p); + if (rc) { + dt_prop_err(np, name, "unable to read phandle\n"); + return rc; + } + + c->depends = msmclk_parse_phandle(dev, p); + if (IS_ERR(c->depends)) { + dt_prop_err(np, name, "hashtable lookup failed\n"); + return PTR_ERR(c->depends); + } + + return 0; +} + +static int generic_clk_parse_init_config(struct device *dev, struct clk *c, + struct device_node *np) +{ + int rc; + u32 temp; + char *name = "qcom,always-on"; + + c->always_on = of_property_read_bool(np, name); + + name = "qcom,config-rate"; + /* This property is optional */ + if (!of_find_property(np, name, NULL)) + return 0; + + rc = of_property_read_u32(np, name, &temp); + if (rc) { + dt_prop_err(np, name, "unable to read u32\n"); + return rc; + } + c->init_rate = temp; + + return rc; +} + +void *msmclk_generic_clk_init(struct device *dev, struct device_node *np, + struct clk *c) +{ + int rc; + + /* CLK_INIT macro */ + spin_lock_init(&c->lock); + mutex_init(&c->prepare_lock); + INIT_LIST_HEAD(&c->children); + INIT_LIST_HEAD(&c->siblings); + INIT_LIST_HEAD(&c->list); + c->dbg_name = np->name; + + rc = generic_clk_add_lookup_tbl_entry(dev, c); + rc |= generic_clk_parse_flags(dev, c, np); + rc |= generic_clk_parse_parents(dev, c, np); + rc |= generic_clk_parse_vdd(dev, c, np); + rc |= generic_clk_parse_fmax(dev, c, np); + rc |= generic_clk_parse_depends(dev, c, np); + rc |= generic_clk_parse_init_config(dev, c, np); + + if (rc) { + dt_err(np, "unable to read clk\n"); + return ERR_PTR(-EINVAL); + } + + return c; +} + +static struct msmclk_parser *msmclk_parser_lookup(struct device_node *np) +{ + struct msmclk_parser *item; + list_for_each_entry(item, &msmclk_parser_list, list) { + if (of_device_is_compatible(np, item->compatible)) + return item; + } + return NULL; +} +void msmclk_parser_register(struct msmclk_parser *item) +{ + mutex_lock(&msmclk_lock); + list_add(&item->list, &msmclk_parser_list); + mutex_unlock(&msmclk_lock); +} + +static int msmclk_htable_add(struct device *dev, void *result, phandle key); + +void *msmclk_parse_dt_node(struct device *dev, struct device_node *np) +{ + struct msmclk_parser *parser; + phandle key; + void *result; + int rc; + + key = np->phandle; + result = msmclk_lookup_phandle(dev, key); + if (!result) + return ERR_PTR(-EINVAL); + + if (!of_device_is_available(np)) { + dt_err(np, "node is disabled\n"); + return ERR_PTR(-EINVAL); + } + + parser = msmclk_parser_lookup(np); + if (IS_ERR_OR_NULL(parser)) { + dt_err(np, "no parser found\n"); + return ERR_PTR(-EINVAL); + } + + /* This may return -EPROBE_DEFER */ + result = parser->parsedt(dev, np); + if (IS_ERR(result)) { + dt_err(np, "parsedt failed"); + return result; + } + + rc = msmclk_htable_add(dev, result, key); + if (rc) + return ERR_PTR(rc); + + return result; +} + +void *msmclk_parse_phandle(struct device *dev, phandle key) +{ + struct hitem *item; + struct device_node *np; + struct msmclk_data *drv = dev_get_drvdata(dev); + + /* + * the default phandle value is 0. Since hashtable keys must + * be unique, reject the default value. + */ + if (!key) + return ERR_PTR(-EINVAL); + + hash_for_each_possible(drv->htable, item, list, key) { + if (item->key == key) + return item->ptr; + } + + np = of_find_node_by_phandle(key); + if (!np) + return ERR_PTR(-EINVAL); + + return msmclk_parse_dt_node(dev, np); +} +EXPORT_SYMBOL(msmclk_parse_phandle); + +void *msmclk_lookup_phandle(struct device *dev, phandle key) +{ + struct hitem *item; + struct msmclk_data *drv = dev_get_drvdata(dev); + + hash_for_each_possible(drv->htable, item, list, key) { + if (item->key == key) + return item->ptr; + } + + return ERR_PTR(-EINVAL); +} +EXPORT_SYMBOL(msmclk_lookup_phandle); + +static int msmclk_htable_add(struct device *dev, void *data, phandle key) +{ + struct hitem *item; + struct msmclk_data *drv = dev_get_drvdata(dev); + + /* + * If there are no phandle references to a node, key == 0. However, if + * there is a second node like this, both will have key == 0. This + * violates the requirement that hashtable keys be unique. Skip it. + */ + if (!key) + return 0; + + if (!IS_ERR(msmclk_lookup_phandle(dev, key))) { + struct device_node *np = of_find_node_by_phandle(key); + dev_err(dev, "attempt to add duplicate entry for %s\n", + np ? np->name : "NULL"); + return -EINVAL; + } + + item = devm_kzalloc(dev, sizeof(*item), GFP_KERNEL); + if (!item) { + dev_err(dev, "memory alloc failure\n"); + return -ENOMEM; + } + + INIT_HLIST_NODE(&item->list); + item->key = key; + item->ptr = data; + + hash_add(drv->htable, &item->list, key); + return 0; +} + +/* + * Currently, regulators are the only elements capable of probe deferral. + * Check them first to handle probe deferal efficiently. +*/ +static int get_ext_regulators(struct device *dev) +{ + int num_strings, i, rc; + struct device_node *np; + void *item; + char *name = "qcom,regulator-names"; + + np = dev->of_node; + /* This property is optional */ + num_strings = of_property_count_strings(np, name); + if (num_strings <= 0) + return 0; + + for (i = 0; i < num_strings; i++) { + const char *str; + char buf[50]; + phandle key; + + rc = of_property_read_string_index(np, name, i, &str); + if (rc) { + dt_prop_err(np, name, "unable to read string\n"); + return rc; + } + + item = devm_regulator_get(dev, str); + if (IS_ERR(item)) { + dev_err(dev, "Failed to get regulator: %s\n", str); + return PTR_ERR(item); + } + + snprintf(buf, ARRAY_SIZE(buf), "%s-supply", str); + rc = of_property_read_phandle_index(np, buf, 0, &key); + if (rc) { + dt_prop_err(np, buf, "unable to read phandle\n"); + return rc; + } + + rc = msmclk_htable_add(dev, item, key); + if (rc) + return rc; + } + return 0; +} + +static struct clk *msmclk_clk_get(struct of_phandle_args *clkspec, void *data) +{ + phandle key; + struct clk *c = ERR_PTR(-ENOENT); + + key = clkspec->args[0]; + c = msmclk_lookup_phandle(data, key); + + if (!IS_ERR(c) && !(c->flags & CLKFLAG_INIT_DONE)) + return ERR_PTR(-EPROBE_DEFER); + + return c; +} + +static void *regulator_dt_parser(struct device *dev, struct device_node *np) +{ + dt_err(np, "regulators should be handled in probe()"); + return ERR_PTR(-EINVAL); +} +MSMCLK_PARSER(regulator_dt_parser, "qcom,rpm-smd-regulator", 0); + +static void *msmclk_dt_parser(struct device *dev, struct device_node *np) +{ + dt_err(np, "calling into other clock controllers isn't allowed"); + return ERR_PTR(-EINVAL); +} +MSMCLK_PARSER(msmclk_dt_parser, "qcom,msm-clock-controller", 0); + +static struct msmclk_data *msmclk_drv_init(struct device *dev) +{ + struct msmclk_data *drv; + size_t size; + + drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL); + if (!drv) { + dev_err(dev, "memory alloc failure\n"); + return ERR_PTR(-ENOMEM); + } + dev_set_drvdata(dev, drv); + + drv->dev = dev; + INIT_LIST_HEAD(&drv->list); + + /* This overestimates size */ + drv->max_clk_tbl_size = of_get_child_count(dev->of_node); + size = sizeof(*drv->clk_tbl) * drv->max_clk_tbl_size; + drv->clk_tbl = devm_kzalloc(dev, size, GFP_KERNEL); + if (!drv->clk_tbl) { + dev_err(dev, "memory alloc failure clock table size %zu\n", + size); + return ERR_PTR(-ENOMEM); + } + + hash_init(drv->htable); + return drv; +} + +static int msmclk_probe(struct platform_device *pdev) +{ + struct resource *res; + struct device *dev; + struct msmclk_data *drv; + struct device_node *child; + void *result; + int rc = 0; + + dev = &pdev->dev; + drv = msmclk_drv_init(dev); + if (IS_ERR(drv)) + return PTR_ERR(drv); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cc-base"); + if (!res) { + dt_err(dev->of_node, "missing cc-base\n"); + return -EINVAL; + } + drv->base = devm_ioremap(dev, res->start, resource_size(res)); + if (!drv->base) { + dev_err(dev, "ioremap failed for drv->base\n"); + return -ENOMEM; + } + rc = msmclk_htable_add(dev, drv, dev->of_node->phandle); + if (rc) + return rc; + + rc = enable_rpm_scaling(); + if (rc) + return rc; + + rc = get_ext_regulators(dev); + if (rc) + return rc; + + /* + * Returning -EPROBE_DEFER here is inefficient due to + * destroying work 'unnecessarily' + */ + for_each_available_child_of_node(dev->of_node, child) { + result = msmclk_parse_dt_node(dev, child); + if (!IS_ERR(result)) + continue; + if (!msmclk_debug) + return PTR_ERR(result); + /* + * Parse and report all errors instead of immediately + * exiting. Return the first error code. + */ + if (!rc) + rc = PTR_ERR(result); + } + if (rc) + return rc; + + rc = of_clk_add_provider(dev->of_node, msmclk_clk_get, dev); + if (rc) { + dev_err(dev, "of_clk_add_provider failed\n"); + return rc; + } + + /* + * can't fail after registering clocks, because users may have + * gotten clock references. Failing would delete the memory. + */ + WARN_ON(msm_clock_register(drv->clk_tbl, drv->clk_tbl_size)); + dev_info(dev, "registered clocks\n"); + + return 0; +} + +static struct of_device_id msmclk_match_table[] = { + {.compatible = "qcom,msm-clock-controller"}, + {} +}; + +static struct platform_driver msmclk_driver = { + .probe = msmclk_probe, + .driver = { + .name = "msm-clock-controller", + .of_match_table = msmclk_match_table, + .owner = THIS_MODULE, + }, +}; + +static bool initialized; +int __init msmclk_init(void) +{ + int rc; + if (initialized) + return 0; + + rc = platform_driver_register(&msmclk_driver); + if (rc) + return rc; + initialized = true; + return rc; +} +arch_initcall(msmclk_init); |