diff options
Diffstat (limited to 'drivers/power')
| -rw-r--r-- | drivers/power/avs/Makefile | 2 | ||||
| -rw-r--r-- | drivers/power/avs/omap_core_dvfs.c | 303 | ||||
| -rw-r--r-- | drivers/power/max17042_battery.c | 148 |
3 files changed, 428 insertions, 25 deletions
diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile index 9827e6ce4ec..7b8ef7ee817 100644 --- a/drivers/power/avs/Makefile +++ b/drivers/power/avs/Makefile @@ -3,7 +3,7 @@ obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o ifneq ($(CONFIG_POWER_TI_HARDWARE_VOLTAGE_CONTROL),) # OMAP Common -omap-volt-common = omap_vc.o omap_vp.o +omap-volt-common = omap_vc.o omap_vp.o omap_core_dvfs.o # OMAP SoC specific ifneq ($(CONFIG_ARCH_OMAP3),) diff --git a/drivers/power/avs/omap_core_dvfs.c b/drivers/power/avs/omap_core_dvfs.c new file mode 100644 index 00000000000..2f98d534fbb --- /dev/null +++ b/drivers/power/avs/omap_core_dvfs.c @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2014 Motorola Mobility LLC + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt, __func__ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/opp.h> +#include <linux/of_device.h> +#include <linux/regulator/consumer.h> +#include <linux/regmap.h> +#include <linux/clk.h> +#include <linux/err.h> + +#define DRIVER_NAME "omap-core-dvfs" + + +struct omap_core_dvfs_map { + unsigned long cpu_freq; + unsigned long core_freq; +}; + +struct omap_core_dvfs_data { + struct device *dev; + struct clk *l3_clock; + struct clk *dpll_clock; + struct regulator *reg; + unsigned int volt_tolerance; + long curr_freq; + struct omap_core_dvfs_map *map; +}; + +static struct omap_core_dvfs_data *core_dvfs_data; + +static const struct of_device_id omap_core_dvfs_match_tbl[] = { + {.compatible = "ti,omap-core-dvfs"}, + {}, +}; +MODULE_DEVICE_TABLE(of, omap_core_dvfs_match_tbl); +static int get_match_freq(unsigned long cpu_freq, unsigned long *core_freq) +{ + struct omap_core_dvfs_map *map = core_dvfs_data->map; + while (map->core_freq && map->cpu_freq) { + if (map->cpu_freq == cpu_freq) { + *core_freq = map->core_freq; + return 0; + } + map++; + } + return -ENODATA; +} +static int cpufreq_trans(struct notifier_block *nb, + unsigned long val, void *data) +{ + struct cpufreq_freqs *freqs = (struct cpufreq_freqs *)data; + struct omap_core_dvfs_data *pdata = core_dvfs_data; + int ret = 0; + long d, new_freq, dpll_freq, old_freq; + struct opp *opp; + unsigned long volt = 0, volt_old = 0, tol = 0; + + if (IS_ERR_OR_NULL(freqs)) + return -ENODATA; + + if (val != CPUFREQ_PRECHANGE || freqs->new == freqs->old) + return 0; + + ret = get_match_freq(freqs->new * 1000, &new_freq); + if (ret) { + pr_err( + "Could not find cpu freq %u in core map\n", freqs->new * 1000); + goto f_out; + } + if (pdata->curr_freq == new_freq) + return 0; + + /* calculate target frequency to be set in dpll */ + old_freq = clk_get_rate(pdata->l3_clock); + d = clk_get_rate(pdata->dpll_clock) / old_freq; + dpll_freq = clk_round_rate(pdata->dpll_clock, new_freq * d); + if (dpll_freq < 0) + dpll_freq = new_freq * d; + + rcu_read_lock(); + opp = opp_find_freq_ceil(pdata->dev, &new_freq); + if (IS_ERR(opp)) { + rcu_read_unlock(); + pr_err("failed to find core OPP for %ld\n", new_freq); + ret = PTR_ERR(opp); + goto f_out; + } + volt = opp_get_voltage(opp); + rcu_read_unlock(); + + tol = volt * pdata->volt_tolerance / 100; + volt_old = regulator_get_voltage(pdata->reg); + + pr_debug("L3 DVFS %ld MHz, %ld mV --> %ld MHz, %ld mV\n", + old_freq / 1000000, volt_old ? volt_old / 1000 : -1, + dpll_freq / d / 1000000, volt ? volt / 1000 : -1); + + if (freqs->new > freqs->old) { + ret = regulator_set_voltage_tol(pdata->reg, volt, tol); + if (ret) { + pr_err("failed to scale core voltage up: %d\n", ret); + goto f_out; + } + } + ret = clk_set_rate(pdata->dpll_clock, dpll_freq); + if (ret) { + pr_err("failed to set core clock rate: %d\n", ret); + regulator_set_voltage_tol(pdata->reg, volt_old, tol); + goto f_out; + } + if (freqs->new < freqs->old) { + ret = regulator_set_voltage_tol(pdata->reg, volt, tol); + if (ret) { + pr_err("failed to scale voltage down: %d\n", ret); + clk_set_rate(pdata->dpll_clock, old_freq * d); + goto f_out; + } + } + pdata->curr_freq = new_freq; +f_out: + return ret; +} + +static struct notifier_block cpufreq_trans_block = { + .notifier_call = cpufreq_trans +}; + +static int of_init_opp_map(struct device *dev, struct omap_core_dvfs_map **map) +{ + const struct property *prop; + const __be32 *val; + struct omap_core_dvfs_map *m; + int nr, i; + + prop = of_find_property(dev->of_node, "map", NULL); + if (!prop) + return -ENODEV; + if (!prop->value) + return -ENODATA; + + /* + * Each entry is a set of tuples consisting of freq-kHz from + * OPP CPU list and index of matching OPP in core list. + */ + nr = prop->length / sizeof(u32); + if (nr % 2) { + dev_err(dev, "Invalid map\n"); + return -EINVAL; + } + + m = devm_kzalloc(dev, prop->length, GFP_KERNEL); + if (!m) { + dev_err(dev, "Unable to create new map\n"); + return -ENOMEM; + } + *map = m; + val = prop->value; + for (i = 0; i < nr; i += 2, m++) { + m->cpu_freq = be32_to_cpup(val++) * 1000; + m->core_freq = be32_to_cpup(val++) * 1000; + } + m->cpu_freq = 0; + m->core_freq = 0; + + return 0; +} + +static int omap_core_dvfs_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *nd = dev->of_node; + int ret; + struct omap_core_dvfs_data *data; + const char *pname, *str; + struct regulator *reg; + + if (!nd) { + dev_err(dev, "no OF information?\n"); + return -EINVAL; + } + + reg = devm_regulator_get(dev, "core_dvfs"); + if (IS_ERR(reg)) { + dev_err(dev, "core_dvfs regulator not ready, retry\n"); + return -EPROBE_DEFER; + } + + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(dev, "Unable to allocate data\n"); + return -ENOMEM; + } + + ret = of_init_opp_table(dev); + if (ret) { + dev_err(dev, "Failed to init OPP table: %d\n", ret); + goto fail; + } + + ret = of_init_opp_map(dev, &data->map); + if (ret) { + dev_err(dev, "Failed to init map: %d\n", ret); + goto fail; + } + pname = "l3_clkname"; + ret = of_property_read_string(nd, pname, &str); + if (ret) + goto property_err; + + data->l3_clock = devm_clk_get(dev, str); + if (IS_ERR(data->l3_clock)) { + ret = PTR_ERR(data->l3_clock); + dev_err(dev, "Failed to get %s clock: %d\n", str, ret); + goto fail; + } + + pname = "dpll_clkname"; + ret = of_property_read_string(nd, pname, &str); + if (ret) + goto property_err; + + data->dpll_clock = devm_clk_get(dev, str); + if (IS_ERR(data->dpll_clock)) { + ret = PTR_ERR(data->dpll_clock); + dev_err(dev, "Failed to get %s clock: %d\n", str, ret); + goto fail; + } + + of_property_read_u32(nd, "voltage-tolerance", &data->volt_tolerance); + + ret = cpufreq_register_notifier(&cpufreq_trans_block, + CPUFREQ_TRANSITION_NOTIFIER); + if (ret) { + dev_err(dev, "CPU notifier registration failed with %d\n", ret); + cpufreq_unregister_notifier( + &cpufreq_trans_block, CPUFREQ_TRANSITION_NOTIFIER); + goto fail; + } + data->reg = reg; + data->dev = dev; + core_dvfs_data = data; + platform_set_drvdata(pdev, data); + + return 0; + +property_err: + dev_err(dev, " Missing/Invalid '%s' property\n", pname); + +fail: + return ret; +} + +static struct platform_driver omap_core_dvfs_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(omap_core_dvfs_match_tbl), + }, + .probe = omap_core_dvfs_probe, +}; +static int __init omap_core_dvfs_init(void) +{ + int ret; + ret = platform_driver_register(&omap_core_dvfs_driver); + if (ret) + pr_err("driver register failed for omap_pmic(%d)\n", ret); + return ret; +} +device_initcall_sync(omap_core_dvfs_init); + +static void __exit omap_core_dvfs_exit(void) +{ + platform_driver_unregister(&omap_core_dvfs_driver); +} +module_exit(omap_core_dvfs_exit); + +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Motorola Mobility LLC"); +MODULE_DESCRIPTION("OMAP Global PRM driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 6d0327f120a..c6c70c4afeb 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -64,15 +64,23 @@ CONFIG_TS_BIT_ENBL | CONFIG_SS_BIT_ENBL) #define MODEL_LOCK1 0X0000 #define MODEL_LOCK2 0X0000 -#define dQ_ACC_DIV 0x4 -#define dP_ACC_100 0x1900 -#define dP_ACC_200 0x3200 +#define MAX17042_INIT_NUM_CYCLES 160 +#define MAX17047_INIT_NUM_CYCLES 96 + +#define MAX17042_dQ_ACC_DIV 4 +#define MAX17047_dQ_ACC_DIV 16 + +#define MAX17042_dP_ACC_200 0x3200 +#define MAX17047_dP_ACC_200 0x0C80 #define MAX17042_IC_VERSION 0x0092 #define MAX17047_IC_VERSION 0x00AC /* same for max17050 */ +#define MAX17042_AGE_DIV 256 + #define INIT_DATA_PROPERTY "maxim,regs-init-data" #define CONFIG_NODE "maxim,configuration" +#define VERSION_PROPERTY "version" #define CONFIG_PROPERTY "config" #define FULL_SOC_THRESH_PROPERTY "full_soc_thresh" #define DESIGN_CAP_PROPERTY "design_cap" @@ -119,6 +127,26 @@ struct max17042_chip { int malicious_online; }; +#ifdef CONFIG_OF +const char *get_dts_batt_id(struct device *dev) +{ + int lenp; + const char *retval = NULL; + struct device_node *n = of_find_node_by_path("/chosen"); + + if (n) { + retval = of_get_property(n, "batt-id", &lenp); + if (!retval || !lenp) { + dev_err(dev, "%s: batt-id len %d\n", __func__, lenp); + retval = NULL; + } + of_node_put(n); + } + + return retval; +} +#endif + static int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) { int ret = i2c_smbus_write_word_data(client, reg, value); @@ -598,8 +626,9 @@ static void max17042_update_capacity_regs(struct max17042_chip *chip) max17042_write_verify_reg(chip->client, MAX17042_FullCAP, config->fullcap); + /* Set DesignCap to fullcapnom here */ max17042_write_reg(chip->client, MAX17042_DesignCap, - config->design_cap); + config->fullcapnom); max17042_write_verify_reg(chip->client, MAX17042_FullCAPNom, config->fullcapnom); } @@ -614,10 +643,21 @@ static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip) max17042_write_reg(chip->client, MAX17042_VFSOC0Enable, VFSOC0_LOCK); } +static void max17042_advance_to_coulomb_counter_mode(struct max17042_chip *chip) +{ + u16 value = (chip->chip_type == MAX17042 ? + MAX17042_INIT_NUM_CYCLES : MAX17047_INIT_NUM_CYCLES); + max17042_write_verify_reg(chip->client, MAX17042_Cycles, value); +} + static void max17042_load_new_capacity_params(struct max17042_chip *chip) { u16 rep_cap, dq_acc, vfSoc; u32 rem_cap; + u16 dQ_ACC_DIV = (chip->chip_type == MAX17042 ? + MAX17042_dQ_ACC_DIV : MAX17047_dQ_ACC_DIV); + u16 dP_ACC_200 = (chip->chip_type == MAX17042 ? + MAX17042_dP_ACC_200 : MAX17047_dP_ACC_200); struct max17042_config_data *config = chip->pdata->config_data; @@ -669,25 +709,14 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) config->soc_alrt_thresh); max17042_override_por(client, MAX17042_CONFIG, config->config); max17042_override_por(client, MAX17042_SHDNTIMER, config->shdntimer); - - max17042_override_por(client, MAX17042_DesignCap, config->design_cap); - max17042_override_por(client, MAX17042_ICHGTerm, config->ichgt_term); - max17042_override_por(client, MAX17042_AtRate, config->at_rate); - max17042_override_por(client, MAX17042_LearnCFG, config->learn_cfg); - max17042_override_por(client, MAX17042_FilterCFG, config->filter_cfg); - max17042_override_por(client, MAX17042_RelaxCFG, config->relax_cfg); max17042_override_por(client, MAX17042_MiscCFG, config->misc_cfg); max17042_override_por(client, MAX17042_MaskSOC, config->masksoc); - max17042_override_por(client, MAX17042_FullCAP, config->fullcap); - max17042_override_por(client, MAX17042_FullCAPNom, config->fullcapnom); if (chip->chip_type == MAX17042) max17042_override_por(client, MAX17042_SOC_empty, config->socempty); max17042_override_por(client, MAX17042_LAvg_empty, config->lavg_empty); - max17042_override_por(client, MAX17042_dQacc, config->dqacc); - max17042_override_por(client, MAX17042_dPacc, config->dpacc); if (chip->chip_type == MAX17042) max17042_override_por(client, MAX17042_V_empty, config->vempty); @@ -696,8 +725,6 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(client, MAX17042_TempNom, config->temp_nom); max17042_override_por(client, MAX17042_TempLim, config->temp_lim); max17042_override_por(client, MAX17042_FCTC, config->fctc); - max17042_override_por(client, MAX17042_RCOMP0, config->rcomp0); - max17042_override_por(client, MAX17042_TempCo, config->tcompc0); if (chip->chip_type == MAX17042) { max17042_override_por(client, MAX17042_EmptyTempCo, config->empty_tempco); @@ -748,6 +775,9 @@ static int max17042_init_chip(struct max17042_chip *chip) /* reset vfsoc0 reg */ max17042_reset_vfsoc0_reg(chip); + /* advance to coulomb-counter mode */ + max17042_advance_to_coulomb_counter_mode(chip); + /* load new capacity params */ max17042_load_new_capacity_params(chip); @@ -821,8 +851,13 @@ static void max17042_init_worker(struct work_struct *work) ret = max17042_init_chip(chip); } - if (!ret) + if (!ret) { chip->init_complete = 1; + if (chip->chip_type == MAX17047) { + max17042_write_reg(chip->client, MAX17047_Config_Ver, + chip->pdata->config_data->version); + } + } mutex_unlock(&chip->lock); } @@ -831,11 +866,16 @@ static void max17042_malicious_removed_worker(struct work_struct *work) { struct max17042_chip *chip = container_of(work, struct max17042_chip, work_malicious_removed); + int ret; mutex_lock(&chip->lock); max17042_perform_soft_POR(chip); - max17042_init_chip(chip); + ret = max17042_init_chip(chip); + if (!ret && chip->chip_type == MAX17047) + max17042_write_reg(chip->client, MAX17047_Config_Ver, + chip->pdata->config_data->version); + dev_info(&chip->client->dev, "malicious ps removed, chip re-inited\n"); mutex_unlock(&chip->lock); @@ -949,6 +989,10 @@ static int max17042_cfg_rqrd_prop(struct device *dev, struct device_node *np, struct max17042_config_data *config_data) { + if (of_property_read_u16(np, VERSION_PROPERTY, + &config_data->version)) + return -EINVAL; + if (of_property_read_u16(np, CONFIG_PROPERTY, &config_data->config)) return -EINVAL; @@ -1008,13 +1052,26 @@ static void max17042_cfg_optnl_prop(struct device_node *np, static struct max17042_config_data * max17042_get_config_data(struct device *dev) { + char *config_node = NULL; + char config_node_path[64]; struct max17042_config_data *config_data; struct device_node *np = dev->of_node; if (!np) return NULL; - np = of_get_child_by_name(np, CONFIG_NODE); + config_node = (char *)get_dts_batt_id(dev); + if (config_node) { + snprintf(config_node_path, sizeof(config_node_path), + "%s-%s", CONFIG_NODE, config_node); + config_node = config_node_path; + } else { + config_node = CONFIG_NODE; + } + + dev_info(dev, "using %s profile\n", config_node); + + np = of_get_child_by_name(np, config_node); if (!np) return NULL; @@ -1175,6 +1232,23 @@ static int max17042_debugfs_write_data(void *data, u64 val) DEFINE_SIMPLE_ATTRIBUTE(data_fops, max17042_debugfs_read_data, max17042_debugfs_write_data, "0x%02llx\n"); +static int max17042_debugfs_read_capacity(void *data, u64 *val) +{ + struct max17042_chip *chip = (struct max17042_chip *)data; + *val = chip->debugfs_capacity; + return 0; +} + +static int max17042_debugfs_write_capacity(void *data, u64 val) +{ + struct max17042_chip *chip = (struct max17042_chip *)data; + chip->debugfs_capacity = val; + power_supply_changed(&chip->battery); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(capacity_fops, max17042_debugfs_read_capacity, + max17042_debugfs_write_capacity, "%llu\n"); + static int max17042_debugfs_create(struct max17042_chip *chip) { chip->debugfs_root = debugfs_create_dir(dev_name(&chip->client->dev), @@ -1191,8 +1265,8 @@ static int max17042_debugfs_create(struct max17042_chip *chip) goto err_debugfs; chip->debugfs_capacity = 0xFF; - if (!debugfs_create_u8("capacity", S_IRUGO | S_IWUSR, - chip->debugfs_root, &chip->debugfs_capacity)) + if (!debugfs_create_file("capacity", S_IRUGO | S_IWUSR, + chip->debugfs_root, chip, &capacity_fops)) goto err_debugfs; return 0; @@ -1226,7 +1300,6 @@ static void max17042_external_power_changed(struct power_supply *psy) } } - static ssize_t max17042_show_alert_threshold(struct device *dev, struct device_attribute *attr, char *buf) @@ -1256,8 +1329,21 @@ static ssize_t max17042_store_alert_threshold(struct device *dev, static DEVICE_ATTR(alert_threshold, S_IRUGO | S_IWUSR, max17042_show_alert_threshold, max17042_store_alert_threshold); + +static ssize_t max17042_show_battery_age(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct max17042_chip *chip = dev_get_drvdata(dev); + int ret = max17042_read_reg(chip->client, MAX17042_Age); + + return ret < 0 ? ret : sprintf(buf, "%u\n", ret / MAX17042_AGE_DIV); +} +static DEVICE_ATTR(battery_age, S_IRUGO, max17042_show_battery_age, NULL); + static struct attribute *max17042_attrs[] = { &dev_attr_alert_threshold.attr, + &dev_attr_battery_age.attr, NULL, }; @@ -1265,6 +1351,20 @@ static struct attribute_group max17042_attr_group = { .attrs = max17042_attrs, }; +static bool max17042_new_config_data(struct max17042_chip *chip) +{ + int ret; + + if (chip->chip_type == MAX17042) + return false; + + ret = max17042_read_reg(chip->client, MAX17047_Config_Ver); + if (ret < 0) + return false; + + return (chip->pdata->config_data->version != ret); +} + static int max17042_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1384,7 +1484,7 @@ static int max17042_probe(struct i2c_client *client, } reg = max17042_read_reg(chip->client, MAX17042_STATUS); - if (reg & STATUS_POR_BIT) { + if (reg & STATUS_POR_BIT || max17042_new_config_data(chip)) { INIT_WORK(&chip->work, max17042_init_worker); schedule_work(&chip->work); } else { |