blob: 211e43bb6ea342b59cdb422783075911587a1fba [file] [log] [blame]
/*
* Copyright (c) 2012, 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 <linux/module.h>
#include <linux/io.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/platform_device.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <mach/msm_iomap.h>
/* Address for Perf Level Registor */
#define VDD_APC_PLEVEL_BASE (MSM_CLK_CTL_BASE + 0x0298)
#define VDD_APC_PLEVEL(n) (VDD_APC_PLEVEL_BASE + 4 * n)
/* Address for SYS_P_Level register */
#define VDD_SVS_PLEVEL_ADDR (MSM_CSR_BASE + 0x124)
#define MV_TO_UV(mv) ((mv)*1000)
#define UV_TO_MV(uv) (((uv)+999)/1000)
#define MSM_VP_REGULATOR_DEV_NAME "vp-regulator"
/**
* Convert Voltage to PLEVEL register value
* Here x is required voltage in minivolt
* e.g. if Required voltage is 1200mV then
* required value to be programmed into the
* Plevel register is 0x32. This equation is
* based on H/W logic being used in SVS controller.
*
* Here we are taking the minimum voltage step
* to be 12.5mV as per H/W logic and adding 0x20
* is for selecting the reference voltage.
* 750mV is minimum voltage of MSMC2 smps.
*/
#define VOLT_TO_BIT(x) (((x-750)/(12500/1000)) + 0x20)
#define VREG_VREF_SEL (1 << 5)
#define VREG_VREF_SEL_SHIFT (0x5)
#define VREG_PD_EN (1 << 6)
#define VREG_PD_EN_SHIFT (0x6)
#define VREG_LVL_M (0x1F)
/**
* struct msm_vp - Structure for VP
* @regulator_dev: structure for regulator device
* @current_voltage: current voltage value
*/
struct msm_vp {
struct device *dev;
struct regulator_dev *rdev;
int current_voltage;
};
/* Function to change the Vdd Level */
static int vp_reg_set_voltage(struct regulator_dev *rdev, int min_uV,
int max_uV, unsigned *sel)
{
struct msm_vp *vp = rdev_get_drvdata(rdev);
uint32_t reg_val, perf_level, plevel, cur_plevel, fine_step_volt;
reg_val = readl_relaxed(VDD_SVS_PLEVEL_ADDR);
perf_level = reg_val & 0x07;
plevel = (min_uV - 750000) / 25000;
fine_step_volt = (min_uV - 750000) % 25000;
/**
* Program the new voltage level for the current perf_level
* in corresponding PLEVEL register.
*/
cur_plevel = readl_relaxed(VDD_APC_PLEVEL(perf_level));
/* clear lower 7 bits */
cur_plevel &= ~(0x7F);
cur_plevel |= (plevel | VREG_VREF_SEL);
if (fine_step_volt >= 12500)
cur_plevel |= VREG_PD_EN;
writel_relaxed(cur_plevel, VDD_APC_PLEVEL(perf_level));
/* Clear the current perf level */
reg_val &= 0xF8;
writel_relaxed(reg_val, VDD_SVS_PLEVEL_ADDR);
/* Initiate the PMIC SSBI request to change the voltage */
reg_val |= (BIT(7) | perf_level << 3);
writel_relaxed(reg_val, VDD_SVS_PLEVEL_ADDR);
mb();
udelay(62);
if ((readl_relaxed(VDD_SVS_PLEVEL_ADDR) & 0x07) != perf_level) {
pr_err("Vdd Set Failed\n");
return -EIO;
}
vp->current_voltage = (min_uV / 1000);
return 0;
}
static int vp_reg_get_voltage(struct regulator_dev *rdev)
{
uint32_t reg_val, perf_level, vlevel, cur_plevel;
uint32_t vref_sel, pd_en;
uint32_t cur_voltage;
reg_val = readl_relaxed(VDD_SVS_PLEVEL_ADDR);
perf_level = reg_val & 0x07;
cur_plevel = readl_relaxed(VDD_APC_PLEVEL(perf_level));
vref_sel = (cur_plevel >> VREG_VREF_SEL_SHIFT) & 0x1;
pd_en = (cur_plevel >> VREG_PD_EN_SHIFT) & 0x1;
vlevel = cur_plevel & VREG_LVL_M;
cur_voltage = (750000 + (pd_en * 12500) +
(vlevel * 25000)) * (2 - vref_sel);
return cur_voltage;
}
static int vp_reg_enable(struct regulator_dev *rdev)
{
return 0;
}
static int vp_reg_disable(struct regulator_dev *rdev)
{
return 0;
}
/* Regulator registration specific data */
/* FIXME: should move to board-xx-regulator.c file */
static struct regulator_consumer_supply vp_consumer =
REGULATOR_SUPPLY("vddx_cx", "msm-cpr");
static struct regulator_init_data vp_reg_data = {
.constraints = {
.name = "vddx_c2",
.min_uV = 750000,
.max_uV = 1500000,
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE |
REGULATOR_CHANGE_STATUS,
.valid_modes_mask = REGULATOR_MODE_NORMAL,
.boot_on = 1,
.input_uV = 0,
.always_on = 1,
},
.num_consumer_supplies = 1,
.consumer_supplies = &vp_consumer,
};
/* Regulator specific ops */
static struct regulator_ops vp_reg_ops = {
.enable = vp_reg_enable,
.disable = vp_reg_disable,
.get_voltage = vp_reg_get_voltage,
.set_voltage = vp_reg_set_voltage,
};
/* Regulator Description */
static struct regulator_desc vp_reg = {
.name = "vddcx",
.id = -1,
.ops = &vp_reg_ops,
.type = REGULATOR_VOLTAGE,
};
static int __devinit msm_vp_reg_probe(struct platform_device *pdev)
{
struct msm_vp *vp;
int rc;
vp = kzalloc(sizeof(struct msm_vp), GFP_KERNEL);
if (!vp) {
pr_err("Could not allocate memory for VP\n");
return -ENOMEM;
}
vp->rdev = regulator_register(&vp_reg, NULL, &vp_reg_data, vp, NULL);
if (IS_ERR(vp->rdev)) {
rc = PTR_ERR(vp->rdev);
pr_err("Failed to register regulator: %d\n", rc);
goto error;
}
platform_set_drvdata(pdev, vp);
return 0;
error:
kfree(vp);
return rc;
}
static int __devexit msm_vp_reg_remove(struct platform_device *pdev)
{
struct msm_vp *vp = platform_get_drvdata(pdev);
regulator_unregister(vp->rdev);
platform_set_drvdata(pdev, NULL);
kfree(vp);
return 0;
}
static struct platform_driver msm_vp_reg_driver = {
.probe = msm_vp_reg_probe,
.remove = __devexit_p(msm_vp_reg_remove),
.driver = {
.name = MSM_VP_REGULATOR_DEV_NAME,
.owner = THIS_MODULE,
},
};
static int __init msm_vp_reg_init(void)
{
return platform_driver_register(&msm_vp_reg_driver);
}
postcore_initcall(msm_vp_reg_init);
static void __exit msm_vp_reg_exit(void)
{
platform_driver_unregister(&msm_vp_reg_driver);
}
module_exit(msm_vp_reg_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MSM VP regulator driver");
MODULE_VERSION("1.0");
MODULE_ALIAS("platform:" MSM_VP_REGULATOR_DEV_NAME);