mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 19:47:08 +02:00
Merge "soc: qcom: add initial version of qti_battery_debug driver"
This commit is contained in:
commit
0e80a8e22e
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
750
drivers/soc/qcom/pmic_glink.c
Normal file
750
drivers/soc/qcom/pmic_glink.c
Normal 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");
|
||||
232
drivers/soc/qcom/qti_battery_debug.c
Normal file
232
drivers/soc/qcom/qti_battery_debug.c
Normal 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");
|
||||
75
include/linux/soc/qcom/pmic_glink.h
Normal file
75
include/linux/soc/qcom/pmic_glink.h
Normal 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
|
||||
Loading…
Reference in New Issue
Block a user