i2c: rk3x: Make sure the i2c transfer to be finished before system reboot
If the system rebooted, there might be i2c transfer at the same time, it will make something unpredictable, because the i2c host was reset, but the slave device wasn't, such as rk808 pmic, so make sure the i2c transfer to be finished before system shutdown at the reset mode. This call chain is expected to be executed before kernel_restart to do something before reset system. such as, i2c restart, boot mode config. Change-Id: I3c09f3acbe86595c295edc191aa38351adb7d5dc Signed-off-by: David Wu <david.wu@rock-chips.com> Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com> Signed-off-by: Sugar Zhang <sugar.zhang@rock-chips.com>
This commit is contained in:
parent
72dd938512
commit
497e7bd6e1
5 changed files with 104 additions and 4 deletions
|
|
@ -137,6 +137,7 @@ void machine_restart(char *cmd)
|
|||
local_irq_disable();
|
||||
smp_send_stop();
|
||||
|
||||
do_kernel_pre_restart(cmd);
|
||||
do_kernel_restart(cmd);
|
||||
|
||||
/* Give a grace period for failure to restart of 1s */
|
||||
|
|
|
|||
|
|
@ -191,6 +191,8 @@ void machine_restart(char *cmd)
|
|||
local_irq_disable();
|
||||
smp_send_stop();
|
||||
|
||||
do_kernel_pre_restart(cmd);
|
||||
|
||||
/*
|
||||
* UpdateCapsule() depends on the system being reset via
|
||||
* ResetSystem().
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@
|
|||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
|
||||
/* Register Map */
|
||||
|
|
@ -189,6 +191,8 @@ struct rk3x_i2c_soc_data {
|
|||
* @state: state of i2c transfer
|
||||
* @processed: byte length which has been send or received
|
||||
* @error: error code for i2c transfer
|
||||
* @i2c_restart_nb: make sure the i2c transfer to be finished
|
||||
* @system_restarting: true if system is restarting
|
||||
*/
|
||||
struct rk3x_i2c {
|
||||
struct i2c_adapter adap;
|
||||
|
|
@ -219,8 +223,17 @@ struct rk3x_i2c {
|
|||
enum rk3x_i2c_state state;
|
||||
unsigned int processed;
|
||||
int error;
|
||||
|
||||
struct notifier_block i2c_restart_nb;
|
||||
bool system_restarting;
|
||||
};
|
||||
|
||||
static inline void rk3x_i2c_wake_up(struct rk3x_i2c *i2c)
|
||||
{
|
||||
if (!i2c->system_restarting)
|
||||
wake_up(&i2c->wait);
|
||||
}
|
||||
|
||||
static inline void i2c_writel(struct rk3x_i2c *i2c, u32 value,
|
||||
unsigned int offset)
|
||||
{
|
||||
|
|
@ -293,7 +306,7 @@ static void rk3x_i2c_stop(struct rk3x_i2c *i2c, int error)
|
|||
i2c_writel(i2c, ctrl, REG_CON);
|
||||
|
||||
/* signal that we are finished with the current msg */
|
||||
wake_up(&i2c->wait);
|
||||
rk3x_i2c_wake_up(i2c);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -469,7 +482,7 @@ static void rk3x_i2c_handle_stop(struct rk3x_i2c *i2c, unsigned int ipd)
|
|||
i2c->state = STATE_IDLE;
|
||||
|
||||
/* signal rk3x_i2c_xfer that we are finished */
|
||||
wake_up(&i2c->wait);
|
||||
rk3x_i2c_wake_up(i2c);
|
||||
}
|
||||
|
||||
static irqreturn_t rk3x_i2c_irq(int irqno, void *dev_id)
|
||||
|
|
@ -1087,10 +1100,10 @@ static int rk3x_i2c_xfer_common(struct i2c_adapter *adap,
|
|||
if (i + ret >= num)
|
||||
i2c->is_last_msg = true;
|
||||
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
|
||||
rk3x_i2c_start(i2c);
|
||||
|
||||
spin_unlock_irqrestore(&i2c->lock, flags);
|
||||
|
||||
if (!polling) {
|
||||
timeout = wait_event_timeout(i2c->wait, !i2c->busy,
|
||||
msecs_to_jiffies(WAIT_TIMEOUT));
|
||||
|
|
@ -1142,6 +1155,40 @@ static int rk3x_i2c_xfer_polling(struct i2c_adapter *adap,
|
|||
return rk3x_i2c_xfer_common(adap, msgs, num, true);
|
||||
}
|
||||
|
||||
static int rk3x_i2c_restart_notify(struct notifier_block *this,
|
||||
unsigned long mode, void *cmd)
|
||||
{
|
||||
struct rk3x_i2c *i2c = container_of(this, struct rk3x_i2c,
|
||||
i2c_restart_nb);
|
||||
int tmo = WAIT_TIMEOUT * USEC_PER_MSEC;
|
||||
u32 val;
|
||||
|
||||
if (i2c->state != STATE_IDLE) {
|
||||
i2c->system_restarting = true;
|
||||
/* complete the unfinished job */
|
||||
while (tmo-- && i2c->busy) {
|
||||
udelay(1);
|
||||
rk3x_i2c_irq(0, i2c);
|
||||
}
|
||||
}
|
||||
|
||||
if (tmo <= 0) {
|
||||
dev_err(i2c->dev, "restart timeout, ipd: 0x%02x, state: %d\n",
|
||||
i2c_readl(i2c, REG_IPD), i2c->state);
|
||||
|
||||
/* Force a STOP condition without interrupt */
|
||||
i2c_writel(i2c, 0, REG_IEN);
|
||||
val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK;
|
||||
val |= REG_CON_EN | REG_CON_STOP;
|
||||
i2c_writel(i2c, val, REG_CON);
|
||||
|
||||
udelay(10);
|
||||
i2c->state = STATE_IDLE;
|
||||
}
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
static __maybe_unused int rk3x_i2c_resume(struct device *dev)
|
||||
{
|
||||
struct rk3x_i2c *i2c = dev_get_drvdata(dev);
|
||||
|
|
@ -1255,6 +1302,14 @@ static int rk3x_i2c_probe(struct platform_device *pdev)
|
|||
spin_lock_init(&i2c->lock);
|
||||
init_waitqueue_head(&i2c->wait);
|
||||
|
||||
i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
|
||||
i2c->i2c_restart_nb.priority = 128;
|
||||
ret = register_pre_restart_handler(&i2c->i2c_restart_nb);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(i2c->regs))
|
||||
return PTR_ERR(i2c->regs);
|
||||
|
|
@ -1365,6 +1420,7 @@ static int rk3x_i2c_remove(struct platform_device *pdev)
|
|||
i2c_del_adapter(&i2c->adap);
|
||||
|
||||
clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
|
||||
unregister_pre_restart_handler(&i2c->i2c_restart_nb);
|
||||
clk_unprepare(i2c->pclk);
|
||||
clk_unprepare(i2c->clk);
|
||||
|
||||
|
|
|
|||
|
|
@ -49,6 +49,26 @@ extern int register_restart_handler(struct notifier_block *);
|
|||
extern int unregister_restart_handler(struct notifier_block *);
|
||||
extern void do_kernel_restart(char *cmd);
|
||||
|
||||
#ifdef CONFIG_NO_GKI
|
||||
extern int register_pre_restart_handler(struct notifier_block *nb);
|
||||
extern int unregister_pre_restart_handler(struct notifier_block *nb);
|
||||
extern void do_kernel_pre_restart(char *cmd);
|
||||
#else
|
||||
static inline int register_pre_restart_handler(struct notifier_block *nb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int unregister_pre_restart_handler(struct notifier_block *nb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void do_kernel_pre_restart(char *cmd)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Architecture-specific implementations of sys_reboot commands.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -217,6 +217,27 @@ void do_kernel_restart(char *cmd)
|
|||
atomic_notifier_call_chain(&restart_handler_list, reboot_mode, cmd);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_NO_GKI
|
||||
static ATOMIC_NOTIFIER_HEAD(pre_restart_handler_list);
|
||||
|
||||
int register_pre_restart_handler(struct notifier_block *nb)
|
||||
{
|
||||
return atomic_notifier_chain_register(&pre_restart_handler_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(register_pre_restart_handler);
|
||||
|
||||
int unregister_pre_restart_handler(struct notifier_block *nb)
|
||||
{
|
||||
return atomic_notifier_chain_unregister(&pre_restart_handler_list, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(unregister_pre_restart_handler);
|
||||
|
||||
void do_kernel_pre_restart(char *cmd)
|
||||
{
|
||||
atomic_notifier_call_chain(&pre_restart_handler_list, reboot_mode, cmd);
|
||||
}
|
||||
#endif
|
||||
|
||||
void migrate_to_reboot_cpu(void)
|
||||
{
|
||||
/* The boot cpu is always logical cpu 0 */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue