/* * Copyright (c) 2013-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) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #include #include struct proxy_consumer { struct list_head list; struct regulator *reg; bool enable; int min_uV; int max_uV; u32 current_uA; }; static DEFINE_MUTEX(proxy_consumer_list_mutex); static LIST_HEAD(proxy_consumer_list); static bool proxy_consumers_removed; /** * regulator_proxy_consumer_register() - conditionally register a proxy consumer * for the specified regulator and set its boot time parameters * @reg_dev: Device pointer of the regulator * @reg_node: Device node pointer of the regulator * * Returns a struct proxy_consumer pointer corresponding to the regulator on * success, ERR_PTR() if an error occurred, or NULL if no proxy consumer is * needed for the regulator. This function calls * regulator_get(reg_dev, "proxy") after first checking if any proxy consumer * properties are present in the reg_node device node. After that, the voltage, * minimum current, and/or the enable state will be set based upon the device * node property values. */ struct proxy_consumer *regulator_proxy_consumer_register(struct device *reg_dev, struct device_node *reg_node) { struct proxy_consumer *consumer = NULL; const char *reg_name = ""; u32 voltage[2] = {0}; int rc; /* Return immediately if no proxy consumer properties are specified. */ if (!of_find_property(reg_node, "qcom,proxy-consumer-enable", NULL) && !of_find_property(reg_node, "qcom,proxy-consumer-voltage", NULL) && !of_find_property(reg_node, "qcom,proxy-consumer-current", NULL)) return NULL; mutex_lock(&proxy_consumer_list_mutex); /* Do not register new consumers if they cannot be removed later. */ if (proxy_consumers_removed) { rc = -EPERM; goto unlock; } if (dev_name(reg_dev)) reg_name = dev_name(reg_dev); consumer = kzalloc(sizeof(*consumer), GFP_KERNEL); if (!consumer) { pr_err("kzalloc failed\n"); rc = -ENOMEM; goto unlock; } consumer->enable = of_property_read_bool(reg_node, "qcom,proxy-consumer-enable"); of_property_read_u32(reg_node, "qcom,proxy-consumer-current", &consumer->current_uA); rc = of_property_read_u32_array(reg_node, "qcom,proxy-consumer-voltage", voltage, 2); if (!rc) { consumer->min_uV = voltage[0]; consumer->max_uV = voltage[1]; } dev_dbg(reg_dev, "proxy consumer request: enable=%d, voltage_range=[%d, %d] uV, min_current=%d uA\n", consumer->enable, consumer->min_uV, consumer->max_uV, consumer->current_uA); consumer->reg = regulator_get(reg_dev, "proxy"); if (IS_ERR_OR_NULL(consumer->reg)) { rc = PTR_ERR(consumer->reg); pr_err("regulator_get() failed for %s, rc=%d\n", reg_name, rc); goto unlock; } if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) { rc = regulator_set_voltage(consumer->reg, consumer->min_uV, consumer->max_uV); if (rc) { pr_err("regulator_set_voltage %s failed, rc=%d\n", reg_name, rc); goto free_regulator; } } if (consumer->current_uA > 0) { rc = regulator_set_optimum_mode(consumer->reg, consumer->current_uA); if (rc < 0) { pr_err("regulator_set_optimum_mode %s failed, rc=%d\n", reg_name, rc); goto remove_voltage; } } if (consumer->enable) { rc = regulator_enable(consumer->reg); if (rc) { pr_err("regulator_enable %s failed, rc=%d\n", reg_name, rc); goto remove_current; } } list_add(&consumer->list, &proxy_consumer_list); mutex_unlock(&proxy_consumer_list_mutex); return consumer; remove_current: regulator_set_optimum_mode(consumer->reg, 0); remove_voltage: regulator_set_voltage(consumer->reg, 0, INT_MAX); free_regulator: regulator_put(consumer->reg); unlock: kfree(consumer); mutex_unlock(&proxy_consumer_list_mutex); return ERR_PTR(rc); } /* proxy_consumer_list_mutex must be held by caller. */ static int regulator_proxy_consumer_remove(struct proxy_consumer *consumer) { int rc = 0; if (consumer->enable) { rc = regulator_disable(consumer->reg); if (rc) pr_err("regulator_disable failed, rc=%d\n", rc); } if (consumer->current_uA > 0) { rc = regulator_set_optimum_mode(consumer->reg, 0); if (rc < 0) pr_err("regulator_set_optimum_mode failed, rc=%d\n", rc); } if (consumer->max_uV > 0 && consumer->min_uV <= consumer->max_uV) { rc = regulator_set_voltage(consumer->reg, 0, INT_MAX); if (rc) pr_err("regulator_set_voltage failed, rc=%d\n", rc); } regulator_put(consumer->reg); list_del(&consumer->list); kfree(consumer); return rc; } /** * regulator_proxy_consumer_unregister() - unregister a proxy consumer and * remove its boot time requests * @consumer: Pointer to proxy_consumer to be removed * * Returns 0 on success or errno on failure. This function removes all requests * made by the proxy consumer in regulator_proxy_consumer_register() and then * frees the consumer's resources. */ int regulator_proxy_consumer_unregister(struct proxy_consumer *consumer) { int rc = 0; if (IS_ERR_OR_NULL(consumer)) return 0; mutex_lock(&proxy_consumer_list_mutex); if (!proxy_consumers_removed) rc = regulator_proxy_consumer_remove(consumer); mutex_unlock(&proxy_consumer_list_mutex); return rc; } /* * Remove all proxy requests at late_initcall_sync. The assumption is that all * devices have probed at this point and made their own regulator requests. */ static int __init regulator_proxy_consumer_remove_all(void) { struct proxy_consumer *consumer; struct proxy_consumer *temp; mutex_lock(&proxy_consumer_list_mutex); proxy_consumers_removed = true; if (!list_empty(&proxy_consumer_list)) pr_info("removing regulator proxy consumer requests\n"); list_for_each_entry_safe(consumer, temp, &proxy_consumer_list, list) { regulator_proxy_consumer_remove(consumer); } mutex_unlock(&proxy_consumer_list_mutex); return 0; } late_initcall_sync(regulator_proxy_consumer_remove_all);