From 04d7c9d33243bd5414bd0c8339899609ecf64355 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 17 Mar 2020 20:36:56 -0700 Subject: [PATCH] soc: qcom: pmic_glink: add subsystem restart (SSR) support Add support for PMIC Glink clients to receive notification when the subsystem goes down and comes up again. PMIC Glink client can pass a function pointer state_cb via which they would like to receive when PMIC Glink state transitions so that they can unregister from the framework (e.g. UCSI) and clean up their state as needed. PMIC Glink device supports SSR for the subsystem specified under "qcom,subsys-name" property. Change-Id: I0f32f638d72a13e231e6813fb3d782c8fa7a4af0 Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/pmic_glink.c | 111 +++++++++++++++++++++++++++- include/linux/soc/qcom/pmic_glink.h | 8 ++ 2 files changed, 117 insertions(+), 2 deletions(-) diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c index f2d6bcd46a78..f3b5167bb8a3 100644 --- a/drivers/soc/qcom/pmic_glink.c +++ b/drivers/soc/qcom/pmic_glink.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) "PMIC_GLINK: %s: " fmt, __func__ #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include #include /** @@ -33,9 +35,16 @@ * @rx_list: list for rx messages * @dev_list: list for pmic_glink_dev_list * @state: indicates when remote subsystem is up/down + * @prev_state: previous state of remote subsystem * @child_probed: indicates when the children are probed * @log_filter: message owner filter for logging * @log_enable: enables message logging + * @client_dev_list: list of client devices to be notified on state + * transition during an SSR or PDR + * @ssr_nb: notifier block for subsystem notifier + * @subsys_name: subsystem name from which SSR notifications should + * be handled and notified to the clients + * @subsys_handle: handle to subsystem notifier */ struct pmic_glink_dev { struct rpmsg_device *rpdev; @@ -51,9 +60,14 @@ struct pmic_glink_dev { struct list_head rx_list; struct list_head dev_list; atomic_t state; + atomic_t prev_state; bool child_probed; u32 log_filter; bool log_enable; + struct list_head client_dev_list; + struct notifier_block ssr_nb; + const char *subsys_name; + void *subsys_handle; }; /** @@ -65,6 +79,9 @@ struct pmic_glink_dev { * @priv: private data for client * @msg_cb: callback function for client to receive the messages that * are intended to be delivered to it over PMIC Glink + * @node: list node to be added in client_dev_list of pmic_glink device + * @state_cb: callback function to notify pmic glink state in the event of + * a subsystem restart (SSR) or a protection domain restart (PDR) */ struct pmic_glink_client { struct pmic_glink_dev *pgdev; @@ -73,6 +90,9 @@ struct pmic_glink_client { struct mutex lock; void *priv; int (*msg_cb)(void *priv, void *data, size_t len); + struct list_head node; + void (*state_cb)(void *priv, + enum pmic_glink_state state); }; struct pmic_glink_buf { @@ -84,6 +104,50 @@ struct pmic_glink_buf { static LIST_HEAD(pmic_glink_dev_list); static DEFINE_MUTEX(pmic_glink_dev_lock); +static void pmic_glink_notify_clients(struct pmic_glink_dev *pgdev, + enum pmic_glink_state state) +{ + struct pmic_glink_client *pos; + + pm_stay_awake(pgdev->dev); + + mutex_lock(&pgdev->client_lock); + list_for_each_entry(pos, &pgdev->client_dev_list, node) + pos->state_cb(pos->priv, state); + mutex_unlock(&pgdev->client_lock); + + pm_relax(pgdev->dev); + + pr_debug("state_cb done %d\n", state); +} + +static int pmic_glink_ssr_notifier_cb(struct notifier_block *nb, + unsigned long code, void *data) +{ + struct pmic_glink_dev *pgdev = container_of(nb, struct pmic_glink_dev, + ssr_nb); + + pr_debug("code: %lu\n", code); + + switch (code) { + case SUBSYS_BEFORE_SHUTDOWN: + atomic_set(&pgdev->prev_state, code); + pmic_glink_notify_clients(pgdev, PMIC_GLINK_STATE_DOWN); + break; + case SUBSYS_AFTER_POWERUP: + /* + * Do not notify PMIC Glink clients here but rather from + * pmic_glink_init_work which will be run only after rpmsg + * driver is probed and Glink communication is up. + */ + break; + default: + break; + } + + return NOTIFY_DONE; +} + static struct pmic_glink_dev *get_pmic_glink_from_dev(struct device *dev) { struct pmic_glink_dev *tmp, *pos; @@ -208,6 +272,7 @@ struct pmic_glink_client *pmic_glink_register_client(struct device *dev, client->msg_cb = client_data->msg_cb; client->priv = client_data->priv; client->pgdev = pgdev; + client->state_cb = client_data->state_cb; mutex_lock(&pgdev->client_lock); rc = idr_alloc(&pgdev->client_idr, client, client->id, client->id + 1, @@ -221,6 +286,10 @@ struct pmic_glink_client *pmic_glink_register_client(struct device *dev, return ERR_PTR(rc); } + if (client->state_cb) { + INIT_LIST_HEAD(&client->node); + list_add_tail(&client->node, &pgdev->client_dev_list); + } mutex_unlock(&pgdev->client_lock); return client; @@ -239,10 +308,17 @@ EXPORT_SYMBOL(pmic_glink_register_client); */ int pmic_glink_unregister_client(struct pmic_glink_client *client) { + struct pmic_glink_client *pos, *tmp; + if (!client || !client->pgdev) return -ENODEV; mutex_lock(&client->pgdev->client_lock); + list_for_each_entry_safe(pos, tmp, &client->pgdev->client_dev_list, + node) { + if (pos == client) + list_del(&client->node); + } idr_remove(&client->pgdev->client_idr, client->id); mutex_unlock(&client->pgdev->client_lock); @@ -412,6 +488,11 @@ static void pmic_glink_init_work(struct work_struct *work) struct device *dev = pgdev->dev; int rc; + if (atomic_read(&pgdev->prev_state) == SUBSYS_BEFORE_SHUTDOWN) { + pmic_glink_notify_clients(pgdev, PMIC_GLINK_STATE_UP); + atomic_set(&pgdev->prev_state, SUBSYS_AFTER_POWERUP); + } + if (pgdev->child_probed) return; @@ -464,34 +545,60 @@ static int pmic_glink_probe(struct platform_device *pdev) return -EINVAL; } + of_property_read_string(dev->of_node, "qcom,subsys-name", + &pgdev->subsys_name); + pgdev->rx_wq = create_singlethread_workqueue("pmic_glink_rx"); if (!pgdev->rx_wq) { pr_err("Failed to create pmic_glink_rx wq\n"); return -ENOMEM; } - dev_set_drvdata(dev, pgdev); - INIT_WORK(&pgdev->rx_work, pmic_glink_rx_work); INIT_WORK(&pgdev->init_work, pmic_glink_init_work); + INIT_LIST_HEAD(&pgdev->client_dev_list); INIT_LIST_HEAD(&pgdev->rx_list); INIT_LIST_HEAD(&pgdev->dev_list); spin_lock_init(&pgdev->rx_lock); mutex_init(&pgdev->client_lock); idr_init(&pgdev->client_idr); + atomic_set(&pgdev->prev_state, SUBSYS_BEFORE_POWERUP); + + if (pgdev->subsys_name) { + pgdev->ssr_nb.notifier_call = pmic_glink_ssr_notifier_cb; + pgdev->subsys_handle = subsys_notif_register_notifier( + pgdev->subsys_name, + &pgdev->ssr_nb); + if (IS_ERR(pgdev->subsys_handle)) { + rc = PTR_ERR(pgdev->subsys_handle); + pr_err("Failed in subsys_notif_register_notifier %d\n", + rc); + goto error_subsys; + } + } + + dev_set_drvdata(dev, pgdev); pgdev->dev = dev; pmic_glink_dev_add(pgdev); pmic_glink_add_debugfs(pgdev); + device_init_wakeup(pgdev->dev, true); pr_debug("%s probed successfully\n", pgdev->channel_name); return 0; + +error_subsys: + idr_destroy(&pgdev->client_idr); + destroy_workqueue(pgdev->rx_wq); + return rc; } static int pmic_glink_remove(struct platform_device *pdev) { struct pmic_glink_dev *pgdev = dev_get_drvdata(&pdev->dev); + subsys_notif_unregister_notifier(pgdev->subsys_handle, &pgdev->ssr_nb); + device_init_wakeup(pgdev->dev, false); debugfs_remove_recursive(pgdev->debugfs_dir); flush_workqueue(pgdev->rx_wq); destroy_workqueue(pgdev->rx_wq); diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h index 24d1e47d5006..ea0b102a484b 100644 --- a/include/linux/soc/qcom/pmic_glink.h +++ b/include/linux/soc/qcom/pmic_glink.h @@ -11,6 +11,11 @@ struct pmic_glink_client; struct device; +enum pmic_glink_state { + PMIC_GLINK_STATE_DOWN, + PMIC_GLINK_STATE_UP, +}; + /** * struct pmic_glink_client_data - pmic_glink client data * @name: Client name @@ -18,12 +23,15 @@ struct device; * @priv: private data for client * @msg_cb: callback function for client to receive the messages that * are intended to be delivered to it over PMIC Glink + * @state_cb: callback function to notify pmic glink state in the event of + * a subsystem restart (SSR) or a protection domain restart (PDR) */ struct pmic_glink_client_data { const char *name; u32 id; void *priv; int (*msg_cb)(void *priv, void *data, size_t len); + void (*state_cb)(void *priv, enum pmic_glink_state state); }; /**