| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * OMAP3/OMAP4 Voltage Management Routines |
| * |
| * Author: Thara Gopinath <thara@ti.com> |
| * |
| * Copyright (C) 2007 Texas Instruments, Inc. |
| * Rajendra Nayak <rnayak@ti.com> |
| * Lesly A M <x0080970@ti.com> |
| * |
| * Copyright (C) 2008, 2011 Nokia Corporation |
| * Kalle Jokiniemi |
| * Paul Walmsley |
| * |
| * Copyright (C) 2010 Texas Instruments, Inc. |
| * Thara Gopinath <thara@ti.com> |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/io.h> |
| #include <linux/err.h> |
| #include <linux/export.h> |
| #include <linux/debugfs.h> |
| #include <linux/slab.h> |
| #include <linux/clk.h> |
| |
| #include "common.h" |
| |
| #include "prm-regbits-34xx.h" |
| #include "prm-regbits-44xx.h" |
| #include "prm44xx.h" |
| #include "prcm44xx.h" |
| #include "prminst44xx.h" |
| #include "control.h" |
| |
| #include "voltage.h" |
| #include "powerdomain.h" |
| |
| #include "vc.h" |
| #include "vp.h" |
| |
| static LIST_HEAD(voltdm_list); |
| |
| /* Public functions */ |
| /** |
| * voltdm_get_voltage() - Gets the current non-auto-compensated voltage |
| * @voltdm: pointer to the voltdm for which current voltage info is needed |
| * |
| * API to get the current non-auto-compensated voltage for a voltage domain. |
| * Returns 0 in case of error else returns the current voltage. |
| */ |
| unsigned long voltdm_get_voltage(struct voltagedomain *voltdm) |
| { |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return 0; |
| } |
| |
| return voltdm->nominal_volt; |
| } |
| |
| /** |
| * voltdm_scale() - API to scale voltage of a particular voltage domain. |
| * @voltdm: pointer to the voltage domain which is to be scaled. |
| * @target_volt: The target voltage of the voltage domain |
| * |
| * This API should be called by the kernel to do the voltage scaling |
| * for a particular voltage domain during DVFS. |
| */ |
| int voltdm_scale(struct voltagedomain *voltdm, |
| unsigned long target_volt) |
| { |
| int ret, i; |
| unsigned long volt = 0; |
| |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return -EINVAL; |
| } |
| |
| if (!voltdm->scale) { |
| pr_err("%s: No voltage scale API registered for vdd_%s\n", |
| __func__, voltdm->name); |
| return -ENODATA; |
| } |
| |
| if (!voltdm->volt_data) { |
| pr_err("%s: No voltage data defined for vdd_%s\n", |
| __func__, voltdm->name); |
| return -ENODATA; |
| } |
| |
| /* Adjust voltage to the exact voltage from the OPP table */ |
| for (i = 0; voltdm->volt_data[i].volt_nominal != 0; i++) { |
| if (voltdm->volt_data[i].volt_nominal >= target_volt) { |
| volt = voltdm->volt_data[i].volt_nominal; |
| break; |
| } |
| } |
| |
| if (!volt) { |
| pr_warn("%s: not scaling. OPP voltage for %lu, not found.\n", |
| __func__, target_volt); |
| return -EINVAL; |
| } |
| |
| ret = voltdm->scale(voltdm, volt); |
| if (!ret) |
| voltdm->nominal_volt = volt; |
| |
| return ret; |
| } |
| |
| /** |
| * voltdm_reset() - Resets the voltage of a particular voltage domain |
| * to that of the current OPP. |
| * @voltdm: pointer to the voltage domain whose voltage is to be reset. |
| * |
| * This API finds out the correct voltage the voltage domain is supposed |
| * to be at and resets the voltage to that level. Should be used especially |
| * while disabling any voltage compensation modules. |
| */ |
| void voltdm_reset(struct voltagedomain *voltdm) |
| { |
| unsigned long target_volt; |
| |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return; |
| } |
| |
| target_volt = voltdm_get_voltage(voltdm); |
| if (!target_volt) { |
| pr_err("%s: unable to find current voltage for vdd_%s\n", |
| __func__, voltdm->name); |
| return; |
| } |
| |
| voltdm_scale(voltdm, target_volt); |
| } |
| |
| /** |
| * omap_voltage_get_volttable() - API to get the voltage table associated with a |
| * particular voltage domain. |
| * @voltdm: pointer to the VDD for which the voltage table is required |
| * @volt_data: the voltage table for the particular vdd which is to be |
| * populated by this API |
| * |
| * This API populates the voltage table associated with a VDD into the |
| * passed parameter pointer. Returns the count of distinct voltages |
| * supported by this vdd. |
| * |
| */ |
| void omap_voltage_get_volttable(struct voltagedomain *voltdm, |
| struct omap_volt_data **volt_data) |
| { |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return; |
| } |
| |
| *volt_data = voltdm->volt_data; |
| } |
| |
| /** |
| * omap_voltage_get_voltdata() - API to get the voltage table entry for a |
| * particular voltage |
| * @voltdm: pointer to the VDD whose voltage table has to be searched |
| * @volt: the voltage to be searched in the voltage table |
| * |
| * This API searches through the voltage table for the required voltage |
| * domain and tries to find a matching entry for the passed voltage volt. |
| * If a matching entry is found volt_data is populated with that entry. |
| * This API searches only through the non-compensated voltages int the |
| * voltage table. |
| * Returns pointer to the voltage table entry corresponding to volt on |
| * success. Returns -ENODATA if no voltage table exisits for the passed voltage |
| * domain or if there is no matching entry. |
| */ |
| struct omap_volt_data *omap_voltage_get_voltdata(struct voltagedomain *voltdm, |
| unsigned long volt) |
| { |
| int i; |
| |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| if (!voltdm->volt_data) { |
| pr_warn("%s: voltage table does not exist for vdd_%s\n", |
| __func__, voltdm->name); |
| return ERR_PTR(-ENODATA); |
| } |
| |
| for (i = 0; voltdm->volt_data[i].volt_nominal != 0; i++) { |
| if (voltdm->volt_data[i].volt_nominal == volt) |
| return &voltdm->volt_data[i]; |
| } |
| |
| pr_notice("%s: Unable to match the current voltage with the voltage table for vdd_%s\n", |
| __func__, voltdm->name); |
| |
| return ERR_PTR(-ENODATA); |
| } |
| |
| /** |
| * omap_voltage_register_pmic() - API to register PMIC specific data |
| * @voltdm: pointer to the VDD for which the PMIC specific data is |
| * to be registered |
| * @pmic: the structure containing pmic info |
| * |
| * This API is to be called by the SOC/PMIC file to specify the |
| * pmic specific info as present in omap_voltdm_pmic structure. |
| */ |
| int omap_voltage_register_pmic(struct voltagedomain *voltdm, |
| struct omap_voltdm_pmic *pmic) |
| { |
| if (!voltdm || IS_ERR(voltdm)) { |
| pr_warn("%s: VDD specified does not exist!\n", __func__); |
| return -EINVAL; |
| } |
| |
| voltdm->pmic = pmic; |
| |
| return 0; |
| } |
| |
| /** |
| * omap_voltage_late_init() - Init the various voltage parameters |
| * |
| * This API is to be called in the later stages of the |
| * system boot to init the voltage controller and |
| * voltage processors. |
| */ |
| int __init omap_voltage_late_init(void) |
| { |
| struct voltagedomain *voltdm; |
| |
| if (list_empty(&voltdm_list)) { |
| pr_err("%s: Voltage driver support not added\n", |
| __func__); |
| return -EINVAL; |
| } |
| |
| list_for_each_entry(voltdm, &voltdm_list, node) { |
| struct clk *sys_ck; |
| |
| if (!voltdm->scalable) |
| continue; |
| |
| sys_ck = clk_get(NULL, voltdm->sys_clk.name); |
| if (IS_ERR(sys_ck)) { |
| pr_warn("%s: Could not get sys clk.\n", __func__); |
| return -EINVAL; |
| } |
| voltdm->sys_clk.rate = clk_get_rate(sys_ck); |
| WARN_ON(!voltdm->sys_clk.rate); |
| clk_put(sys_ck); |
| |
| if (voltdm->vc) { |
| voltdm->scale = omap_vc_bypass_scale; |
| omap_vc_init_channel(voltdm); |
| } |
| |
| if (voltdm->vp) { |
| voltdm->scale = omap_vp_forceupdate_scale; |
| omap_vp_init(voltdm); |
| } |
| } |
| |
| return 0; |
| } |
| |
| static struct voltagedomain *_voltdm_lookup(const char *name) |
| { |
| struct voltagedomain *voltdm, *temp_voltdm; |
| |
| voltdm = NULL; |
| |
| list_for_each_entry(temp_voltdm, &voltdm_list, node) { |
| if (!strcmp(name, temp_voltdm->name)) { |
| voltdm = temp_voltdm; |
| break; |
| } |
| } |
| |
| return voltdm; |
| } |
| |
| static int _voltdm_register(struct voltagedomain *voltdm) |
| { |
| if (!voltdm || !voltdm->name) |
| return -EINVAL; |
| |
| list_add(&voltdm->node, &voltdm_list); |
| |
| pr_debug("voltagedomain: registered %s\n", voltdm->name); |
| |
| return 0; |
| } |
| |
| /** |
| * voltdm_lookup - look up a voltagedomain by name, return a pointer |
| * @name: name of voltagedomain |
| * |
| * Find a registered voltagedomain by its name @name. Returns a pointer |
| * to the struct voltagedomain if found, or NULL otherwise. |
| */ |
| struct voltagedomain *voltdm_lookup(const char *name) |
| { |
| struct voltagedomain *voltdm ; |
| |
| if (!name) |
| return NULL; |
| |
| voltdm = _voltdm_lookup(name); |
| |
| return voltdm; |
| } |
| |
| /** |
| * voltdm_init - set up the voltagedomain layer |
| * @voltdm_list: array of struct voltagedomain pointers to register |
| * |
| * Loop through the array of voltagedomains @voltdm_list, registering all |
| * that are available on the current CPU. If voltdm_list is supplied |
| * and not null, all of the referenced voltagedomains will be |
| * registered. No return value. |
| */ |
| void voltdm_init(struct voltagedomain **voltdms) |
| { |
| struct voltagedomain **v; |
| |
| if (voltdms) { |
| for (v = voltdms; *v; v++) |
| _voltdm_register(*v); |
| } |
| } |