423 lines
11 KiB
C
Executable File
423 lines
11 KiB
C
Executable File
/*
|
|
* drivers/power/supply/axp/axp20x/axp20x-regulator.c
|
|
* (C) Copyright 2010-2016
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* caiyongheng <caiyongheng@allwinnertech.com>
|
|
*
|
|
* regulator driver of axp20x
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of
|
|
* the License, or (at your option) any later version.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/driver.h>
|
|
#include <linux/regulator/machine.h>
|
|
#include <linux/module.h>
|
|
#include <linux/power/axp_depend.h>
|
|
#include "../axp-core.h"
|
|
#include "../axp-regulator.h"
|
|
#include "axp20x.h"
|
|
#include "axp20x-regulator.h"
|
|
|
|
enum AXP_REGLS {
|
|
VCC_DCDC2,
|
|
VCC_DCDC3,
|
|
VCC_LDO1,
|
|
VCC_LDO2,
|
|
VCC_LDO3,
|
|
VCC_LDO4,
|
|
VCC_LDOIO0,
|
|
VCC_20X_MAX,
|
|
};
|
|
|
|
struct axp20x_regulators {
|
|
struct regulator_dev *regulators[VCC_20X_MAX];
|
|
struct axp_dev *chip;
|
|
};
|
|
|
|
static const int axp20_ldo4_table[] = {
|
|
1250, 1300, 1400, 1500,
|
|
1600, 1700, 1800, 1900,
|
|
2000, 2500, 2700, 2800,
|
|
3000, 3100, 3200, 3300
|
|
};
|
|
|
|
#define AXP20X_LDO(_id, min, max, step1, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, switch_vol, step2, new_level,\
|
|
mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \
|
|
AXP_LDO(AXP20X, _id, min, max, step1, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, switch_vol, step2, new_level,\
|
|
mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag)
|
|
|
|
#define AXP20X_LDO_SEL(_id, min, max, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, vtable,\
|
|
mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \
|
|
AXP_LDO_SEL(AXP20X, _id, min, max, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, vtable,\
|
|
mode_addr, freq_addr, dvm_ereg, dvm_ebit, dvm_flag)
|
|
|
|
#define AXP20X_DCDC(_id, min, max, step1, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, switch_vol, step2, new_level,\
|
|
mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag) \
|
|
AXP_DCDC(AXP20X, _id, min, max, step1, vreg, shift, nbits,\
|
|
ereg, emask, enval, disval, switch_vol, step2, new_level,\
|
|
mode_addr, mode_bit, freq_addr, dvm_ereg, dvm_ebit, dvm_flag)
|
|
|
|
static struct axp_regulator_info axp20x_regulator_info[] = {
|
|
AXP20X_DCDC(2, 700, 2275, 25, DCDC2, 0, 6, DCDC2EN, 0x10, 0x10,
|
|
0x00, 0, 0, 0, 0x80, 0x04, 0x37, 0x25, 2, 0),
|
|
AXP20X_DCDC(3, 700, 3500, 25, DCDC3, 0, 7, DCDC3EN, 0x02, 0x02,
|
|
0x00, 0, 0, 0, 0x80, 0x02, 0x37, 0x25, 3, 0),
|
|
AXP20X_LDO(1, 3300, 3300, 0, LDO1, 0, 0, LDO1EN, 0x00, 0x00,
|
|
0x00, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
AXP20X_LDO(2, 1800, 3300, 100, LDO2, 4, 4, LDO2EN, 0x04, 0x04,
|
|
0x00, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
AXP20X_LDO(3, 700, 3500, 25, LDO3, 0, 7, LDO3EN, 0x40, 0x40,
|
|
0x00, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
AXP20X_LDO_SEL(4, 1250, 3300, LDO4, 0, 4, LDO4EN, 0x08,
|
|
0x08, 0, axp20_ldo4, 0, 0, 0, 0, 0),
|
|
AXP20X_LDO(IO0, 1800, 3300, 100, LDOIO0, 4, 4, LDOIO0EN, 0x07, 0x03,
|
|
0x00, 0, 0, 0, 0, 0, 0, 0, 0),
|
|
};
|
|
|
|
static struct regulator_init_data axp20x_regulator_init_data[] = {
|
|
[VCC_DCDC2] = {
|
|
.constraints = {
|
|
.name = "axp20x_dcdc2",
|
|
.min_uV = 700000,
|
|
.max_uV = 2275000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
[VCC_DCDC3] = {
|
|
.constraints = {
|
|
.name = "axp20x_dcdc3",
|
|
.min_uV = 700000,
|
|
.max_uV = 3500000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
[VCC_LDO1] = {
|
|
.constraints = {
|
|
.name = "axp20x_rtc", /* ldo1 */
|
|
.min_uV = 3300000,
|
|
.max_uV = 3300000,
|
|
.always_on = 1,
|
|
},
|
|
},
|
|
[VCC_LDO2] = {
|
|
.constraints = {
|
|
.name = "axp20x_ldo2",
|
|
.min_uV = 1800000,
|
|
.max_uV = 3300000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
[VCC_LDO3] = {
|
|
.constraints = {
|
|
.name = "axp20x_ldo3",
|
|
.min_uV = 700000,
|
|
.max_uV = 3300000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
[VCC_LDO4] = {
|
|
.constraints = {
|
|
.name = "axp20x_ldo4",
|
|
.min_uV = 1250000,
|
|
.max_uV = 3300000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
[VCC_LDOIO0] = {
|
|
.constraints = {
|
|
.name = "axp20x_ldoio0",
|
|
.min_uV = 1800000,
|
|
.max_uV = 3300000,
|
|
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE
|
|
| REGULATOR_CHANGE_STATUS,
|
|
},
|
|
},
|
|
};
|
|
|
|
static s32 axp20x_regulator_dependence(const char *ldo_name)
|
|
{
|
|
s32 axp20x_dependence = 0;
|
|
|
|
if (strstr(ldo_name, "dcdc2") != NULL)
|
|
axp20x_dependence |= AXP20X_DCDC2_BIT;
|
|
else if (strstr(ldo_name, "dcdc3") != NULL)
|
|
axp20x_dependence |= AXP20X_DCDC3_BIT;
|
|
else if (strstr(ldo_name, "ldo2") != NULL)
|
|
axp20x_dependence |= AXP20X_LDO2_BIT;
|
|
else if (strstr(ldo_name, "ldo3") != NULL)
|
|
axp20x_dependence |= AXP20X_LDO3_BIT;
|
|
else if (strstr(ldo_name, "ldo4") != NULL)
|
|
axp20x_dependence |= AXP20X_LDO4_BIT;
|
|
else if (strstr(ldo_name, "ldoio0") != NULL)
|
|
axp20x_dependence |= AXP20X_LDOIO0_BIT;
|
|
else if (strstr(ldo_name, "rtc") != NULL)
|
|
axp20x_dependence |= AXP20X_RTC_BIT;
|
|
else
|
|
return -1;
|
|
|
|
axp20x_dependence |= (0 << 30);
|
|
return axp20x_dependence;
|
|
}
|
|
|
|
int axp20x_need_save_regulator;
|
|
static axp_mem_data_t *regu_list;
|
|
static u32 ldo_count;
|
|
|
|
int axp20x_regulator_save(void)
|
|
{
|
|
struct regulator *regu = NULL;
|
|
char *spy_id = NULL;
|
|
char *rtc_id = NULL;
|
|
int ret = 0, ldo_idx = 0;
|
|
|
|
if (axp20x_need_save_regulator) {
|
|
for (ldo_idx = 0; ldo_idx < ldo_count; ldo_idx++) {
|
|
spy_id = (char *)((regu_list + ldo_idx)->id_name);
|
|
|
|
rtc_id = strstr(spy_id, "rtc");
|
|
if (rtc_id != NULL) {
|
|
(regu_list + ldo_idx)->mem_data = 0;
|
|
rtc_id = NULL;
|
|
continue;
|
|
}
|
|
|
|
regu = regulator_get(NULL, spy_id);
|
|
if (IS_ERR(regu)) {
|
|
pr_err("%s: fail to get regulator %s\n",
|
|
__func__, spy_id);
|
|
return -1;
|
|
}
|
|
|
|
ret = regulator_get_voltage(regu);
|
|
if (ret < 0) {
|
|
pr_err("%s: fail to get %s voltage!\n",
|
|
__func__, spy_id);
|
|
return -1;
|
|
}
|
|
|
|
(regu_list + ldo_idx)->mem_data = ret;
|
|
|
|
ret = regulator_is_enabled(regu);
|
|
if (ret > 0)
|
|
(regu_list + ldo_idx)->mem_data |= (1 << 31);
|
|
else
|
|
(regu_list + ldo_idx)->mem_data &= (~(1 << 31));
|
|
|
|
regulator_put(regu);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void axp20x_regulator_restore(void)
|
|
{
|
|
struct regulator *regu = NULL;
|
|
char *spy_id = NULL;
|
|
int ret = 0, volt = 0, ldo_idx = 0;
|
|
|
|
if (axp20x_need_save_regulator) {
|
|
for (ldo_idx = 0; ldo_idx < ldo_count; ldo_idx++) {
|
|
if (0 == (regu_list + ldo_idx)->mem_data)
|
|
continue;
|
|
|
|
spy_id = (char *)((regu_list + ldo_idx)->id_name);
|
|
|
|
regu = regulator_get(NULL, spy_id);
|
|
if (IS_ERR(regu)) {
|
|
pr_err("%s: fail to get regulator %s\n",
|
|
__func__, spy_id);
|
|
continue;
|
|
}
|
|
|
|
ret = regulator_get_voltage(regu);
|
|
volt = (regu_list + ldo_idx)->mem_data & 0x0fffffff;
|
|
|
|
if (ret != volt) {
|
|
ret = regulator_set_voltage(regu, volt, volt);
|
|
if (ret != 0) {
|
|
pr_err("%s: fail to set %s voltage!\n",
|
|
__func__, spy_id);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if ((regu_list + ldo_idx)->mem_data & (1 << 31)) {
|
|
ret = regu->rdev->desc->ops->enable(regu->rdev);
|
|
if (ret != 0) {
|
|
pr_err("%s: fail to enable %s!\n",
|
|
__func__, spy_id);
|
|
continue;
|
|
}
|
|
} else {
|
|
ret = regu->rdev->desc->ops->disable(
|
|
regu->rdev);
|
|
if (ret != 0) {
|
|
pr_err("%s: fail to enable %s!\n",
|
|
__func__, spy_id);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
regulator_put(regu);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int axp20x_regulator_probe(struct platform_device *pdev)
|
|
{
|
|
s32 i, ret = 0;
|
|
struct axp_regulator_info *info;
|
|
struct axp20x_regulators *regu_data;
|
|
struct axp_dev *axp_dev = dev_get_drvdata(pdev->dev.parent);
|
|
|
|
if (pdev->dev.of_node) {
|
|
ret = axp_regulator_dt_parse(pdev->dev.of_node,
|
|
axp20x_regulator_init_data,
|
|
axp20x_regulator_dependence);
|
|
if (ret) {
|
|
pr_err("%s parse device tree err\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
} else {
|
|
pr_err("axp20x regulator device tree err!\n");
|
|
return -EBUSY;
|
|
}
|
|
|
|
regu_data = devm_kzalloc(&pdev->dev, sizeof(*regu_data),
|
|
GFP_KERNEL);
|
|
if (!regu_data)
|
|
return -ENOMEM;
|
|
|
|
regu_data->chip = axp_dev;
|
|
platform_set_drvdata(pdev, regu_data);
|
|
|
|
for (i = 0; i < VCC_20X_MAX; i++) {
|
|
info = &axp20x_regulator_info[i];
|
|
info->pmu_num = axp_dev->pmu_num;
|
|
if (info->desc.id == AXP20X_ID_LDO4) {
|
|
regu_data->regulators[i] = axp_regulator_sel_register(&pdev->dev,
|
|
axp_dev->regmap,
|
|
&axp20x_regulator_init_data[i], info);
|
|
} else {
|
|
regu_data->regulators[i] = axp_regulator_register(&pdev->dev,
|
|
axp_dev->regmap,
|
|
&axp20x_regulator_init_data[i], info);
|
|
}
|
|
|
|
if (IS_ERR(regu_data->regulators[i])) {
|
|
dev_err(&pdev->dev, "failed to register regulator %s\n",
|
|
info->desc.name);
|
|
while (--i >= 0)
|
|
axp_regulator_unregister(
|
|
regu_data->regulators[i]);
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (info->desc.id >= AXP_DCDC_ID_START) {
|
|
ret = axp_regulator_create_attrs(
|
|
®u_data->regulators[i]->dev);
|
|
if (ret)
|
|
dev_err(&pdev->dev,
|
|
"failed to register regulator attr %s\n",
|
|
info->desc.name);
|
|
}
|
|
}
|
|
|
|
/* voltage not to the OTP default when wakeup */
|
|
if (axp20x_need_save_regulator == 0) {
|
|
axp_regmap_set_bits(axp_dev->regmap, AXP20X_HOTOVER_CTL, 0x02);
|
|
} else {
|
|
ret = axp_get_ldo_count(pdev->dev.of_node, &ldo_count);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to get ldo count\n");
|
|
return ret;
|
|
}
|
|
|
|
regu_list = (axp_mem_data_t *)kzalloc(
|
|
sizeof(axp_mem_data_t) * ldo_count, GFP_KERNEL);
|
|
if (!regu_list) {
|
|
pr_err("%s: request regu_list failed\n", __func__);
|
|
return -1;
|
|
}
|
|
|
|
ret = axp_mem_regu_init(pdev->dev.of_node,
|
|
regu_list, ldo_count);
|
|
if (ret) {
|
|
dev_err(&pdev->dev, "failed to init mem regu\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* we should set the sys power domain here*/
|
|
init_sys_pwr_dm();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int axp20x_regulator_remove(struct platform_device *pdev)
|
|
{
|
|
struct axp20x_regulators *regu_data = platform_get_drvdata(pdev);
|
|
int i;
|
|
|
|
for (i = 0; i < VCC_20X_MAX; i++)
|
|
regulator_unregister(regu_data->regulators[i]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id axp20x_regulator_dt_ids[] = {
|
|
{ .compatible = "axp203-regulator", },
|
|
{ .compatible = "axp209-regulator", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, axp20x_regulator_dt_ids);
|
|
|
|
static struct platform_driver axp20x_regulator_driver = {
|
|
.driver = {
|
|
.name = "axp20x-regulator",
|
|
.of_match_table = axp20x_regulator_dt_ids,
|
|
},
|
|
.probe = axp20x_regulator_probe,
|
|
.remove = axp20x_regulator_remove,
|
|
};
|
|
|
|
static int __init axp20x_regulator_initcall(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&axp20x_regulator_driver);
|
|
if (IS_ERR_VALUE(ret)) {
|
|
pr_err("%s: failed, errno %d\n", __func__, ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
subsys_initcall(axp20x_regulator_initcall);
|
|
|
|
MODULE_DESCRIPTION("Regulator Driver of axp20x");
|
|
MODULE_AUTHOR("caiyongheng");
|
|
MODULE_LICENSE("GPL");
|