blob: 3373b3f1072dd2dee76d000cfd6881242918893b [file] [log] [blame]
/*
* Regulators driver for Marvell 88PM800
*
* Copyright (C) 2012 Marvell International Ltd.
* Joseph(Yossi) Hanin <yhanin@marvell.com>
* Yi Zhang <yizhang@marvell.com>
*
* 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.
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/mfd/88pm80x.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/ctype.h>
/* BUCK1/4 with DVC[0..3] */
#define PM8XX_BUCK1 (0x3C)
#define PM8XX_BUCK3 (0x41)
#define PM802_BUCK1 (0x29)
#define PM802S_BUCK1 (0x2B)
#define PM813_BUCK1 (0x2a)
#define PM803_BUCK1 (0x2B)
#define PM826_BUCK1 (0x16)
#define PM8XX_WAKEUP1 (0x0D)
#define PM8XX_DVC_EN (1 << 7)
#define BUCK_MIN 600000
#define BUCK_MAX 1587500
#define BUCK_STEP 12500
#define BUCK_MAX_DVC_LVL 4
#define DVC_CONTROL_REG 0x4F
#define DVC_SET_ADDR1 (1 << 0)
#define DVC_SET_ADDR2 (1 << 1)
#define PM826_BUCK_MIN 480000
#define PM826_BUCK_STEP 10000
struct pm8xx_dvc {
struct platform_device *pdev;
struct regmap *powermap;
struct regmap *basemap;
/* to support multi pmic, they have different affected buck */
u32 affectedbuck;
};
static struct pm80x_chip *pm8xx_dvc_gchip;
static struct pm8xx_dvc *pm8xx_dvcdata;
static u32 cached_dvc_val[BUCK_MAX_DVC_LVL];
static inline int volt_to_reg(int uv)
{
#ifdef CONFIG_MFD_PM826
if (pm8xx_dvc_gchip && CHIP_PM826 == pm8xx_dvc_gchip->type)
return (uv - PM826_BUCK_MIN) / PM826_BUCK_STEP;
else
#endif
return (uv - BUCK_MIN) / BUCK_STEP;
}
static inline int reg_to_volt(int regval)
{
#ifdef CONFIG_MFD_PM826
if (pm8xx_dvc_gchip && CHIP_PM826 == pm8xx_dvc_gchip->type)
return regval * PM826_BUCK_STEP + PM826_BUCK_MIN;
else
#endif
return regval * BUCK_STEP + BUCK_MIN;
}
static int getchip_id(void)
{
int val, chip_id, ret = 0;
ret = regmap_read(pm8xx_dvcdata->basemap, 0x0, &val);
chip_id = val & 0xff;
return chip_id;
}
int set_dvc_control_register(unsigned int lvl)
{
int control_lvl, ret;
struct regmap *regmap = pm8xx_dvcdata->powermap;
control_lvl = lvl / 4;
ret = regmap_update_bits(regmap, DVC_CONTROL_REG,
DVC_SET_ADDR1 | DVC_SET_ADDR2, control_lvl);
if (ret < 0)
return ret;
return 0;
}
/*
* Example for usage: set buck1 vl1 as 1200mV
* pm8xx_dvc_setvolt(PM800_ID_BUCK1, 1, 1200 * mV2uV);
*/
int pm8xx_dvc_setvolt(unsigned int buckid, unsigned int lvl, int uv)
{
struct platform_device *pdev;
struct regmap *regmap;
int ret = 0, idx;
u32 affectbuckbits;
u32 regbase = PM8XX_BUCK1;
int chip_id;
if (unlikely(!pm8xx_dvcdata)) {
cached_dvc_val[lvl] = uv;
pr_err("error: pm8xx_dvcdata not initialized, lvl%d: %dmv\n", lvl, uv);
return 0;
}
pdev = pm8xx_dvcdata->pdev;
regmap = pm8xx_dvcdata->powermap;
affectbuckbits = pm8xx_dvcdata->affectedbuck;
pdev = pm8xx_dvcdata->pdev;
regmap = pm8xx_dvcdata->powermap;
affectbuckbits = pm8xx_dvcdata->affectedbuck;
/* buck1 start at 0 */
if (!(affectbuckbits & (1 << buckid))) {
dev_err(&pdev->dev, "unsupported buck%d in DVC\n", buckid);
return -EINVAL;
}
if (uv < BUCK_MIN || uv > BUCK_MAX) {
dev_err(&pdev->dev, "Failed to allocate pm8xx_dvcdata\n");
return -EINVAL;
}
/*check chip id, 88pm860 support more dvc level than other pmic chip*/
chip_id = getchip_id();
#ifdef CONFIG_MFD_PM802
if (chip_id >= CHIP_PM802_ID_A0 && chip_id < CHIP_PM803_ID)
regbase = PM802_BUCK1;
else if (CHIP_PM802S_ID_A0 == chip_id
|| CHIP_PM802S_ID_A1 == chip_id
|| CHIP_PM802S_ID_A2 == chip_id)
regbase = PM802S_BUCK1;
#endif
#ifdef CONFIG_MFD_PM803
if (chip_id == CHIP_PM803_ID)
regbase = PM803_BUCK1;
#endif
#ifdef CONFIG_MFD_PM813
if (chip_id == CHIP_PM813_ID || chip_id == CHIP_PM813S_A1_ID
|| chip_id == CHIP_PM813S_A0_ID)
regbase = PM813_BUCK1;
#endif
#ifdef CONFIG_MFD_PM826
if (CHIP_PM826 == pm8xx_dvc_gchip->type)
regbase = PM826_BUCK1;
#endif
if (lvl >= BUCK_MAX_DVC_LVL) {
dev_err(&pdev->dev, "DVC lvl: %d out of range\n", lvl);
BUG();
}
for (idx = 0; idx < buckid; idx++) {
if ((affectbuckbits >> idx) & 0x1)
regbase += BUCK_MAX_DVC_LVL;
else
regbase += 1;
}
ret = regmap_write(regmap, regbase + lvl, volt_to_reg(uv));
return ret;
};
EXPORT_SYMBOL(pm8xx_dvc_setvolt);
int pm8xx_dvc_getvolt(unsigned int buckid, unsigned int lvl, int *uv)
{
struct platform_device *pdev;
struct regmap *regmap;
int ret = 0, regval = 0, idx;
u32 affectbuckbits;
u32 regbase = PM8XX_BUCK1;
int chip_id;
if (unlikely(!pm8xx_dvcdata)) {
*uv = cached_dvc_val[lvl];
pr_err("error: pm8xx_dvcdata not inited, lvl%d: %dmv\n", lvl, *uv);
return 0;
}
pdev = pm8xx_dvcdata->pdev;
regmap = pm8xx_dvcdata->powermap;
affectbuckbits = pm8xx_dvcdata->affectedbuck;
*uv = 0;
if (!(affectbuckbits & (1 << buckid))) {
dev_err(&pdev->dev, "unsupported buck%d in DVC\n", buckid);
return -EINVAL;
}
/* check chip id, 88pm860 support more dvc level than other pmic chip */
chip_id = getchip_id();
#ifdef CONFIG_MFD_PM802
if (chip_id >= CHIP_PM802_ID_A0 && chip_id < CHIP_PM803_ID)
regbase = PM802_BUCK1;
else if (CHIP_PM802S_ID_A0 == chip_id
|| CHIP_PM802S_ID_A1 == chip_id
|| CHIP_PM802S_ID_A2 == chip_id)
regbase = PM802S_BUCK1;
#endif
#ifdef CONFIG_MFD_PM803
if (chip_id == CHIP_PM803_ID)
regbase = PM803_BUCK1;
#endif
#ifdef CONFIG_MFD_PM813
if (chip_id == CHIP_PM813_ID || chip_id == CHIP_PM813S_A1_ID
|| chip_id == CHIP_PM813S_A0_ID)
regbase = PM813_BUCK1;
#endif
#ifdef CONFIG_MFD_PM826
if (CHIP_PM826 == pm8xx_dvc_gchip->type)
regbase = PM826_BUCK1;
#endif
if (lvl >= BUCK_MAX_DVC_LVL) {
dev_err(&pdev->dev, "DVC lvl: %d out of range\n", lvl);
BUG();
}
for (idx = 0; idx < buckid; idx++) {
if ((affectbuckbits >> idx) & 0x1)
regbase += BUCK_MAX_DVC_LVL;
else
regbase += 1;
}
ret = regmap_read(regmap, regbase + lvl, &regval);
if (!ret)
*uv = reg_to_volt(regval);
return ret;
}
EXPORT_SYMBOL(pm8xx_dvc_getvolt);
/*
* buck1_info usage:
* 1. read buck1 voltage
* >> echo +[level] > /sys/kernel/debug/buck1_info
* >> cat /sys/kernel/debug/buck1_info
* 2. set buck1 voltage
* >> echo +[level] [voltage] > /sys/kernel/debug/buck1_info
*/
static int buck1_dvc_lvl;
static ssize_t pm8xx_buck1_info_read(struct file *file, char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret, uv, len = 0;
char str[100];
if (buck1_dvc_lvl < 0 || buck1_dvc_lvl > 16) {
pr_err("dvc level error\n");
return -EINVAL;
}
ret = pm8xx_dvc_getvolt(PM800_ID_BUCK1, buck1_dvc_lvl, &uv);
if (ret < 0) {
pr_err("get buck1 dvc level %d error\n", buck1_dvc_lvl);
return -EINVAL;
} else
len = snprintf(str, sizeof(str),
"buck 1, level %d, voltage %uV\n", buck1_dvc_lvl, uv);
return simple_read_from_buffer(user_buf, count, ppos, str, len);
}
static ssize_t pm8xx_buck1_info_write(struct file *file, const char __user *user_buf,
size_t count, loff_t *ppos)
{
int ret, volt;
ret = sscanf(user_buf, "+%d %d\n", &buck1_dvc_lvl, &volt);
if (ret < 1) {
pr_err("Right format: +[level] [voltage]\n");
return -EINVAL;
}
if (ret == 2)
pm8xx_dvc_setvolt(PM800_ID_BUCK1, buck1_dvc_lvl, volt);
pm8xx_dvc_getvolt(PM800_ID_BUCK1, buck1_dvc_lvl, &volt);
pr_info("buck 1, level %d, voltage %duV\n", buck1_dvc_lvl, volt);
return count;
}
static const struct file_operations pm8xx_buck1_info_ops = {
.owner = THIS_MODULE,
.open = simple_open,
.read = pm8xx_buck1_info_read,
.write = pm8xx_buck1_info_write,
};
static int pm8xx_dvc_probe(struct platform_device *pdev)
{
struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent);
struct pm8xx_dvc *dvcdata;
struct dentry *pm8xx_dvc_buck1;
int ret = 0, val = 0;
#ifdef CONFIG_CPU_ASR1901
if (CHIP_PM826 != chip->type) {
dev_err(&pdev->dev, "asr190x doesn't support none-pm826 dvc\n");
return 0;
}
#endif
if (pm8xx_dvcdata)
return 0;
dvcdata = devm_kzalloc(&pdev->dev, sizeof(*pm8xx_dvcdata), GFP_KERNEL);
if (!dvcdata) {
dev_err(&pdev->dev, "Failed to allocate pm8xx_dvcdata");
return -ENOMEM;
}
dvcdata->basemap = chip->regmap;
dvcdata->powermap = chip->subchip->regmap_power;
dvcdata->pdev = pdev;
platform_set_drvdata(pdev, dvcdata);
pm8xx_dvcdata = dvcdata;
/* get pmic affected buck 32bit */
if (IS_ENABLED(CONFIG_OF)) {
if (of_property_read_u32(pdev->dev.of_node,
"dvc-affected-buckbits",
&dvcdata->affectedbuck))
dev_dbg(&pdev->dev, "Failed to get affectedbuckbits\n");
} else {
/* by default only enable buck1 */
dvcdata->affectedbuck = 0x1;
}
/* enable PMIC dvc feature */
ret = regmap_update_bits(chip->regmap, PM8XX_WAKEUP1,
PM8XX_DVC_EN, PM8XX_DVC_EN);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to enable pmic dvc feature!\n");
devm_kfree(&pdev->dev, dvcdata);
return ret;
}
regmap_read(chip->regmap, PM8XX_WAKEUP1, &val);
dev_info(&pdev->dev, "DVC power hold enabled %x! DVC buck %x\n",
val, dvcdata->affectedbuck);
pm8xx_dvc_buck1 = debugfs_create_file("buck1_info", S_IRUGO | S_IFREG,
NULL, NULL, &pm8xx_buck1_info_ops);
if (!pm8xx_dvc_buck1) {
pr_err("create buck1_info debugfs error!\n");
return -ENOENT;
} else if (pm8xx_dvc_buck1 == ERR_PTR(-ENODEV)) {
pr_err("CONFIG_DEBUG_FS is not enabled\n");
return -ENOENT;
}
pm8xx_dvc_gchip = chip;
return 0;
}
static int pm8xx_dvc_remove(struct platform_device *pdev)
{
struct pm8xx_dvc *dvcdata =
(struct pm8xx_dvc *)platform_get_drvdata(pdev);
devm_kfree(&pdev->dev, dvcdata);
return 0;
}
static struct of_device_id pm8xx_dvc_of_match[] = {
{.compatible = "marvell,88pm8xx-dvc"},
/* add other pmic dvc device name here */
{},
};
MODULE_DEVICE_TABLE(of, pm8xx_dvc_of_match);
static struct platform_driver pm8xx_dvc_driver = {
.driver = {
.name = "88pm8xx-dvc",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(pm8xx_dvc_of_match),
},
.probe = pm8xx_dvc_probe,
.remove = pm8xx_dvc_remove,
};
static int __init pm8xx_dvc_init(void)
{
return platform_driver_register(&pm8xx_dvc_driver);
}
subsys_initcall(pm8xx_dvc_init);
static void __exit pm8xx_dvc_exit(void)
{
platform_driver_unregister(&pm8xx_dvc_driver);
}
module_exit(pm8xx_dvc_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DVC Driver for Marvell 88PM8xx PMIC");
MODULE_ALIAS("platform:88pm8xx-dvc");