i2c: rk3x: Make sure the i2c transfer to be finished before system reboot
authorDavid Wu <david.wu@rock-chips.com>
Wed, 3 Aug 2016 03:27:31 +0000 (11:27 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Fri, 26 May 2017 04:03:12 +0000 (12:03 +0800)
If the system rebooted, there might be i2c transfer at the
same time, it will make something unpredictable, because
the i2c host was reseted, 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.

Change-Id: I3c09f3acbe86595c295edc191aa38351adb7d5dc
Signed-off-by: David Wu <david.wu@rock-chips.com>
Signed-off-by: Jianqun Xu <jay.xu@rock-chips.com>
arch/arm/kernel/reboot.c
arch/arm64/kernel/process.c
drivers/i2c/busses/i2c-rk3x.c
include/linux/reboot.h
kernel/reboot.c

index 1a06da8f0366d9b1056f220c26d9386219f09c60..7ebcd3f2f651f5ab17041b95fef979c2bc7b9da3 100644 (file)
@@ -166,6 +166,8 @@ void machine_restart(char *cmd)
        local_irq_disable();
        smp_send_stop();
 
+       do_kernel_i2c_restart(cmd);
+
        /* Flush the console to make sure all the relevant messages make it
         * out to the console drivers */
        arm_machine_flush_console();
index 8e0b77810dcc74ca8456fcfa4b3763211a05ed8c..bcd7dac42d3aee5bd1458696cd6e2bfc363e54f4 100644 (file)
@@ -145,6 +145,8 @@ void machine_restart(char *cmd)
        local_irq_disable();
        smp_send_stop();
 
+       do_kernel_i2c_restart(cmd);
+
        /*
         * UpdateCapsule() depends on the system being reset via
         * ResetSystem().
index 5b10f4eaee35bbb7f3c0bdbad5af49c5746e7e3e..4c6e3d2433dd69c513505ef9148860752a71b779 100644 (file)
@@ -25,6 +25,8 @@
 #include <linux/mfd/syscon.h>
 #include <linux/regmap.h>
 #include <linux/math64.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
 
 
 /* Register Map */
@@ -190,6 +192,7 @@ 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
  */
 struct rk3x_i2c {
        struct i2c_adapter adap;
@@ -220,6 +223,8 @@ struct rk3x_i2c {
        enum rk3x_i2c_state state;
        unsigned int processed;
        int error;
+
+       struct notifier_block i2c_restart_nb;
 };
 
 static inline void i2c_writel(struct rk3x_i2c *i2c, u32 value,
@@ -1075,10 +1080,10 @@ static int rk3x_i2c_xfer(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);
+
                timeout = wait_event_timeout(i2c->wait, !i2c->busy,
                                             msecs_to_jiffies(WAIT_TIMEOUT));
 
@@ -1114,6 +1119,39 @@ static int rk3x_i2c_xfer(struct i2c_adapter *adap,
        return ret < 0 ? ret : num;
 }
 
+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) {
+               /* 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);
@@ -1223,6 +1261,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_i2c_restart_handler(&i2c->i2c_restart_nb);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
+               return ret;
+       }
+
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
        if (IS_ERR(i2c->regs))
@@ -1345,6 +1391,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_i2c_restart_handler(&i2c->i2c_restart_nb);
        clk_unprepare(i2c->pclk);
        clk_unprepare(i2c->clk);
 
index a7ff409f386d0fadcb318fd41eefe90d97783145..2f3bc9a0d84ffba202a0af13fc7f76861abab84a 100644 (file)
@@ -42,6 +42,10 @@ extern int register_restart_handler(struct notifier_block *);
 extern int unregister_restart_handler(struct notifier_block *);
 extern void do_kernel_restart(char *cmd);
 
+extern int register_i2c_restart_handler(struct notifier_block *);
+extern int unregister_i2c_restart_handler(struct notifier_block *);
+extern void do_kernel_i2c_restart(char *cmd);
+
 /*
  * Architecture-specific implementations of sys_reboot commands.
  */
index bd30a973fe946b03916a1eeb873928adfe1b32b0..0394859a8a6b1355f4614e7b122bc185ccd6d0fe 100644 (file)
@@ -185,6 +185,25 @@ void do_kernel_restart(char *cmd)
        atomic_notifier_call_chain(&restart_handler_list, reboot_mode, cmd);
 }
 
+static ATOMIC_NOTIFIER_HEAD(i2c_restart_handler_list);
+
+int register_i2c_restart_handler(struct notifier_block *nb)
+{
+       return atomic_notifier_chain_register(&i2c_restart_handler_list, nb);
+}
+EXPORT_SYMBOL(register_i2c_restart_handler);
+
+int unregister_i2c_restart_handler(struct notifier_block *nb)
+{
+       return atomic_notifier_chain_unregister(&i2c_restart_handler_list, nb);
+}
+EXPORT_SYMBOL(unregister_i2c_restart_handler);
+
+void do_kernel_i2c_restart(char *cmd)
+{
+       atomic_notifier_call_chain(&i2c_restart_handler_list, reboot_mode, cmd);
+}
+
 void migrate_to_reboot_cpu(void)
 {
        /* The boot cpu is always logical cpu 0 */