756 lines
17 KiB
C
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);
|