mfd: rk808: support power off system in syscore shutdown
For PMIC that power off supplies by write register via i2c bus, it's better to do power off at syscore shutdown. Because when run to kernel's "pm_power_off" call, i2c may has been stopped or PMIC may not be able to get i2c transfer while there are too many devices are competiting. This patch effects on PMIC: RK808/RK818/RK816, not including RK805 which power off system by pull up pmic sleep pin in ATF. The i2c maybe stopped before pm_power_off() is called, which results in the PMIC power off failure issue. Moving PMIC power off operation to syscore shutdown is better. Signed-off-by: Joseph Chen <chenjh@rock-chips.com> Change-Id: Ib43827ebd49059719b8899f90a696b6c32a6ddd1
This commit is contained in:
+178
-98
@@ -20,6 +20,7 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/syscore_ops.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/pinctrl/devinfo.h>
|
||||
|
||||
@@ -686,33 +687,17 @@ static struct i2c_client *rk808_i2c_client;
|
||||
static struct rk808_reg_data *suspend_reg, *resume_reg;
|
||||
static int suspend_reg_num, resume_reg_num;
|
||||
|
||||
static void rk808_pm_power_off(void)
|
||||
static void rk805_device_shutdown_prepare(void)
|
||||
{
|
||||
int ret;
|
||||
unsigned int reg, bit;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
|
||||
switch (rk808->variant) {
|
||||
case RK805_ID:
|
||||
reg = RK805_DEV_CTRL_REG;
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
case RK808_ID:
|
||||
reg = RK808_DEVCTRL_REG,
|
||||
bit = DEV_OFF_RST;
|
||||
break;
|
||||
case RK816_ID:
|
||||
reg = RK816_DEV_CTRL_REG,
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
case RK818_ID:
|
||||
reg = RK818_DEVCTRL_REG;
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
default:
|
||||
if (!rk808)
|
||||
return;
|
||||
}
|
||||
ret = regmap_update_bits(rk808->regmap, reg, bit, bit);
|
||||
|
||||
ret = regmap_update_bits(rk808->regmap,
|
||||
RK805_GPIO_IO_POL_REG,
|
||||
SLP_SD_MSK, SHUTDOWN_FUN);
|
||||
if (ret)
|
||||
dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n");
|
||||
}
|
||||
@@ -762,10 +747,45 @@ static void rk817_shutdown_prepare(void)
|
||||
mdelay(2);
|
||||
}
|
||||
|
||||
static void rk8xx_shutdown(struct i2c_client *client)
|
||||
static void rk8xx_device_shutdown(void)
|
||||
{
|
||||
struct rk808 *rk808 = i2c_get_clientdata(client);
|
||||
int ret = 0;
|
||||
int ret;
|
||||
unsigned int reg, bit;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
|
||||
switch (rk808->variant) {
|
||||
case RK805_ID:
|
||||
reg = RK805_DEV_CTRL_REG;
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
case RK808_ID:
|
||||
reg = RK808_DEVCTRL_REG,
|
||||
bit = DEV_OFF_RST;
|
||||
break;
|
||||
case RK816_ID:
|
||||
reg = RK816_DEV_CTRL_REG;
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
case RK818_ID:
|
||||
reg = RK818_DEVCTRL_REG;
|
||||
bit = DEV_OFF;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(rk808->regmap, reg, bit, bit);
|
||||
if (ret)
|
||||
dev_err(&rk808_i2c_client->dev, "Failed to shutdown device!\n");
|
||||
}
|
||||
|
||||
/* Called in syscore shutdown */
|
||||
static void (*pm_shutdown)(void);
|
||||
|
||||
static void rk8xx_syscore_shutdown(void)
|
||||
{
|
||||
int ret;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
|
||||
if (!rk808) {
|
||||
dev_warn(&rk808_i2c_client->dev,
|
||||
@@ -780,24 +800,104 @@ static void rk8xx_shutdown(struct i2c_client *client)
|
||||
regmap_update_bits(rk808->regmap,
|
||||
RK808_RTC_INT_REG,
|
||||
(0x3 << 2), (0x0 << 2));
|
||||
/*
|
||||
* For PMIC that power off supplies by write register via i2c bus,
|
||||
* it's better to do power off at syscore shutdown here.
|
||||
*
|
||||
* Because when run to kernel's "pm_power_off" call, i2c may has
|
||||
* been stopped or PMIC may not be able to get i2c transfer while
|
||||
* there are too many devices are competiting.
|
||||
*/
|
||||
if (system_state == SYSTEM_POWER_OFF) {
|
||||
if (rk808->variant == RK809_ID || rk808->variant == RK817_ID) {
|
||||
ret = regmap_update_bits(rk808->regmap,
|
||||
RK817_SYS_CFG(3),
|
||||
RK817_SLPPIN_FUNC_MSK,
|
||||
SLPPIN_DN_FUN);
|
||||
if (ret) {
|
||||
dev_warn(&rk808_i2c_client->dev,
|
||||
"Cannot switch to power down function\n");
|
||||
}
|
||||
}
|
||||
|
||||
switch (rk808->variant) {
|
||||
case RK805_ID:
|
||||
ret = regmap_update_bits(rk808->regmap,
|
||||
RK805_GPIO_IO_POL_REG,
|
||||
SLP_SD_MSK,
|
||||
SHUTDOWN_FUN);
|
||||
if (pm_shutdown) {
|
||||
dev_info(&rk808_i2c_client->dev, "System power off\n");
|
||||
pm_shutdown();
|
||||
mdelay(10);
|
||||
dev_info(&rk808_i2c_client->dev,
|
||||
"Power off failed !\n");
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct syscore_ops rk808_syscore_ops = {
|
||||
.shutdown = rk8xx_syscore_shutdown,
|
||||
};
|
||||
|
||||
/*
|
||||
* RK8xx PMICs would do real power off in syscore shutdown, if "pm_power_off"
|
||||
* is not assigned(e.g. PSCI is not enabled), we have to provide a dummy
|
||||
* callback for it, otherwise there comes a halt in Reboot system call:
|
||||
*
|
||||
* if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
|
||||
* cmd = LINUX_REBOOT_CMD_HALT;
|
||||
*/
|
||||
static void rk808_pm_power_off_dummy(void)
|
||||
{
|
||||
pr_info("Dummy power off for RK8xx PMICs, should never reach here!\n");
|
||||
|
||||
while (1)
|
||||
;
|
||||
}
|
||||
|
||||
static ssize_t rk8xx_dbg_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
char cmd;
|
||||
u32 input[2], addr, data;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
|
||||
ret = sscanf(buf, "%c ", &cmd);
|
||||
if (ret != 1) {
|
||||
pr_err("Unknown command\n");
|
||||
goto out;
|
||||
}
|
||||
switch (cmd) {
|
||||
case 'w':
|
||||
ret = sscanf(buf, "%c %x %x ", &cmd, &input[0], &input[1]);
|
||||
if (ret != 3) {
|
||||
pr_err("error! cmd format: echo w [addr] [value]\n");
|
||||
goto out;
|
||||
};
|
||||
addr = input[0] & 0xff;
|
||||
data = input[1] & 0xff;
|
||||
pr_info("cmd : %c %x %x\n\n", cmd, input[0], input[1]);
|
||||
regmap_write(rk808->regmap, addr, data);
|
||||
regmap_read(rk808->regmap, addr, &data);
|
||||
pr_info("new: %x %x\n", addr, data);
|
||||
break;
|
||||
case RK809_ID:
|
||||
case RK817_ID:
|
||||
rk817_shutdown_prepare();
|
||||
case 'r':
|
||||
ret = sscanf(buf, "%c %x ", &cmd, &input[0]);
|
||||
if (ret != 2) {
|
||||
pr_err("error! cmd format: echo r [addr]\n");
|
||||
goto out;
|
||||
};
|
||||
pr_info("cmd : %c %x\n\n", cmd, input[0]);
|
||||
addr = input[0] & 0xff;
|
||||
regmap_read(rk808->regmap, addr, &data);
|
||||
pr_info("%x %x\n", input[0], data);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
pr_err("Unknown command\n");
|
||||
break;
|
||||
}
|
||||
if (ret)
|
||||
dev_warn(&client->dev,
|
||||
"Cannot switch to power down function\n");
|
||||
|
||||
out:
|
||||
return count;
|
||||
}
|
||||
|
||||
static int rk817_pinctrl_init(struct device *dev, struct rk808 *rk808)
|
||||
@@ -1003,54 +1103,6 @@ static void rk817_of_property_prepare(struct rk808 *rk808, struct device *dev)
|
||||
dev_err(dev, "failed to register reboot nb\n");
|
||||
}
|
||||
|
||||
static ssize_t rk8xx_dbg_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
int ret;
|
||||
char cmd;
|
||||
u32 input[2], addr, data;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
|
||||
ret = sscanf(buf, "%c ", &cmd);
|
||||
if (ret != 1) {
|
||||
pr_err("Unknown command\n");
|
||||
goto out;
|
||||
}
|
||||
switch (cmd) {
|
||||
case 'w':
|
||||
ret = sscanf(buf, "%c %x %x ", &cmd, &input[0], &input[1]);
|
||||
if (ret != 3) {
|
||||
pr_err("error! cmd format: echo w [addr] [value]\n");
|
||||
goto out;
|
||||
};
|
||||
addr = input[0] & 0xff;
|
||||
data = input[1] & 0xff;
|
||||
pr_info("cmd : %c %x %x\n\n", cmd, input[0], input[1]);
|
||||
regmap_write(rk808->regmap, addr, data);
|
||||
regmap_read(rk808->regmap, addr, &data);
|
||||
pr_info("new: %x %x\n", addr, data);
|
||||
break;
|
||||
case 'r':
|
||||
ret = sscanf(buf, "%c %x ", &cmd, &input[0]);
|
||||
if (ret != 2) {
|
||||
pr_err("error! cmd format: echo r [addr]\n");
|
||||
goto out;
|
||||
};
|
||||
pr_info("cmd : %c %x\n\n", cmd, input[0]);
|
||||
addr = input[0] & 0xff;
|
||||
regmap_read(rk808->regmap, addr, &data);
|
||||
pr_info("%x %x\n", input[0], data);
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown command\n");
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct kobject *rk8xx_kobj;
|
||||
static struct device_attribute rk8xx_attrs =
|
||||
__ATTR(rk8xx_dbg, 0200, NULL, rk8xx_dbg_store);
|
||||
@@ -1077,15 +1129,15 @@ static int rk808_probe(struct i2c_client *client,
|
||||
unsigned char pmic_id_msb, pmic_id_lsb;
|
||||
u8 on_source = 0, off_source = 0;
|
||||
unsigned int on, off;
|
||||
int msb, lsb;
|
||||
int pm_off = 0, msb, lsb;
|
||||
int nr_pre_init_regs;
|
||||
int nr_cells;
|
||||
|
||||
int ret;
|
||||
int i;
|
||||
void (*of_property_prepare_fn)(struct rk808 *rk808,
|
||||
struct device *dev) = NULL;
|
||||
int (*pinctrl_init)(struct device *dev, struct rk808 *rk808) = NULL;
|
||||
void (*device_shutdown_fn)(void) = NULL;
|
||||
|
||||
rk808 = devm_kzalloc(&client->dev, sizeof(*rk808), GFP_KERNEL);
|
||||
if (!rk808)
|
||||
@@ -1132,6 +1184,8 @@ static int rk808_probe(struct i2c_client *client,
|
||||
suspend_reg_num = ARRAY_SIZE(rk805_suspend_reg);
|
||||
resume_reg = rk805_resume_reg;
|
||||
resume_reg_num = ARRAY_SIZE(rk805_resume_reg);
|
||||
device_shutdown_fn = rk8xx_device_shutdown;
|
||||
rk808->pm_pwroff_prep_fn = rk805_device_shutdown_prepare;
|
||||
break;
|
||||
case RK808_ID:
|
||||
rk808->regmap_cfg = &rk808_regmap_config;
|
||||
@@ -1140,6 +1194,7 @@ static int rk808_probe(struct i2c_client *client,
|
||||
nr_pre_init_regs = ARRAY_SIZE(rk808_pre_init_reg);
|
||||
cells = rk808s;
|
||||
nr_cells = ARRAY_SIZE(rk808s);
|
||||
device_shutdown_fn = rk8xx_device_shutdown;
|
||||
break;
|
||||
case RK816_ID:
|
||||
rk808->regmap_cfg = &rk816_regmap_config;
|
||||
@@ -1155,6 +1210,7 @@ static int rk808_probe(struct i2c_client *client,
|
||||
suspend_reg_num = ARRAY_SIZE(rk816_suspend_reg);
|
||||
resume_reg = rk816_resume_reg;
|
||||
resume_reg_num = ARRAY_SIZE(rk816_resume_reg);
|
||||
device_shutdown_fn = rk8xx_device_shutdown;
|
||||
break;
|
||||
case RK818_ID:
|
||||
rk808->regmap_cfg = &rk818_regmap_config;
|
||||
@@ -1169,6 +1225,7 @@ static int rk808_probe(struct i2c_client *client,
|
||||
suspend_reg_num = ARRAY_SIZE(rk818_suspend_reg);
|
||||
resume_reg = rk818_resume_reg;
|
||||
resume_reg_num = ARRAY_SIZE(rk818_resume_reg);
|
||||
device_shutdown_fn = rk8xx_device_shutdown;
|
||||
break;
|
||||
case RK809_ID:
|
||||
case RK817_ID:
|
||||
@@ -1180,6 +1237,7 @@ static int rk808_probe(struct i2c_client *client,
|
||||
nr_cells = ARRAY_SIZE(rk817s);
|
||||
on_source = RK817_ON_SOURCE_REG;
|
||||
off_source = RK817_OFF_SOURCE_REG;
|
||||
rk808->pm_pwroff_prep_fn = rk817_shutdown_prepare;
|
||||
of_property_prepare_fn = rk817_of_property_prepare;
|
||||
pinctrl_init = rk817_pinctrl_init;
|
||||
break;
|
||||
@@ -1226,9 +1284,9 @@ static int rk808_probe(struct i2c_client *client,
|
||||
|
||||
for (i = 0; i < nr_pre_init_regs; i++) {
|
||||
ret = regmap_update_bits(rk808->regmap,
|
||||
pre_init_reg[i].addr,
|
||||
pre_init_reg[i].mask,
|
||||
pre_init_reg[i].value);
|
||||
pre_init_reg[i].addr,
|
||||
pre_init_reg[i].mask,
|
||||
pre_init_reg[i].value);
|
||||
if (ret) {
|
||||
dev_err(&client->dev,
|
||||
"0x%x write err\n",
|
||||
@@ -1272,8 +1330,17 @@ static int rk808_probe(struct i2c_client *client,
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
if (of_property_read_bool(np, "rockchip,system-power-controller"))
|
||||
pm_power_off = rk808_pm_power_off;
|
||||
pm_off = of_property_read_bool(np, "rockchip,system-power-controller");
|
||||
if (pm_off) {
|
||||
if (!pm_power_off_prepare)
|
||||
pm_power_off_prepare = rk808->pm_pwroff_prep_fn;
|
||||
|
||||
if (device_shutdown_fn) {
|
||||
register_syscore_ops(&rk808_syscore_ops);
|
||||
/* power off system in the syscore shutdown ! */
|
||||
pm_shutdown = device_shutdown_fn;
|
||||
}
|
||||
}
|
||||
|
||||
rk8xx_kobj = kobject_create_and_add("rk8xx", NULL);
|
||||
if (rk8xx_kobj) {
|
||||
@@ -1282,6 +1349,9 @@ static int rk808_probe(struct i2c_client *client,
|
||||
dev_err(&client->dev, "create rk8xx sysfs error\n");
|
||||
}
|
||||
|
||||
if (!pm_power_off)
|
||||
pm_power_off = rk808_pm_power_off_dummy;
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
@@ -1296,20 +1366,31 @@ static int rk808_remove(struct i2c_client *client)
|
||||
struct rk808 *rk808 = i2c_get_clientdata(client);
|
||||
|
||||
regmap_del_irq_chip(client->irq, rk808->irq_data);
|
||||
mfd_remove_devices(&client->dev);
|
||||
|
||||
/**
|
||||
* pm_power_off may points to a function from another module.
|
||||
* Check if the pointer is set by us and only then overwrite it.
|
||||
*/
|
||||
if (pm_power_off == rk808_pm_power_off)
|
||||
if (pm_power_off == rk808_pm_power_off_dummy)
|
||||
pm_power_off = NULL;
|
||||
|
||||
/**
|
||||
* As above, check if the pointer is set by us before overwrite.
|
||||
*/
|
||||
if (rk808->pm_pwroff_prep_fn &&
|
||||
pm_power_off_prepare == rk808->pm_pwroff_prep_fn)
|
||||
pm_power_off_prepare = NULL;
|
||||
|
||||
if (pm_shutdown)
|
||||
unregister_syscore_ops(&rk808_syscore_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __maybe_unused rk8xx_suspend(struct device *dev)
|
||||
{
|
||||
struct rk808 *rk808 = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
int i, ret = 0;
|
||||
int value;
|
||||
|
||||
@@ -1372,9 +1453,9 @@ static int __maybe_unused rk8xx_suspend(struct device *dev)
|
||||
|
||||
static int __maybe_unused rk8xx_resume(struct device *dev)
|
||||
{
|
||||
struct rk808 *rk808 = i2c_get_clientdata(to_i2c_client(dev));
|
||||
int value;
|
||||
struct rk808 *rk808 = i2c_get_clientdata(rk808_i2c_client);
|
||||
int i, ret = 0;
|
||||
int value;
|
||||
|
||||
for (i = 0; i < resume_reg_num; i++) {
|
||||
ret = regmap_update_bits(rk808->regmap,
|
||||
@@ -1434,7 +1515,6 @@ static struct i2c_driver rk808_i2c_driver = {
|
||||
},
|
||||
.probe = rk808_probe,
|
||||
.remove = rk808_remove,
|
||||
.shutdown = rk8xx_shutdown,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_ROCKCHIP_THUNDER_BOOT
|
||||
|
||||
@@ -1067,6 +1067,7 @@ struct rk808 {
|
||||
long variant;
|
||||
const struct regmap_config *regmap_cfg;
|
||||
const struct regmap_irq_chip *regmap_irq_chip;
|
||||
void (*pm_pwroff_prep_fn)(void);
|
||||
struct rk808_pin_info *pins;
|
||||
};
|
||||
#endif /* __LINUX_REGULATOR_RK808_H */
|
||||
|
||||
Reference in New Issue
Block a user