Merge "soc: qcom: add initial version of qti_battery_debug driver"

This commit is contained in:
qctecmdr 2022-05-06 11:38:10 -07:00 committed by Gerrit - the friendly Code Review server
commit 0e80a8e22e
5 changed files with 1091 additions and 0 deletions

View File

@ -217,6 +217,38 @@ 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 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 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

View File

@ -19,6 +19,8 @@ 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_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

View File

@ -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 <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/remoteproc/qcom_rproc.h>
#include <linux/soc/qcom/pdr.h>
#include <linux/soc/qcom/pmic_glink.h>
/**
* 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");

View File

@ -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 <linux/debugfs.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/soc/qcom/pmic_glink.h>
/* 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");

View File

@ -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 <linux/types.h>
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