M7350/kernel/drivers/clk/msm/msm-clock-controller.c
2024-09-09 08:57:42 +00:00

756 lines
17 KiB
C

/*
* Copyright (c) 2014, 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(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);