summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/avs/Makefile2
-rw-r--r--drivers/power/avs/omap_core_dvfs.c303
-rw-r--r--drivers/power/max17042_battery.c148
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 {