diff --git a/drivers/power/supply/qti_battery_charger.c b/drivers/power/supply/qti_battery_charger.c index 94efdc19cb87..6110b9f78e76 100644 --- a/drivers/power/supply/qti_battery_charger.c +++ b/drivers/power/supply/qti_battery_charger.c @@ -148,6 +148,8 @@ struct battery_chg_dev { u32 *thermal_levels; int curr_thermal_level; int num_thermal_levels; + atomic_t state; + struct work_struct subsys_up_work; }; static const int battery_prop_map[BATT_PROP_MAX] = { @@ -198,6 +200,16 @@ static int battery_chg_write(struct battery_chg_dev *bcdev, void *data, { int rc; + /* + * When the subsystem goes down, it's better to return the last + * known values until it comes back up. Hence, return 0 so that + * pmic_glink_write() is not attempted until pmic glink is up. + */ + if (atomic_read(&bcdev->state) == PMIC_GLINK_STATE_DOWN) { + pr_debug("glink state is down\n"); + return 0; + } + mutex_lock(&bcdev->rw_lock); reinit_completion(&bcdev->ack); rc = pmic_glink_write(bcdev->client, data, len); @@ -268,6 +280,40 @@ static int get_property_id(struct psy_state *pst, return -ENOENT; } +static void battery_chg_notify_enable(struct battery_chg_dev *bcdev) +{ + struct battery_charger_set_notify_msg req_msg = { { 0 } }; + int rc; + + /* Send request to enable notification */ + req_msg.hdr.owner = MSG_OWNER_BC; + req_msg.hdr.type = MSG_TYPE_NOTIFY; + req_msg.hdr.opcode = BC_SET_NOTIFY_REQ; + + rc = battery_chg_write(bcdev, &req_msg, sizeof(req_msg)); + if (rc < 0) + pr_err("Failed to enable notification rc=%d\n", rc); +} + +static void battery_chg_subsys_up_work(struct work_struct *work) +{ + struct battery_chg_dev *bcdev = container_of(work, + struct battery_chg_dev, subsys_up_work); + + battery_chg_notify_enable(bcdev); +} + +static void battery_chg_state_cb(void *priv, enum pmic_glink_state state) +{ + struct battery_chg_dev *bcdev = priv; + + pr_debug("state: %d\n", state); + + atomic_set(&bcdev->state, state); + if (state == PMIC_GLINK_STATE_UP) + schedule_work(&bcdev->subsys_up_work); +} + /** * qti_battery_charger_get_prop() - Gets the property being requested * @@ -977,7 +1023,6 @@ static int battery_chg_probe(struct platform_device *pdev) struct battery_chg_dev *bcdev; struct device *dev = &pdev->dev; struct pmic_glink_client_data client_data = { }; - struct battery_charger_set_notify_msg req_msg = { { 0 } }; int rc, i; bcdev = devm_kzalloc(&pdev->dev, sizeof(*bcdev), GFP_KERNEL); @@ -1012,12 +1057,15 @@ static int battery_chg_probe(struct platform_device *pdev) mutex_init(&bcdev->rw_lock); init_completion(&bcdev->ack); + INIT_WORK(&bcdev->subsys_up_work, battery_chg_subsys_up_work); + atomic_set(&bcdev->state, PMIC_GLINK_STATE_UP); bcdev->dev = dev; client_data.id = MSG_OWNER_BC; client_data.name = "battery_charger"; - client_data.callback = battery_chg_callback; + client_data.msg_cb = battery_chg_callback; client_data.priv = bcdev; + client_data.state_cb = battery_chg_state_cb; bcdev->client = pmic_glink_register_client(dev, &client_data); if (IS_ERR(bcdev->client)) { @@ -1045,14 +1093,7 @@ static int battery_chg_probe(struct platform_device *pdev) goto error; } - /* Send request to enable notification */ - req_msg.hdr.owner = MSG_OWNER_BC; - req_msg.hdr.type = MSG_TYPE_NOTIFY; - req_msg.hdr.opcode = BC_SET_NOTIFY_REQ; - - rc = battery_chg_write(bcdev, &req_msg, sizeof(req_msg)); - if (rc < 0) - pr_err("Failed to enable notification rc=%d\n", rc); + battery_chg_notify_enable(bcdev); return 0; error: diff --git a/drivers/soc/qcom/altmode-glink.c b/drivers/soc/qcom/altmode-glink.c index bf994c404f36..bca5359e8704 100644 --- a/drivers/soc/qcom/altmode-glink.c +++ b/drivers/soc/qcom/altmode-glink.c @@ -381,6 +381,28 @@ static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index) return rc; } +static void altmode_state_cb(void *priv, enum pmic_glink_state state) +{ + struct altmode_dev *amdev = priv; + + pr_debug("state: %d\n", state); + + switch (state) { + case PMIC_GLINK_STATE_DOWN: + /* As of now, nothing to do */ + break; + case PMIC_GLINK_STATE_UP: + mutex_lock(&amdev->client_lock); + if (!list_empty(&amdev->client_list)) + schedule_delayed_work(&amdev->send_pan_en_work, + msecs_to_jiffies(20)); + mutex_unlock(&amdev->client_lock); + break; + default: + return; + } +} + #define USBC_NOTIFY_IND_MASK GENMASK(7, 0) #define GET_OP(opcode) (opcode & USBC_NOTIFY_IND_MASK) #define GET_SVID(opcode) (opcode >> 16) @@ -495,10 +517,17 @@ static int altmode_probe(struct platform_device *pdev) RAW_INIT_NOTIFIER_HEAD(&amdev->probe_notifier); + mutex_init(&amdev->client_lock); + idr_init(&amdev->client_idr); + INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en); + INIT_LIST_HEAD(&amdev->d_node); + INIT_LIST_HEAD(&amdev->client_list); + pgclient_data.id = MSG_OWNER_USBC_PAN; pgclient_data.name = "altmode"; - pgclient_data.callback = altmode_callback; + pgclient_data.msg_cb = altmode_callback; pgclient_data.priv = amdev; + pgclient_data.state_cb = altmode_state_cb; amdev->pgclient = pmic_glink_register_client(amdev->dev, &pgclient_data); @@ -507,21 +536,19 @@ static int altmode_probe(struct platform_device *pdev) if (rc != -EPROBE_DEFER) dev_err(dev, "Error in pmic_glink registration: %d\n", rc); - return rc; + goto error_register; } platform_set_drvdata(pdev, amdev); - mutex_init(&amdev->client_lock); - idr_init(&amdev->client_idr); - INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en); - INIT_LIST_HEAD(&amdev->d_node); - INIT_LIST_HEAD(&amdev->client_list); - altmode_device_add(amdev); altmode_notify_clients(amdev, pdev); return 0; + +error_register: + idr_destroy(&amdev->client_idr); + return rc; } static void altmode_device_remove(struct altmode_dev *amdev) diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c index 8fc6f37ee218..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; }; /** @@ -63,7 +77,11 @@ struct pmic_glink_dev { * @id: Unique id for client for communication * @lock: lock for sending data * @priv: private data for client - * @callback: callback function 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; @@ -71,7 +89,10 @@ struct pmic_glink_client { u32 id; struct mutex lock; void *priv; - int (*callback)(void *priv, void *data, size_t len); + 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 { @@ -83,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; @@ -177,7 +242,7 @@ struct pmic_glink_client *pmic_glink_register_client(struct device *dev, if (!dev || !dev->parent) return ERR_PTR(-ENODEV); - if (!client_data->id || !client_data->callback || !client_data->name) + if (!client_data->id || !client_data->msg_cb || !client_data->name) return ERR_PTR(-EINVAL); pgdev = get_pmic_glink_from_dev(dev->parent); @@ -204,9 +269,10 @@ struct pmic_glink_client *pmic_glink_register_client(struct device *dev, mutex_init(&client->lock); client->id = client_data->id; - client->callback = client_data->callback; + 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, @@ -220,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; @@ -238,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); @@ -263,7 +340,7 @@ static void pmic_glink_rx_callback(struct pmic_glink_dev *pgdev, client = idr_find(&pgdev->client_idr, hdr->owner); mutex_unlock(&pgdev->client_lock); - if (!client || !client->callback) { + if (!client || !client->msg_cb) { pr_err("No client present for %u\n", hdr->owner); return; } @@ -276,7 +353,7 @@ static void pmic_glink_rx_callback(struct pmic_glink_dev *pgdev, pbuf->buf); } - client->callback(client->priv, pbuf->buf, pbuf->len); + client->msg_cb(client->priv, pbuf->buf, pbuf->len); } static void pmic_glink_rx_work(struct work_struct *work) @@ -411,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; @@ -463,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/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c index e655a81698a2..8e464d1af1ed 100644 --- a/drivers/soc/qcom/qti_battery_debug.c +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -683,7 +683,7 @@ static int battery_dbg_probe(struct platform_device *pdev) bd->dev = &pdev->dev; client_data.id = MSG_OWNER_BD; client_data.name = "battery_debug"; - client_data.callback = battery_dbg_callback; + client_data.msg_cb = battery_dbg_callback; client_data.priv = bd; bd->client = pmic_glink_register_client(bd->dev, &client_data); diff --git a/drivers/spmi/spmi-glink-debug.c b/drivers/spmi/spmi-glink-debug.c index b0ebb1918103..1c6520a91a8a 100644 --- a/drivers/spmi/spmi-glink-debug.c +++ b/drivers/spmi/spmi-glink-debug.c @@ -321,7 +321,7 @@ static int spmi_glink_probe(struct platform_device *pdev) client_data.id = MSG_OWNER_REG_DUMP; client_data.name = "spmi_register_debug"; - client_data.callback = spmi_glink_callback; + client_data.msg_cb = spmi_glink_callback; client_data.priv = gd; gd->client = pmic_glink_register_client(&pdev->dev, &client_data); diff --git a/drivers/usb/typec/ucsi/ucsi_glink.c b/drivers/usb/typec/ucsi/ucsi_glink.c index e35757f902dd..d5340da33205 100644 --- a/drivers/usb/typec/ucsi/ucsi_glink.c +++ b/drivers/usb/typec/ucsi/ucsi_glink.c @@ -84,6 +84,8 @@ struct ucsi_dev { unsigned long cmd_requested_flags; struct ucsi_glink_constat_info constat_info; struct work_struct notify_work; + struct work_struct setup_work; + atomic_t state; }; static void *ucsi_ipc_log; @@ -267,6 +269,9 @@ static int ucsi_qti_glink_write(struct ucsi_dev *udev, unsigned int offset, if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; + if (atomic_read(&udev->state) == PMIC_GLINK_STATE_DOWN) + return 0; + ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_WRITE_BUF_REQ; @@ -395,6 +400,9 @@ static int ucsi_qti_read(struct ucsi *ucsi, unsigned int offset, if (!validate_ucsi_msg(offset, val_len)) return -EINVAL; + if (atomic_read(&udev->state) == PMIC_GLINK_STATE_DOWN) + return 0; + ucsi_buf.hdr.owner = MSG_OWNER_UC; ucsi_buf.hdr.type = MSG_TYPE_REQ_RESP; ucsi_buf.hdr.opcode = UC_UCSI_READ_BUF_REQ; @@ -442,6 +450,71 @@ static const struct ucsi_operations ucsi_qti_ops = { .async_write = ucsi_qti_async_write }; +static int ucsi_setup(struct ucsi_dev *udev) +{ + int rc; + + if (udev->ucsi) { + dev_err(udev->dev, "ucsi is not NULL\n"); + return -EINVAL; + } + + udev->ucsi = ucsi_create(udev->dev, &ucsi_qti_ops); + if (IS_ERR(udev->ucsi)) { + rc = PTR_ERR(udev->ucsi); + dev_err(udev->dev, "ucsi_create failed rc=%d\n", rc); + udev->ucsi = NULL; + return rc; + } + + ucsi_set_drvdata(udev->ucsi, udev); + + rc = ucsi_register(udev->ucsi); + if (rc) { + dev_err(udev->dev, "ucsi_register failed rc=%d\n", rc); + ucsi_destroy(udev->ucsi); + udev->ucsi = NULL; + return rc; + } + + return 0; +} + +static void ucsi_qti_setup_work(struct work_struct *work) +{ + struct ucsi_dev *udev = container_of(work, struct ucsi_dev, + setup_work); + + ucsi_setup(udev); +} + +static void ucsi_qti_state_cb(void *priv, enum pmic_glink_state state) +{ + struct ucsi_dev *udev = priv; + + dev_dbg(udev->dev, "state: %d\n", state); + + atomic_set(&udev->state, state); + + switch (state) { + case PMIC_GLINK_STATE_DOWN: + if (!udev->ucsi) { + dev_err(udev->dev, "ucsi is NULL\n"); + return; + } + + ucsi_unregister(udev->ucsi); + ucsi_destroy(udev->ucsi); + udev->ucsi = NULL; + break; + case PMIC_GLINK_STATE_UP: + schedule_work(&udev->setup_work); + break; + default: + break; + } +} + static int ucsi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -454,6 +527,7 @@ static int ucsi_probe(struct platform_device *pdev) return -ENOMEM; INIT_WORK(&udev->notify_work, ucsi_qti_notify_work); + INIT_WORK(&udev->setup_work, ucsi_qti_setup_work); mutex_init(&udev->read_lock); mutex_init(&udev->write_lock); mutex_init(&udev->notify_lock); @@ -461,11 +535,13 @@ static int ucsi_probe(struct platform_device *pdev) init_completion(&udev->write_ack); init_completion(&udev->sync_write_ack); atomic_set(&udev->rx_valid, 0); + atomic_set(&udev->state, PMIC_GLINK_STATE_UP); client_data.id = MSG_OWNER_UC; client_data.name = "ucsi"; - client_data.callback = ucsi_callback; + client_data.msg_cb = ucsi_callback; client_data.priv = udev; + client_data.state_cb = ucsi_qti_state_cb; udev->client = pmic_glink_register_client(dev, &client_data); if (IS_ERR(udev->client)) { @@ -477,34 +553,19 @@ static int ucsi_probe(struct platform_device *pdev) } platform_set_drvdata(pdev, udev); + udev->dev = dev; - udev->ucsi = ucsi_create(dev, &ucsi_qti_ops); - if (IS_ERR(udev->ucsi)) { - rc = PTR_ERR(udev->ucsi); - dev_err(dev, "ucsi_create failed rc=%d\n", rc); - return rc; - } - - ucsi_set_drvdata(udev->ucsi, udev); ucsi_ipc_log = ipc_log_context_create(NUM_LOG_PAGES, "ucsi", 0); if (!ucsi_ipc_log) dev_warn(dev, "Error in creating ipc_log_context\n"); - rc = ucsi_register(udev->ucsi); + rc = ucsi_setup(udev); if (rc) { - dev_err(dev, "ucsi_register failed rc=%d\n", rc); - goto out; + ipc_log_context_destroy(ucsi_ipc_log); + ucsi_ipc_log = NULL; + pmic_glink_unregister_client(udev->client); } - pr_debug("driver probed\n"); - - return 0; -out: - ipc_log_context_destroy(ucsi_ipc_log); - ucsi_ipc_log = NULL; - ucsi_destroy(udev->ucsi); - pmic_glink_unregister_client(udev->client); - return rc; } diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h index fe3039b12f50..ea0b102a484b 100644 --- a/include/linux/soc/qcom/pmic_glink.h +++ b/include/linux/soc/qcom/pmic_glink.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ #ifndef _PMIC_GLINK_H @@ -11,18 +11,27 @@ 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 * @id: Unique id for client for communication * @priv: private data for client - * @callback: callback function 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 (*callback)(void *priv, void *data, size_t len); + int (*msg_cb)(void *priv, void *data, size_t len); + void (*state_cb)(void *priv, enum pmic_glink_state state); }; /**