Merge tag 'clk-for-linus-3.14-part2' of git://git.linaro.org/people/mike.turquette...
[firefly-linux-kernel-4.4.55.git] / drivers / misc / mei / init.c
index f7f3abbe12b6e2a8cc7058397ce77dc0c3cc7029..cdd31c2a2a2bf095e827758e3d71567d80557a5f 100644 (file)
@@ -43,41 +43,119 @@ const char *mei_dev_state_str(int state)
 #undef MEI_DEV_STATE
 }
 
-void mei_device_init(struct mei_device *dev)
-{
-       /* setup our list array */
-       INIT_LIST_HEAD(&dev->file_list);
-       INIT_LIST_HEAD(&dev->device_list);
-       mutex_init(&dev->device_lock);
-       init_waitqueue_head(&dev->wait_hw_ready);
-       init_waitqueue_head(&dev->wait_recvd_msg);
-       init_waitqueue_head(&dev->wait_stop_wd);
-       dev->dev_state = MEI_DEV_INITIALIZING;
 
-       mei_io_list_init(&dev->read_list);
-       mei_io_list_init(&dev->write_list);
-       mei_io_list_init(&dev->write_waiting_list);
-       mei_io_list_init(&dev->ctrl_wr_list);
-       mei_io_list_init(&dev->ctrl_rd_list);
+/**
+ * mei_cancel_work. Cancel mei background jobs
+ *
+ * @dev: the device structure
+ *
+ * returns 0 on success or < 0 if the reset hasn't succeeded
+ */
+void mei_cancel_work(struct mei_device *dev)
+{
+       cancel_work_sync(&dev->init_work);
+       cancel_work_sync(&dev->reset_work);
 
-       INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
-       INIT_WORK(&dev->init_work, mei_host_client_init);
+       cancel_delayed_work(&dev->timer_work);
+}
+EXPORT_SYMBOL_GPL(mei_cancel_work);
 
-       INIT_LIST_HEAD(&dev->wd_cl.link);
-       INIT_LIST_HEAD(&dev->iamthif_cl.link);
-       mei_io_list_init(&dev->amthif_cmd_list);
-       mei_io_list_init(&dev->amthif_rd_complete_list);
+/**
+ * mei_reset - resets host and fw.
+ *
+ * @dev: the device structure
+ */
+int mei_reset(struct mei_device *dev)
+{
+       enum mei_dev_state state = dev->dev_state;
+       bool interrupts_enabled;
+       int ret;
 
-       bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
-       dev->open_handle_count = 0;
+       if (state != MEI_DEV_INITIALIZING &&
+           state != MEI_DEV_DISABLED &&
+           state != MEI_DEV_POWER_DOWN &&
+           state != MEI_DEV_POWER_UP)
+               dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n",
+                        mei_dev_state_str(state));
 
-       /*
-        * Reserving the first client ID
-        * 0: Reserved for MEI Bus Message communications
+       /* we're already in reset, cancel the init timer
+        * if the reset was called due the hbm protocol error
+        * we need to call it before hw start
+        * so the hbm watchdog won't kick in
         */
-       bitmap_set(dev->host_clients_map, 0, 1);
+       mei_hbm_idle(dev);
+
+       /* enter reset flow */
+       interrupts_enabled = state != MEI_DEV_POWER_DOWN;
+       dev->dev_state = MEI_DEV_RESETTING;
+
+       dev->reset_count++;
+       if (dev->reset_count > MEI_MAX_CONSEC_RESET) {
+               dev_err(&dev->pdev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
+               dev->dev_state = MEI_DEV_DISABLED;
+               return -ENODEV;
+       }
+
+       ret = mei_hw_reset(dev, interrupts_enabled);
+       /* fall through and remove the sw state even if hw reset has failed */
+
+       /* no need to clean up software state in case of power up */
+       if (state != MEI_DEV_INITIALIZING &&
+           state != MEI_DEV_POWER_UP) {
+
+               /* remove all waiting requests */
+               mei_cl_all_write_clear(dev);
+
+               mei_cl_all_disconnect(dev);
+
+               /* wake up all readers and writers so they can be interrupted */
+               mei_cl_all_wakeup(dev);
+
+               /* remove entry if already in list */
+               dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n");
+               mei_cl_unlink(&dev->wd_cl);
+               mei_cl_unlink(&dev->iamthif_cl);
+               mei_amthif_reset_params(dev);
+               memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg));
+       }
+
+
+       dev->me_clients_num = 0;
+       dev->rd_msg_hdr = 0;
+       dev->wd_pending = false;
+
+       if (ret) {
+               dev_err(&dev->pdev->dev, "hw_reset failed ret = %d\n", ret);
+               dev->dev_state = MEI_DEV_DISABLED;
+               return ret;
+       }
+
+       if (state == MEI_DEV_POWER_DOWN) {
+               dev_dbg(&dev->pdev->dev, "powering down: end of reset\n");
+               dev->dev_state = MEI_DEV_DISABLED;
+               return 0;
+       }
+
+       ret = mei_hw_start(dev);
+       if (ret) {
+               dev_err(&dev->pdev->dev, "hw_start failed ret = %d\n", ret);
+               dev->dev_state = MEI_DEV_DISABLED;
+               return ret;
+       }
+
+       dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n");
+
+       dev->dev_state = MEI_DEV_INIT_CLIENTS;
+       ret = mei_hbm_start_req(dev);
+       if (ret) {
+               dev_err(&dev->pdev->dev, "hbm_start failed ret = %d\n", ret);
+               dev->dev_state = MEI_DEV_DISABLED;
+               return ret;
+       }
+
+       return 0;
 }
