#include <linux/kernel.h>
 #include <linux/limits.h>
 #include <linux/module.h>
-#include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/property.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 
 #include <linux/surface_aggregator/controller.h>
 #include <linux/surface_aggregator/device.h>
 
 /* -- SSAM base-hub driver. ------------------------------------------------- */
 
+/*
+ * Some devices (especially battery) may need a bit of time to be fully usable
+ * after being (re-)connected. This delay has been determined via
+ * experimentation.
+ */
+#define SSAM_BASE_UPDATE_CONNECT_DELAY         msecs_to_jiffies(2500)
+
 enum ssam_base_hub_state {
        SSAM_BASE_HUB_UNINITIALIZED,
        SSAM_BASE_HUB_CONNECTED,
 struct ssam_base_hub {
        struct ssam_device *sdev;
 
-       struct mutex lock;  /* Guards state update checks and transitions. */
        enum ssam_base_hub_state state;
+       struct delayed_work update_work;
 
        struct ssam_event_notifier notif;
 };
                                        char *buf)
 {
        struct ssam_base_hub *hub = dev_get_drvdata(dev);
-       bool connected;
-
-       mutex_lock(&hub->lock);
-       connected = hub->state == SSAM_BASE_HUB_CONNECTED;
-       mutex_unlock(&hub->lock);
+       bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
 
        return sysfs_emit(buf, "%d\n", connected);
 }
        .attrs = ssam_base_hub_attrs,
 };
 
-static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
+static void ssam_base_hub_update_workfn(struct work_struct *work)
 {
+       struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
        struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
+       enum ssam_base_hub_state state;
        int status = 0;
 
-       lockdep_assert_held(&hub->lock);
+       status = ssam_base_hub_query_state(hub, &state);
+       if (status)
+               return;
 
-       if (hub->state == new)
-               return 0;
-       hub->state = new;
+       if (hub->state == state)
+               return;
+       hub->state = state;
 
        if (hub->state == SSAM_BASE_HUB_CONNECTED)
                status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
 
        if (status)
                dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
-
-       return status;
-}
-
-static int ssam_base_hub_update(struct ssam_base_hub *hub)
-{
-       enum ssam_base_hub_state state;
-       int status;
-
-       mutex_lock(&hub->lock);
-
-       status = ssam_base_hub_query_state(hub, &state);
-       if (!status)
-               status = __ssam_base_hub_update(hub, state);
-
-       mutex_unlock(&hub->lock);
-       return status;
 }
 
 static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
 {
-       struct ssam_base_hub *hub;
-       struct ssam_device *sdev;
-       enum ssam_base_hub_state new;
-
-       hub = container_of(nf, struct ssam_base_hub, notif);
-       sdev = hub->sdev;
+       struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
+       unsigned long delay;
 
        if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
                return 0;
 
        if (event->length < 1) {
-               dev_err(&sdev->dev, "unexpected payload size: %u\n",
-                       event->length);
+               dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
                return 0;
        }
 
-       if (event->data[0])
-               new = SSAM_BASE_HUB_CONNECTED;
-       else
-               new = SSAM_BASE_HUB_DISCONNECTED;
+       /*
+        * Delay update when the base is being connected to give devices/EC
+        * some time to set up.
+        */
+       delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
 
-       mutex_lock(&hub->lock);
-       __ssam_base_hub_update(hub, new);
-       mutex_unlock(&hub->lock);
+       schedule_delayed_work(&hub->update_work, delay);
 
        /*
         * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
 
 static int __maybe_unused ssam_base_hub_resume(struct device *dev)
 {
-       return ssam_base_hub_update(dev_get_drvdata(dev));
+       struct ssam_base_hub *hub = dev_get_drvdata(dev);
+
+       schedule_delayed_work(&hub->update_work, 0);
+       return 0;
 }
 static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
 
        if (!hub)
                return -ENOMEM;
 
-       mutex_init(&hub->lock);
-
        hub->sdev = sdev;
        hub->state = SSAM_BASE_HUB_UNINITIALIZED;
 
        hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
        hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
 
+       INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
+
        ssam_device_set_drvdata(sdev, hub);
 
        status = ssam_notifier_register(sdev->ctrl, &hub->notif);
        if (status)
-               goto err_register;
-
-       status = ssam_base_hub_update(hub);
-       if (status)
-               goto err_update;
+               return status;
 
        status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
        if (status)
-               goto err_update;
+               goto err;
 
+       schedule_delayed_work(&hub->update_work, 0);
        return 0;
 
-err_update:
+err:
        ssam_notifier_unregister(sdev->ctrl, &hub->notif);
+       cancel_delayed_work_sync(&hub->update_work);
        ssam_hub_remove_devices(&sdev->dev);
-err_register:
-       mutex_destroy(&hub->lock);
        return status;
 }
 
        sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
 
        ssam_notifier_unregister(sdev->ctrl, &hub->notif);
+       cancel_delayed_work_sync(&hub->update_work);
        ssam_hub_remove_devices(&sdev->dev);
-
-       mutex_destroy(&hub->lock);
 }
 
 static const struct ssam_device_id ssam_base_hub_match[] = {