summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--arch/arm/mach-tegra/tegra_cl_dvfs.c106
-rw-r--r--arch/arm/mach-tegra/tegra_cl_dvfs.h6
2 files changed, 103 insertions, 9 deletions
diff --git a/arch/arm/mach-tegra/tegra_cl_dvfs.c b/arch/arm/mach-tegra/tegra_cl_dvfs.c
index 86138f001f9b..fec1e443e366 100644
--- a/arch/arm/mach-tegra/tegra_cl_dvfs.c
+++ b/arch/arm/mach-tegra/tegra_cl_dvfs.c
@@ -1,7 +1,7 @@
/*
* arch/arm/mach-tegra/tegra_cl_dvfs.c
*
- * Copyright (c) 2012-2013 NVIDIA CORPORATION. All rights reserved.
+ * Copyright (c) 2012-2014 NVIDIA Corporation. 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 as
@@ -30,6 +30,7 @@
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
#include <linux/regulator/tegra-dfll-bypass-regulator.h>
#include <linux/tegra-soc.h>
@@ -1799,12 +1800,80 @@ static void tegra_cl_dvfs_register_simon_notifier(struct tegra_cl_dvfs *cld)
return;
}
+/*
+ * Two mechanisms to build vdd_map dynamically:
+ *
+ * 1. Use regulator interface to match voltage selector to voltage level,
+ * and platform data coefficients to convert selector to register values.
+ * Applied when vdd supply with I2C inteface and internal voltage selection
+ * register is connected.
+ *
+ * 2. Directly map PWM duty cycle selector to voltage level using platform data
+ * coefficients. Applied when vdd supply driven by PWM data output is connected.
+ */
+static int build_regulator_vdd_map(struct tegra_cl_dvfs_platform_data *p_data,
+ struct regulator *reg, struct voltage_reg_map **p_vdd_map)
+{
+ int n;
+ u32 sel, i;
+ struct voltage_reg_map *vdd_map;
+
+ if (!reg)
+ return -ENOSYS;
+
+ n = regulator_count_voltages(reg);
+ if (n <= 0)
+ return -ENODATA;
+
+ vdd_map = kzalloc(sizeof(*vdd_map) * n, GFP_KERNEL);
+ if (!vdd_map)
+ return -ENOMEM;
+
+ for (i = 0, sel = 0; sel < n; sel++) {
+ int v = regulator_list_voltage(reg, sel);
+ if (v > 0) {
+ vdd_map[i].reg_uV = v;
+ vdd_map[i].reg_value = sel * p_data->u.pmu_i2c.sel_mul +
+ p_data->u.pmu_i2c.sel_offs;
+ i++;
+ }
+ }
+
+ p_data->vdd_map_size = i;
+ p_data->vdd_map = vdd_map;
+ *p_vdd_map = vdd_map;
+ return i ? 0 : -EINVAL;
+}
+
+static int build_direct_vdd_map(struct tegra_cl_dvfs_platform_data *p_data,
+ struct voltage_reg_map **p_vdd_map)
+{
+ int i;
+ struct voltage_reg_map *vdd_map =
+ kzalloc(sizeof(*vdd_map) * MAX_CL_DVFS_VOLTAGES, GFP_KERNEL);
+
+ if (!vdd_map)
+ return -ENOMEM;
+
+ for (i = 0; i < MAX_CL_DVFS_VOLTAGES; i++) {
+ vdd_map[i].reg_uV = i * p_data->u.pmu_pwm.step_uV +
+ p_data->u.pmu_pwm.min_uV;
+ vdd_map[i].reg_value = i;
+ }
+
+ p_data->vdd_map_size = i;
+ p_data->vdd_map = vdd_map;
+ *p_vdd_map = vdd_map;
+ return 0;
+}
+
static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
{
int ret;
struct tegra_cl_dvfs_platform_data *p_data;
struct resource *res, *res_i2c = NULL;
- struct tegra_cl_dvfs *cld;
+ struct voltage_reg_map *p_vdd_map = NULL;
+ struct tegra_cl_dvfs *cld = NULL;
struct clk *ref_clk, *soc_clk, *i2c_clk, *safe_dvfs_clk, *dfll_clk;
/* Get resources */
@@ -1823,7 +1892,7 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
}
p_data = pdev->dev.platform_data;
- if (!p_data || !p_data->cfg_param || !p_data->vdd_map) {
+ if (!p_data || !p_data->cfg_param) {
dev_err(&pdev->dev, "missing platform data\n");
return -ENODATA;
}
@@ -1850,11 +1919,26 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
return -EINVAL;
}
+ /* Build vdd_map if not specified by platform data */
+ if (!p_data->vdd_map || !p_data->vdd_map_size) {
+ struct regulator *reg = safe_dvfs_clk->dvfs->dvfs_rail->reg;
+ if (p_data->pmu_if == TEGRA_CL_DVFS_PMU_PWM)
+ ret = build_direct_vdd_map(p_data, &p_vdd_map);
+ else
+ ret = build_regulator_vdd_map(p_data, reg, &p_vdd_map);
+
+ if (ret) {
+ dev_err(&pdev->dev, "missing vdd_map (%d)\n", ret);
+ goto err_out;
+ }
+ }
+
/* Allocate cl_dvfs object and populate resource accessors */
cld = kzalloc(sizeof(*cld), GFP_KERNEL);
if (!cld) {
dev_err(&pdev->dev, "failed to allocate cl_dvfs object\n");
- return -ENOMEM;
+ ret = -ENOMEM;
+ goto err_out;
}
cld->cl_base = IO_ADDRESS(res->start);
@@ -1870,11 +1954,10 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
#endif
/* Initialize cl_dvfs */
ret = cl_dvfs_init(cld);
- if (ret) {
- kfree(cld);
- return ret;
- }
+ if (ret)
+ goto err_out;
+ /* From now on probe would not fail */
platform_set_drvdata(pdev, cld);
/*
@@ -1904,6 +1987,13 @@ static int __init tegra_cl_dvfs_probe(struct platform_device *pdev)
cld->safe_dvfs->dvfs_rail->vmax_cdev)
schedule_work(&cld->init_cdev_work);
return 0;
+
+err_out:
+ if (p_data && p_vdd_map)
+ p_data->vdd_map = NULL;
+ kfree(p_vdd_map);
+ kfree(cld);
+ return ret;
}
static struct platform_driver tegra_cl_dvfs_driver = {
diff --git a/arch/arm/mach-tegra/tegra_cl_dvfs.h b/arch/arm/mach-tegra/tegra_cl_dvfs.h
index ecca61e8f8aa..59fdb37f8696 100644
--- a/arch/arm/mach-tegra/tegra_cl_dvfs.h
+++ b/arch/arm/mach-tegra/tegra_cl_dvfs.h
@@ -1,7 +1,7 @@
/*
* arch/arm/mach-tegra/tegra_cl_dvfs.h
*
- * Copyright (C) 2012 NVIDIA Corporation.
+ * Copyright (c) 2012-2014 NVIDIA Corporation. 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 as
@@ -80,10 +80,14 @@ struct tegra_cl_dvfs_platform_data {
u8 reg;
u16 slave_addr;
bool addr_10;
+ u32 sel_mul;
+ u32 sel_offs;
} pmu_i2c;
struct {
unsigned long pwm_rate;
bool delta_mode;
+ int min_uV;
+ int step_uV;
enum tegra_cl_dvfs_pwm_bus pwm_bus;
int pwm_pingroup;