-EXPORT_SYMBOL_GPL(mei_device_init);
+EXPORT_SYMBOL_GPL(mei_reset);
 
 /**
  * mei_start - initializes host and fw to start work.
@@ -90,14 +168,21 @@ int mei_start(struct mei_device *dev)
 {
        mutex_lock(&dev->device_lock);
 
-       /* acknowledge interrupt and stop interupts */
+       /* acknowledge interrupt and stop interrupts */
        mei_clear_interrupts(dev);
 
        mei_hw_config(dev);
 
        dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n");
 
-       mei_reset(dev, 1);
+       dev->dev_state = MEI_DEV_INITIALIZING;
+       dev->reset_count = 0;
+       mei_reset(dev);
+
+       if (dev->dev_state == MEI_DEV_DISABLED) {
+               dev_err(&dev->pdev->dev, "reset failed");
+               goto err;
+       }
 
        if (mei_hbm_start_wait(dev)) {
                dev_err(&dev->pdev->dev, "HBM haven't started");
@@ -132,101 +217,64 @@ err:
 EXPORT_SYMBOL_GPL(mei_start);
 
 /**
- * mei_reset - resets host and fw.
+ * mei_restart - restart device after suspend
  *
  * @dev: the device structure
- * @interrupts_enabled: if interrupt should be enabled after reset.
+ *
+ * returns 0 on success or -ENODEV if the restart hasn't succeeded
  */
-void mei_reset(struct mei_device *dev, int interrupts_enabled)
+int mei_restart(struct mei_device *dev)
 {
-       bool unexpected;
-       int ret;
-
-       unexpected = (dev->dev_state != MEI_DEV_INITIALIZING &&
-                       dev->dev_state != MEI_DEV_DISABLED &&
-                       dev->dev_state != MEI_DEV_POWER_DOWN &&
-                       dev->dev_state != MEI_DEV_POWER_UP);
+       int err;
 
-       if (unexpected)
-               dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n",
-                        mei_dev_state_str(dev->dev_state));
-
-       ret = mei_hw_reset(dev, interrupts_enabled);
-       if (ret) {
-               dev_err(&dev->pdev->dev, "hw reset failed disabling the device\n");
-               interrupts_enabled = false;
-               dev->dev_state = MEI_DEV_DISABLED;
-       }
-
-       dev->hbm_state = MEI_HBM_IDLE;
+       mutex_lock(&dev->device_lock);
 
-       if (dev->dev_state != MEI_DEV_INITIALIZING &&
-           dev->dev_state != MEI_DEV_POWER_UP) {
-               if (dev->dev_state != MEI_DEV_DISABLED &&
-                   dev->dev_state != MEI_DEV_POWER_DOWN)
-                       dev->dev_state = MEI_DEV_RESETTING;
+       mei_clear_interrupts(dev);
 
-               /* remove all waiting requests */
-               mei_cl_all_write_clear(dev);
+       dev->dev_state = MEI_DEV_POWER_UP;
+       dev->reset_count = 0;
 
-               mei_cl_all_disconnect(dev);
+       err = mei_reset(dev);
 
-               /* wake up all readings so they can be interrupted */
-               mei_cl_all_wakeup(dev);
-
-               /* remove entry if already in list */
-               dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n");
-               mei_cl_unlink(&dev->wd_cl);
-               mei_cl_unlink(&dev->iamthif_cl);
-               mei_amthif_reset_params(dev);
-               memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg));
-       }
+       mutex_unlock(&dev->device_lock);
 
