From 2ec0cddc2d806b11842a474822925b1d5fc24735 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Tue, 23 Apr 2019 14:25:23 -0700 Subject: [PATCH 1/3] soc: qcom: Add PMIC Glink driver The PMIC Glink driver provides the interface for clients to communicate over GLink for sending and receiving data to charger firmware that runs on a remote subsystem (e.g. ADSP) which supports charging and gauging. This enables clients to read/write battery charging parameters and get notifications. This is a snapshot taken as of msm-5.10 commit 0f4075248b75 ("soc: qcom: pmic_glink: Use SERVREG_SERVICE_STATE_DOWN for handling PDR"). Change-Id: I1e4c09d1ee10c8c5397f4459afe8d656593d718a Signed-off-by: Subbaraman Narayanamurthy Signed-off-by: David Collins --- drivers/soc/qcom/Kconfig | 12 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pmic_glink.c | 750 ++++++++++++++++++++++++++++ include/linux/soc/qcom/pmic_glink.h | 75 +++ 4 files changed, 838 insertions(+) create mode 100644 drivers/soc/qcom/pmic_glink.c create mode 100644 include/linux/soc/qcom/pmic_glink.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index a3294f4e8612..e90b810ee55c 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -217,6 +217,18 @@ config QCOM_SMSM Say yes here to support the Qualcomm Shared Memory State Machine. The state machine is represented by bits in shared memory. +config QTI_PMIC_GLINK + tristate "Enable support for PMIC GLINK" + depends on RPMSG + select QCOM_RPROC_COMMON + select QCOM_PDR_HELPERS + help + The PMIC Glink driver provides the interface for clients to + communicate over GLink for sending and receiving data to charger + firmware that runs on a remote subsystem like DSP which supports + charging and gauging. + This enables clients to read and write battery charging parameters. + config QCOM_SECURE_BUFFER tristate "Helper functions for secure buffers through TZ" depends on QCOM_SCM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 46368a4e7790..f00df8b590a5 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_QCOM_SMEM) += smem.o obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o +obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o obj-$(CONFIG_QCOM_SPM) += spm.o diff --git a/drivers/soc/qcom/pmic_glink.c b/drivers/soc/qcom/pmic_glink.c new file mode 100644 index 000000000000..2e52872d6002 --- /dev/null +++ b/drivers/soc/qcom/pmic_glink.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "PMIC_GLINK: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * struct pmic_glink_dev - Top level data structure for pmic_glink device + * @rpdev: rpmsg device from rpmsg framework + * @dev: pmic_glink parent device for all child devices + * @debugfs_dir: Debugfs directory handle + * @channel_name: Glink channel name used by rpmsg device + * @client_idr: idr list for the clients + * @client_lock: mutex lock when idr APIs are used on client_idr + * @rx_lock: spinlock to be used when rx_list is modified + * @rx_work: worker for handling rx messages + * @init_work: worker to instantiate child devices under pdev + * @rx_wq: workqueue for rx messages + * @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 + * @pdr_handle: handle to PDR notifier + * @pdr_service_name: protection domain service name + * @pdr_path_name: protection domain path name + * @pdr_state: protection domain service state + */ +struct pmic_glink_dev { + struct rpmsg_device *rpdev; + struct device *dev; + struct dentry *debugfs_dir; + const char *channel_name; + struct idr client_idr; + struct mutex client_lock; + spinlock_t rx_lock; + struct work_struct rx_work; + struct work_struct init_work; + struct workqueue_struct *rx_wq; + 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; + void *pdr_handle; + const char *pdr_service_name; + const char *pdr_path_name; + atomic_t pdr_state; +}; + +/** + * struct pmic_glink_client - pmic_glink client device + * @pgdev: pmic_glink device for the client device + * @name: Client name + * @id: Unique id for client for communication + * @lock: lock for sending data + * @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; + const char *name; + u32 id; + 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 { + struct list_head node; + size_t len; + u8 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 QCOM_SSR_BEFORE_SHUTDOWN: + atomic_set(&pgdev->prev_state, code); + pmic_glink_notify_clients(pgdev, PMIC_GLINK_STATE_DOWN); + break; + case QCOM_SSR_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 void pmic_glink_pdr_notifier_cb(int state, char *service_name, + void *priv) +{ + struct pmic_glink_dev *pgdev = priv; + + pr_debug("PDR state: %x\n", state); + + switch (state) { + case SERVREG_SERVICE_STATE_DOWN: + pr_debug("PD state down for %s\n", pgdev->pdr_service_name); + pmic_glink_notify_clients(pgdev, PMIC_GLINK_STATE_DOWN); + atomic_set(&pgdev->pdr_state, state); + break; + case SERVREG_SERVICE_STATE_UP: + /* + * 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. + */ + pr_debug("PD state up for %s\n", pgdev->pdr_service_name); + break; + default: + break; + } +} + +static struct pmic_glink_dev *get_pmic_glink_from_dev(struct device *dev) +{ + struct pmic_glink_dev *tmp, *pos; + + mutex_lock(&pmic_glink_dev_lock); + list_for_each_entry_safe(pos, tmp, &pmic_glink_dev_list, dev_list) { + if (pos->dev == dev) { + mutex_unlock(&pmic_glink_dev_lock); + return pos; + } + } + mutex_unlock(&pmic_glink_dev_lock); + + return NULL; +} + +static struct pmic_glink_dev *get_pmic_glink_from_rpdev( + struct rpmsg_device *rpdev) +{ + struct pmic_glink_dev *tmp, *pos; + + mutex_lock(&pmic_glink_dev_lock); + list_for_each_entry_safe(pos, tmp, &pmic_glink_dev_list, dev_list) { + if (!strcmp(rpdev->id.name, pos->channel_name)) { + mutex_unlock(&pmic_glink_dev_lock); + return pos; + } + } + mutex_unlock(&pmic_glink_dev_lock); + + return NULL; +} + +/** + * pmic_glink_write() - Send data from client to remote subsystem + * + * @client: Client device pointer that is registered already + * @data: Pointer to data that needs to be sent + * @len: Length of data + * + * Return: 0 if success, negative on error. + */ +int pmic_glink_write(struct pmic_glink_client *client, void *data, + size_t len) +{ + int rc; + + if (!client || !client->pgdev || !client->name) + return -ENODEV; + + if (!client->pgdev->rpdev || !atomic_read(&client->pgdev->state)) { + pr_err("Error in sending data for client %s\n", client->name); + return -ENOTCONN; + } + + mutex_lock(&client->lock); + rc = rpmsg_send(client->pgdev->rpdev->ept, data, len); + mutex_unlock(&client->lock); + + if (rc < 0) + pr_err("Failed to send data [%*ph] for client %s, rc=%d\n", + len, data, client->name, rc); + + if (!rc && client->pgdev->log_enable) { + struct pmic_glink_hdr *hdr = data; + + if (client->pgdev->log_filter == hdr->owner) + pr_info("Tx data: %*ph\n", len, data); + else if (client->pgdev->log_filter == 65535) + pr_info("[%u] Tx data: %*ph\n", hdr->owner, len, data); + } + + return rc; +} +EXPORT_SYMBOL(pmic_glink_write); + +/** + * pmic_glink_register_client() - Register a PMIC Glink client + * + * @dev: Device pointer of child device + * @client_data: Client device data pointer + * + * Return: Valid client pointer upon success or ERR_PTR(-ERRNO) + * + * This function should be called by a client with a unique id, name and + * callback function so that the pmic_glink driver can route the messages + * to the client. + */ +struct pmic_glink_client *pmic_glink_register_client(struct device *dev, + const struct pmic_glink_client_data *client_data) +{ + int rc; + struct pmic_glink_dev *pgdev; + struct pmic_glink_client *client; + + if (!dev || !dev->parent) + return ERR_PTR(-ENODEV); + + if (!client_data->id || !client_data->msg_cb || !client_data->name) + return ERR_PTR(-EINVAL); + + pgdev = get_pmic_glink_from_dev(dev->parent); + if (!pgdev) { + pr_err("Failed to get pmic_glink_dev for %s\n", + client_data->name); + return ERR_PTR(-ENODEV); + } + + if (!atomic_read(&pgdev->state)) { + pr_err("pmic_glink is not up\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return ERR_PTR(-ENOMEM); + + client->name = kstrdup(client_data->name, GFP_KERNEL); + if (!client->name) { + kfree(client); + return ERR_PTR(-ENOMEM); + } + + mutex_init(&client->lock); + client->id = client_data->id; + 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, + GFP_KERNEL); + if (rc < 0) { + pr_err("Error in allocating idr for client %s, rc=%d\n", + client->name, rc); + mutex_unlock(&pgdev->client_lock); + kfree(client->name); + kfree(client); + 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; +} +EXPORT_SYMBOL(pmic_glink_register_client); + +/** + * pmic_glink_unregister_client() - Unregister a PMIC Glink client + * + * @client: Client device pointer that is registered already + * + * Return: 0 if success, negative on error. + * + * This function should be called by a client when it wants to unregister from + * pmic_glink driver. Messages will not be routed to client after this is done. + */ +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); + + kfree(client->name); + kfree(client); + return 0; +} +EXPORT_SYMBOL(pmic_glink_unregister_client); + +static void pmic_glink_rx_callback(struct pmic_glink_dev *pgdev, + struct pmic_glink_buf *pbuf) +{ + struct pmic_glink_client *client; + struct pmic_glink_hdr *hdr; + + hdr = (struct pmic_glink_hdr *)pbuf->buf; + + mutex_lock(&pgdev->client_lock); + client = idr_find(&pgdev->client_idr, hdr->owner); + mutex_unlock(&pgdev->client_lock); + + if (!client || !client->msg_cb) { + pr_err("No client present for %u\n", hdr->owner); + return; + } + + if (pgdev->log_enable) { + if (pgdev->log_filter == hdr->owner) + pr_info("Rx data: %*ph\n", pbuf->len, pbuf->buf); + else if (pgdev->log_filter == 65535) + pr_info("[%u] Rx data: %*ph\n", hdr->owner, pbuf->len, + pbuf->buf); + } + + client->msg_cb(client->priv, pbuf->buf, pbuf->len); +} + +static void pmic_glink_rx_work(struct work_struct *work) +{ + struct pmic_glink_dev *pdev = container_of(work, struct pmic_glink_dev, + rx_work); + struct pmic_glink_buf *pbuf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&pdev->rx_lock, flags); + if (!list_empty(&pdev->rx_list)) { + list_for_each_entry_safe(pbuf, tmp, &pdev->rx_list, node) { + spin_unlock_irqrestore(&pdev->rx_lock, flags); + pmic_glink_rx_callback(pdev, pbuf); + spin_lock_irqsave(&pdev->rx_lock, flags); + list_del(&pbuf->node); + kfree(pbuf); + } + } + spin_unlock_irqrestore(&pdev->rx_lock, flags); +} + +static int pmic_glink_rpmsg_callback(struct rpmsg_device *rpdev, void *data, + int len, void *priv, u32 addr) +{ + struct pmic_glink_dev *pdev = dev_get_drvdata(&rpdev->dev); + struct pmic_glink_buf *pbuf; + unsigned long flags; + + if (len < sizeof(struct pmic_glink_hdr)) { + pr_err("Received length %d less than header size: %zu\n", len, + sizeof(struct pmic_glink_hdr)); + return -EINVAL; + } + + pbuf = kzalloc(sizeof(*pbuf) + len, GFP_ATOMIC); + if (!pbuf) + return -ENOMEM; + + pbuf->len = len; + memcpy(pbuf->buf, data, len); + + spin_lock_irqsave(&pdev->rx_lock, flags); + list_add_tail(&pbuf->node, &pdev->rx_list); + spin_unlock_irqrestore(&pdev->rx_lock, flags); + + queue_work(pdev->rx_wq, &pdev->rx_work); + return 0; +} + +static void pmic_glink_rpmsg_remove(struct rpmsg_device *rpdev) +{ + struct pmic_glink_dev *pgdev = NULL; + + pgdev = get_pmic_glink_from_rpdev(rpdev); + if (!pgdev) { + pr_err("Failed to get pmic_glink_dev for %s\n", rpdev->id.name); + return; + } + + atomic_set(&pgdev->state, 0); + pgdev->rpdev = NULL; + pr_debug("%s removed\n", rpdev->id.name); +} + +static int pmic_glink_rpmsg_probe(struct rpmsg_device *rpdev) +{ + struct pmic_glink_dev *pgdev = NULL; + + pgdev = get_pmic_glink_from_rpdev(rpdev); + if (!pgdev) { + pr_err("Failed to get pmic_glink_dev for %s\n", rpdev->id.name); + return -EPROBE_DEFER; + } + + dev_set_drvdata(&rpdev->dev, pgdev); + pgdev->rpdev = rpdev; + atomic_set(&pgdev->state, 1); + schedule_work(&pgdev->init_work); + pr_debug("%s probed\n", rpdev->id.name); + + return 0; +} + +static const struct rpmsg_device_id pmic_glink_rpmsg_match[] = { + { "PMIC_RTR_ADSP_APPS" }, + { "PMIC_LOGS_ADSP_APPS" }, + {} +}; + +static struct rpmsg_driver pmic_glink_rpmsg_driver = { + .id_table = pmic_glink_rpmsg_match, + .probe = pmic_glink_rpmsg_probe, + .remove = pmic_glink_rpmsg_remove, + .callback = pmic_glink_rpmsg_callback, + .drv = { + .name = "pmic_glink_rpmsg", + }, +}; + +#ifdef CONFIG_DEBUG_FS +static void pmic_glink_add_debugfs(struct pmic_glink_dev *pgdev) +{ + struct dentry *dir, *file; + + dir = debugfs_create_dir(dev_name(pgdev->dev), NULL); + if (IS_ERR(dir)) { + pr_err("Failed to create pmic_glink debugfs directory rc=%d\n", + PTR_ERR(dir)); + return; + } + + debugfs_create_u32("filter", 0600, dir, &pgdev->log_filter); + + file = debugfs_create_bool("enable", 0600, dir, &pgdev->log_enable); + if (IS_ERR(file)) { + pr_err("Failed to create enable debugfs file rc=%d\n", + PTR_ERR(file)); + debugfs_remove_recursive(dir); + return; + } + + pgdev->debugfs_dir = dir; +} +#else +static inline void pmic_glink_add_debugfs(struct pmic_glink_dev *pgdev) +{ } +#endif + +static void pmic_glink_init_work(struct work_struct *work) +{ + struct pmic_glink_dev *pgdev = container_of(work, struct pmic_glink_dev, + init_work); + struct device *dev = pgdev->dev; + int rc; + + if (atomic_read(&pgdev->pdr_state) == SERVREG_SERVICE_STATE_DOWN || + atomic_read(&pgdev->prev_state) == QCOM_SSR_BEFORE_SHUTDOWN) { + pmic_glink_notify_clients(pgdev, PMIC_GLINK_STATE_UP); + atomic_set(&pgdev->pdr_state, SERVREG_SERVICE_STATE_UP); + atomic_set(&pgdev->prev_state, QCOM_SSR_AFTER_POWERUP); + } + + if (pgdev->child_probed) + return; + + rc = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (rc < 0) + pr_err("Failed to create devices rc=%d\n", rc); + else + pgdev->child_probed = true; +} + +static void pmic_glink_dev_add(struct pmic_glink_dev *pgdev) +{ + mutex_lock(&pmic_glink_dev_lock); + list_add(&pgdev->dev_list, &pmic_glink_dev_list); + mutex_unlock(&pmic_glink_dev_lock); +} + +static void pmic_glink_dev_remove(struct pmic_glink_dev *pgdev) +{ + struct pmic_glink_dev *pos, *tmp; + + mutex_lock(&pmic_glink_dev_lock); + list_for_each_entry_safe(pos, tmp, &pmic_glink_dev_list, dev_list) { + if (pos == pgdev) + list_del(&pgdev->dev_list); + } + mutex_unlock(&pmic_glink_dev_lock); +} + +static int pmic_glink_probe(struct platform_device *pdev) +{ + struct pmic_glink_dev *pgdev; + struct device *dev = &pdev->dev; + struct pdr_service *service; + int rc; + + pgdev = devm_kzalloc(dev, sizeof(*pgdev), GFP_KERNEL); + if (!pgdev) + return -ENOMEM; + + rc = of_property_read_string(dev->of_node, "qcom,pmic-glink-channel", + &pgdev->channel_name); + if (rc < 0) { + pr_err("Error in reading qcom,pmic-glink-channel rc=%d\n", rc); + return rc; + } + + if (strlen(pgdev->channel_name) > RPMSG_NAME_SIZE) { + pr_err("pmic glink channel name %s exceeds length\n", + pgdev->channel_name); + return -EINVAL; + } + + of_property_read_string(dev->of_node, "qcom,subsys-name", + &pgdev->subsys_name); + + if (of_find_property(dev->of_node, "qcom,protection-domain", NULL)) { + rc = of_property_read_string_index(dev->of_node, + "qcom,protection-domain", 0, + &pgdev->pdr_service_name); + if (rc) { + pr_err("Failed to get PDR service name rc=%d\n", rc); + return rc; + } else if (strlen(pgdev->pdr_service_name) > + SERVREG_NAME_LENGTH) { + pr_err("PDR service name %s is too long\n", + pgdev->pdr_service_name); + return -EINVAL; + } + + rc = of_property_read_string_index(dev->of_node, + "qcom,protection-domain", 1, + &pgdev->pdr_path_name); + if (rc) { + pr_err("Failed to get PDR path name rc=%d\n", rc); + return rc; + } else if (strlen(pgdev->pdr_path_name) > + SERVREG_NAME_LENGTH) { + pr_err("PDR path name %s is too long\n", + pgdev->pdr_path_name); + return -EINVAL; + } + } + + 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; + } + + 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, QCOM_SSR_BEFORE_POWERUP); + atomic_set(&pgdev->pdr_state, SERVREG_SERVICE_STATE_UNINIT); + + if (pgdev->subsys_name) { + pgdev->ssr_nb.notifier_call = pmic_glink_ssr_notifier_cb; + pgdev->subsys_handle = qcom_register_ssr_notifier( + pgdev->subsys_name, + &pgdev->ssr_nb); + if (IS_ERR(pgdev->subsys_handle)) { + rc = PTR_ERR(pgdev->subsys_handle); + pr_err("Failed in qcom_register_ssr_notifier rc=%d\n", + rc); + goto error_subsys; + } + } + + if (pgdev->pdr_service_name) { + pgdev->pdr_handle = pdr_handle_alloc(pmic_glink_pdr_notifier_cb, + pgdev); + if (IS_ERR(pgdev->pdr_handle)) { + rc = PTR_ERR(pgdev->pdr_handle); + if (rc != -EPROBE_DEFER) + pr_err("Failed in pdr_handle_alloc rc=%d\n", + rc); + goto error_service; + } + + service = pdr_add_lookup(pgdev->pdr_handle, + pgdev->pdr_service_name, pgdev->pdr_path_name); + if (IS_ERR(service) && PTR_ERR(service) != -EALREADY) { + rc = PTR_ERR(service); + pr_err("Failed in pdr_add_lookup rc=%d\n", rc); + goto error_pdr; + } + + pr_debug("Registering PDR for path_name: %s service_name: %s\n", + pgdev->pdr_path_name, pgdev->pdr_service_name); + } + + 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_pdr: + pdr_handle_release(pgdev->pdr_handle); +error_service: + qcom_unregister_ssr_notifier(pgdev->subsys_handle, &pgdev->ssr_nb); +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); + + pdr_handle_release(pgdev->pdr_handle); + qcom_unregister_ssr_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); + idr_destroy(&pgdev->client_idr); + of_platform_depopulate(&pdev->dev); + pgdev->child_probed = false; + pmic_glink_dev_remove(pgdev); + + return 0; +} + +static const struct of_device_id pmic_glink_of_match[] = { + { .compatible = "qcom,pmic-glink" }, + {} +}; +MODULE_DEVICE_TABLE(of, pmic_glink_of_match); + +static struct platform_driver pmic_glink_driver = { + .probe = pmic_glink_probe, + .remove = pmic_glink_remove, + .driver = { + .name = "pmic_glink", + .of_match_table = pmic_glink_of_match, + }, +}; + +static int __init pmic_glink_init(void) +{ + int rc; + + rc = platform_driver_register(&pmic_glink_driver); + if (rc < 0) + return rc; + + return register_rpmsg_driver(&pmic_glink_rpmsg_driver); +} +module_init(pmic_glink_init); + +static void __exit pmic_glink_exit(void) +{ + unregister_rpmsg_driver(&pmic_glink_rpmsg_driver); + platform_driver_unregister(&pmic_glink_driver); +} +module_exit(pmic_glink_exit); + +MODULE_DESCRIPTION("QTI PMIC Glink driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("qcom,pmic-glink"); diff --git a/include/linux/soc/qcom/pmic_glink.h b/include/linux/soc/qcom/pmic_glink.h new file mode 100644 index 000000000000..ea0b102a484b --- /dev/null +++ b/include/linux/soc/qcom/pmic_glink.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. + */ + +#ifndef _PMIC_GLINK_H +#define _PMIC_GLINK_H + +#include + +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 + * @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); +}; + +/** + * struct pmic_glink_hdr - PMIC Glink message header + * @owner: message owner for a client + * @type: message type + * @opcode: message opcode + */ +struct pmic_glink_hdr { + u32 owner; + u32 type; + u32 opcode; +}; + +#if IS_ENABLED(CONFIG_QTI_PMIC_GLINK) +struct pmic_glink_client *pmic_glink_register_client(struct device *dev, + const struct pmic_glink_client_data *client_data); +int pmic_glink_unregister_client(struct pmic_glink_client *client); +int pmic_glink_write(struct pmic_glink_client *client, void *data, + size_t len); +#else +static inline struct pmic_glink_client *pmic_glink_register_client( + struct device *dev, + const struct pmic_glink_client_data *client_data) +{ + return ERR_PTR(-ENODEV); +} + +static inline int pmic_glink_unregister_client(struct pmic_glink_client *client) +{ + return -ENODEV; +} + +static inline int pmic_glink_write(struct pmic_glink_client *client, void *data, + size_t len) +{ + return -ENODEV; +} +#endif + +#endif From f4ec9d39d01f445f45cd7fce79bd0ddd691c1c16 Mon Sep 17 00:00:00 2001 From: Guru Das Srinagesh Date: Tue, 3 Mar 2020 19:09:40 -0800 Subject: [PATCH 2/3] soc: Kconfig: Add QTI_PMIC_GLINK_CLIENT_DEBUG This option enables the generation of debugfs files in PMIC GLINK client drivers that are strictly meant for internal debugging only. Change-Id: I0c0c27b38754dcc694a4b98d6cd29e4a78cd7426 Signed-off-by: Guru Das Srinagesh --- drivers/soc/qcom/Kconfig | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index e90b810ee55c..2121ebfffeb5 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -229,6 +229,16 @@ config QTI_PMIC_GLINK charging and gauging. This enables clients to read and write battery charging parameters. +config QTI_PMIC_GLINK_CLIENT_DEBUG + depends on QTI_PMIC_GLINK && DEBUG_FS + bool "Enable debugfs features in PMIC GLINK client drivers" + help + This option enables the generation of debugfs files in PMIC GLINK + client drivers that are strictly meant for internal debugging only. + Writing to these debug files changes key physical parameters of a + system, which may lead to instability. Therefore, this option should + never be enabled on production devices. + config QCOM_SECURE_BUFFER tristate "Helper functions for secure buffers through TZ" depends on QCOM_SCM From 13fb635d446e2d1f0e28ecce048fb55e2048cfc7 Mon Sep 17 00:00:00 2001 From: Subbaraman Narayanamurthy Date: Thu, 16 Jan 2020 18:25:02 -0800 Subject: [PATCH 3/3] soc: qcom: add initial version of qti_battery_debug driver QTI battery debug driver helps to get logs and debug information by communicating with charger firmware (CHGFW) running on the remote subsystem (e.g. DSP) over PMIC Glink. Currently, it supports getting the device context dump of battery gauging software which is part of CHGFW. Change-Id: I7bb303cf61a97f3087f8c024fc6b37e68e5d1f1e Signed-off-by: Subbaraman Narayanamurthy --- drivers/soc/qcom/Kconfig | 10 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/qti_battery_debug.c | 232 +++++++++++++++++++++++++++ 3 files changed, 243 insertions(+) create mode 100644 drivers/soc/qcom/qti_battery_debug.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 2121ebfffeb5..7ba223837447 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -239,6 +239,16 @@ config QTI_PMIC_GLINK_CLIENT_DEBUG system, which may lead to instability. Therefore, this option should never be enabled on production devices. +config QTI_BATTERY_GLINK_DEBUG + tristate "Enable support for QTI battery glink debug driver" + depends on QTI_PMIC_GLINK + depends on DEBUG_FS + help + Qualcomm Technologies, Inc. battery glink debug driver helps to + obtain debug information for battery charging and gauging over PMIC + Glink from charger and gauging firmware running on a remote subsystem + (e.g. DSP). + config QCOM_SECURE_BUFFER tristate "Helper functions for secure buffers through TZ" depends on QCOM_SCM diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index f00df8b590a5..3a840a883413 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o obj-$(CONFIG_QCOM_SMP2P) += smp2p.o obj-$(CONFIG_QCOM_SMSM) += smsm.o obj-$(CONFIG_QTI_PMIC_GLINK) += pmic_glink.o +obj-$(CONFIG_QTI_BATTERY_GLINK_DEBUG) += qti_battery_debug.o obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o obj-$(CONFIG_QCOM_SPM) += spm.o diff --git a/drivers/soc/qcom/qti_battery_debug.c b/drivers/soc/qcom/qti_battery_debug.c new file mode 100644 index 000000000000..a54930861d41 --- /dev/null +++ b/drivers/soc/qcom/qti_battery_debug.c @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "BATTERY_DBG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* owner/type/opcode for battery debug */ +#define MSG_OWNER_BD 32781 +#define MSG_TYPE_REQ_RESP 1 +#define BD_QBG_DUMP_REQ 0x36 + +/* Generic definitions */ +#define MAX_BUF_LEN (560 * sizeof(u32)) +#define BD_WAIT_TIME_MS 1000 + +struct qbg_context_req_msg { + struct pmic_glink_hdr hdr; +}; + +struct qbg_context_resp_msg { + struct pmic_glink_hdr hdr; + u32 length; + u8 buf[MAX_BUF_LEN]; +}; + +struct battery_dbg_dev { + struct device *dev; + struct pmic_glink_client *client; + struct mutex lock; + struct completion ack; + struct qbg_context_resp_msg qbg_dump; + struct dentry *debugfs_dir; + struct debugfs_blob_wrapper qbg_blob; +}; + +static int battery_dbg_write(struct battery_dbg_dev *bd, void *data, size_t len) +{ + int rc; + + mutex_lock(&bd->lock); + reinit_completion(&bd->ack); + rc = pmic_glink_write(bd->client, data, len); + if (!rc) { + rc = wait_for_completion_timeout(&bd->ack, + msecs_to_jiffies(BD_WAIT_TIME_MS)); + if (!rc) { + pr_err("Error, timed out sending message\n"); + mutex_unlock(&bd->lock); + return -ETIMEDOUT; + } + + rc = 0; + } + mutex_unlock(&bd->lock); + + return rc; +} + +static void handle_qbg_dump_message(struct battery_dbg_dev *bd, + struct qbg_context_resp_msg *resp_msg, + size_t len) +{ + u32 buf_len; + + if (len > sizeof(bd->qbg_dump)) { + pr_err("Incorrect length received: %zu expected: %u\n", len, + sizeof(bd->qbg_dump)); + return; + } + + buf_len = resp_msg->length; + if (buf_len > sizeof(bd->qbg_dump.buf)) { + pr_err("Incorrect buffer length: %u\n", buf_len); + return; + } + + pr_debug("buf length: %u\n", buf_len); + memcpy(bd->qbg_dump.buf, resp_msg->buf, buf_len); + bd->qbg_blob.size = buf_len; + + complete(&bd->ack); +} + +static int battery_dbg_callback(void *priv, void *data, size_t len) +{ + struct pmic_glink_hdr *hdr = data; + struct battery_dbg_dev *bd = priv; + + pr_debug("owner: %u type: %u opcode: %#x len: %zu\n", hdr->owner, + hdr->type, hdr->opcode, len); + + switch (hdr->opcode) { + case BD_QBG_DUMP_REQ: + handle_qbg_dump_message(bd, data, len); + break; + default: + pr_err("Unknown opcode %u\n", hdr->opcode); + break; + } + + return 0; +} + +static int get_qbg_context_write(void *data, u64 val) +{ + struct battery_dbg_dev *bd = data; + struct qbg_context_req_msg req_msg = { { 0 } }; + + req_msg.hdr.owner = MSG_OWNER_BD; + req_msg.hdr.type = MSG_TYPE_REQ_RESP; + req_msg.hdr.opcode = BD_QBG_DUMP_REQ; + + return battery_dbg_write(bd, &req_msg, sizeof(req_msg)); +} + +DEFINE_DEBUGFS_ATTRIBUTE(get_qbg_context_debugfs_ops, NULL, + get_qbg_context_write, "%llu\n"); + +static int battery_dbg_add_debugfs(struct battery_dbg_dev *bd) +{ + struct dentry *bd_dir, *file; + + bd_dir = debugfs_create_dir("battery_debug", NULL); + if (!bd_dir) { + pr_err("Failed to create battery debugfs directory\n"); + return -ENOMEM; + } + + file = debugfs_create_file_unsafe("get_qbg_context", 0200, bd_dir, bd, + &get_qbg_context_debugfs_ops); + if (!file) { + pr_err("Failed to create get_qbg_context debugfs file\n"); + goto error; + } + + bd->qbg_blob.data = bd->qbg_dump.buf; + bd->qbg_blob.size = 0; + file = debugfs_create_blob("qbg_context", 0444, bd_dir, &bd->qbg_blob); + if (!file) { + pr_err("Failed to create qbg_context debugfs file\n"); + goto error; + } + + bd->debugfs_dir = bd_dir; + + return 0; +error: + debugfs_remove_recursive(bd_dir); + return -ENOMEM; +} + +static int battery_dbg_probe(struct platform_device *pdev) +{ + struct battery_dbg_dev *bd; + struct pmic_glink_client_data client_data = { }; + int rc; + + bd = devm_kzalloc(&pdev->dev, sizeof(*bd), GFP_KERNEL); + if (!bd) + return -ENOMEM; + + bd->dev = &pdev->dev; + client_data.id = MSG_OWNER_BD; + client_data.name = "battery_debug"; + client_data.msg_cb = battery_dbg_callback; + client_data.priv = bd; + + bd->client = pmic_glink_register_client(bd->dev, &client_data); + if (IS_ERR(bd->client)) { + rc = PTR_ERR(bd->client); + if (rc != -EPROBE_DEFER) + dev_err(bd->dev, "Error in registering with pmic_glink %d\n", + rc); + return rc; + } + + mutex_init(&bd->lock); + init_completion(&bd->ack); + platform_set_drvdata(pdev, bd); + + rc = battery_dbg_add_debugfs(bd); + if (rc < 0) + goto out; + + return 0; +out: + pmic_glink_unregister_client(bd->client); + return rc; +} + +static int battery_dbg_remove(struct platform_device *pdev) +{ + struct battery_dbg_dev *bd = platform_get_drvdata(pdev); + int rc; + + debugfs_remove_recursive(bd->debugfs_dir); + rc = pmic_glink_unregister_client(bd->client); + if (rc < 0) { + pr_err("Error unregistering from pmic_glink, rc=%d\n", rc); + return rc; + } + + return 0; +} + +static const struct of_device_id battery_dbg_match_table[] = { + { .compatible = "qcom,battery-debug" }, + {}, +}; + +static struct platform_driver battery_dbg_driver = { + .driver = { + .name = "qti_battery_debug", + .of_match_table = battery_dbg_match_table, + }, + .probe = battery_dbg_probe, + .remove = battery_dbg_remove, +}; +module_platform_driver(battery_dbg_driver); + +MODULE_DESCRIPTION("QTI Glink battery debug driver"); +MODULE_LICENSE("GPL v2");