-       /* we're already in reset, cancel the init timer */
-       dev->init_clients_timer = 0;
+       if (err || dev->dev_state == MEI_DEV_DISABLED)
+               return -ENODEV;
 
-       dev->me_clients_num = 0;
-       dev->rd_msg_hdr = 0;
-       dev->wd_pending = false;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(mei_restart);
 
-       if (!interrupts_enabled) {
-               dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n");
-               return;
-       }
 
-       ret = mei_hw_start(dev);
-       if (ret) {
-               dev_err(&dev->pdev->dev, "hw_start failed disabling the device\n");
-               dev->dev_state = MEI_DEV_DISABLED;
-               return;
-       }
+static void mei_reset_work(struct work_struct *work)
+{
+       struct mei_device *dev =
+               container_of(work, struct mei_device,  reset_work);
 
-       dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n");
-       /* link is established * start sending messages.  */
+       mutex_lock(&dev->device_lock);
 
-       dev->dev_state = MEI_DEV_INIT_CLIENTS;
+       mei_reset(dev);
 
-       mei_hbm_start_req(dev);
+       mutex_unlock(&dev->device_lock);
 
+       if (dev->dev_state == MEI_DEV_DISABLED)
+               dev_err(&dev->pdev->dev, "reset failed");
 }
-EXPORT_SYMBOL_GPL(mei_reset);
 
 void mei_stop(struct mei_device *dev)
 {
        dev_dbg(&dev->pdev->dev, "stopping the device.\n");
 
-       flush_scheduled_work();
+       mei_cancel_work(dev);
 
-       mutex_lock(&dev->device_lock);
+       mei_nfc_host_exit(dev);
 
-       cancel_delayed_work(&dev->timer_work);
+       mutex_lock(&dev->device_lock);
 
        mei_wd_stop(dev);
 
-       mei_nfc_host_exit();
-
        dev->dev_state = MEI_DEV_POWER_DOWN;
-       mei_reset(dev, 0);
+       mei_reset(dev);
 
        mutex_unlock(&dev->device_lock);
 
@@ -236,3 +284,41 @@ EXPORT_SYMBOL_GPL(mei_stop);
 
 
 
+void mei_device_init(struct mei_device *dev)
+{
+       /* setup our list array */
+       INIT_LIST_HEAD(&dev->file_list);
+       INIT_LIST_HEAD(&dev->device_list);
+       mutex_init(&dev->device_lock);
+       init_waitqueue_head(&dev->wait_hw_ready);
+       init_waitqueue_head(&dev->wait_recvd_msg);
+       init_waitqueue_head(&dev->wait_stop_wd);
+       dev->dev_state = MEI_DEV_INITIALIZING;
+       dev->reset_count = 0;
+
+       mei_io_list_init(&dev->read_list);
+       mei_io_list_init(&dev->write_list);
+       mei_io_list_init(&dev->write_waiting_list);
+       mei_io_list_init(&dev->ctrl_wr_list);
+       mei_io_list_init(&dev->ctrl_rd_list);
+
+       INIT_DELAYED_WORK(&dev->timer_work, mei_timer);
+       INIT_WORK(&dev->init_work, mei_host_client_init);
+       INIT_WORK(&dev->reset_work, mei_reset_work);
+
+       INIT_LIST_HEAD(&dev->wd_cl.link);
+       INIT_LIST_HEAD(&dev->iamthif_cl.link);
+       mei_io_list_init(&dev->amthif_cmd_list);
+       mei_io_list_init(&dev->amthif_rd_complete_list);
+
+       bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX);
+       dev->open_handle_count = 0;
+
+       /*
+        * Reserving the first client ID
+        * 0: Reserved for MEI Bus Message communications
+        */
+       bitmap_set(dev->host_clients_map, 0, 1);
+}
+EXPORT_SYMBOL_GPL(mei_device_init);
+