mirror of
https://github.com/torvalds/linux.git
synced 2026-07-31 03:27:03 +02:00
Merge "soc: qcom: altmode-glink: add SSR support"
This commit is contained in:
commit
ce01c25ee7
|
|
@ -242,13 +242,21 @@ config QTI_PMIC_GLINK_CLIENT_DEBUG
|
|||
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 QTI_ALTMODE_GLINK
|
||||
tristate "Type-C alternate mode over GLINK"
|
||||
depends on QTI_PMIC_GLINK
|
||||
help
|
||||
The Qualcomm Technologies, Inc. Type-C alternate mode driver provides
|
||||
an interface for Type-C alternate mode clients to receive data such
|
||||
as Pin Assignment Notifications from the Type-C stack running on a
|
||||
remote subsystem (e.g. DSP) via the PMIC GLINK interface.
|
||||
|
||||
config QCOM_SECURE_BUFFER
|
||||
tristate "Helper functions for secure buffers through TZ"
|
||||
depends on QCOM_SCM
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ 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_QTI_ALTMODE_GLINK) += altmode-glink.o
|
||||
obj-$(CONFIG_QCOM_SECURE_BUFFER) += secure_buffer.o
|
||||
obj-$(CONFIG_QCOM_SOCINFO) += socinfo.o
|
||||
obj-$(CONFIG_QCOM_SPM) += spm.o
|
||||
|
|
|
|||
608
drivers/soc/qcom/altmode-glink.c
Normal file
608
drivers/soc/qcom/altmode-glink.c
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2020 The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "altmode-glink: %s: " fmt, __func__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/soc/qcom/altmode-glink.h>
|
||||
#include <linux/soc/qcom/pmic_glink.h>
|
||||
|
||||
#define MSG_OWNER_USBC_PAN 32780
|
||||
#define MSG_TYPE_REQ_RESP 1
|
||||
|
||||
#define NOTIFY_PAYLOAD_SIZE 16
|
||||
#define USBC_WRITE_BUFFER_SIZE 8
|
||||
|
||||
#define USBC_CMD_WRITE_REQ 0x15
|
||||
#define USBC_NOTIFY_IND 0x16
|
||||
|
||||
#define ALTMODE_NAME_MAX_LEN 10
|
||||
|
||||
struct usbc_notify_ind_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
u8 payload[NOTIFY_PAYLOAD_SIZE];
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
struct usbc_write_buffer_req_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
u8 buf[USBC_WRITE_BUFFER_SIZE];
|
||||
u32 reserved;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct altmode_dev
|
||||
* Definition of an altmode device.
|
||||
*
|
||||
* @dev: Altmode parent device for all client devices
|
||||
* @name: Short descriptive name of this altmode device
|
||||
* @pgclient: PMIC GLINK client to talk to remote subsystem
|
||||
* @client_idr: idr list for altmode clients
|
||||
* @client_lock: mutex protecting changes to client_idr
|
||||
* @d_node: Linked list node to string together multiple amdev's
|
||||
* @client_list: Linked list head keeping track of this device's clients
|
||||
* @pan_en_sent: Flag to ensure PAN Enable msg is sent only once
|
||||
* @send_pan_en_work: To schedule the sending of the PAN Enable message
|
||||
* @probe_notifier: Inform clients of altmode probe completion
|
||||
*/
|
||||
struct altmode_dev {
|
||||
struct device *dev;
|
||||
char name[ALTMODE_NAME_MAX_LEN];
|
||||
struct pmic_glink_client *pgclient;
|
||||
struct idr client_idr;
|
||||
struct mutex client_lock;
|
||||
struct list_head d_node;
|
||||
struct list_head client_list;
|
||||
atomic_t pan_en_sent;
|
||||
struct delayed_work send_pan_en_work;
|
||||
struct raw_notifier_head probe_notifier;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct altmode_client
|
||||
* Definition of a client of an altmode device.
|
||||
*
|
||||
* @amdev: Parent altmode device of this client
|
||||
* @data: Supplied by client driver during registration
|
||||
* @c_node: Linked list node for parent altmode device's client list
|
||||
* @port_index: Type-C port index assigned by remote subystem
|
||||
*/
|
||||
struct altmode_client {
|
||||
struct altmode_dev *amdev;
|
||||
struct altmode_client_data data;
|
||||
struct list_head c_node;
|
||||
u8 port_index;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct probe_notify_node
|
||||
* Linked list node to keep track of altmode clients who, by design,
|
||||
* register with the altmode framework before altmode probes.
|
||||
*
|
||||
* @amdev_name: Name of the altmode device client wants to bind to
|
||||
* @nb: Client's notifier block
|
||||
* @node: Linked list node for probe_notify_list
|
||||
*/
|
||||
struct probe_notify_node {
|
||||
char *amdev_name;
|
||||
struct notifier_block *nb;
|
||||
struct list_head node;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct list_head amdev_list
|
||||
* List of altmode devices currently using this driver.
|
||||
*/
|
||||
static LIST_HEAD(amdev_list);
|
||||
static DEFINE_MUTEX(amdev_lock);
|
||||
|
||||
/**
|
||||
* struct list_head probe_notify_list
|
||||
* List of altmode clients that register to get notified upon probe
|
||||
* completion. This list is traversed and clients are removed from this
|
||||
* list after they have been registered with the altmode device's
|
||||
* raw_notifier_head.
|
||||
*/
|
||||
static LIST_HEAD(probe_notify_list);
|
||||
static DEFINE_MUTEX(notify_lock);
|
||||
|
||||
static struct altmode_dev *get_amdev_from_dev(struct device *dev)
|
||||
{
|
||||
struct altmode_dev *pos, *tmp;
|
||||
|
||||
mutex_lock(&amdev_lock);
|
||||
list_for_each_entry_safe(pos, tmp, &amdev_list, d_node) {
|
||||
if (pos->dev == dev) {
|
||||
mutex_unlock(&amdev_lock);
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&amdev_lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int __altmode_send_data(struct altmode_dev *amdev, void *data,
|
||||
size_t len)
|
||||
{
|
||||
int rc;
|
||||
struct usbc_write_buffer_req_msg msg = { { 0 } };
|
||||
|
||||
if (len > sizeof(msg.buf)) {
|
||||
pr_err("len %zu exceeds msg buf's size: %zu\n",
|
||||
len, USBC_WRITE_BUFFER_SIZE);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
msg.hdr.owner = MSG_OWNER_USBC_PAN;
|
||||
msg.hdr.type = MSG_TYPE_REQ_RESP;
|
||||
msg.hdr.opcode = USBC_CMD_WRITE_REQ;
|
||||
|
||||
memcpy(msg.buf, data, len);
|
||||
|
||||
rc = pmic_glink_write(amdev->pgclient, &msg, sizeof(msg));
|
||||
if (rc < 0)
|
||||
pr_err("Error in sending message: %d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* altmode_register_notifier()
|
||||
* Register to be notified when altmode probes.
|
||||
*
|
||||
* @amdev_name: The altmode device being registered with by client
|
||||
* @nb: Notifier block to get notified upon probe completion
|
||||
*
|
||||
* Returns: 0 upon success, negative upon errors.
|
||||
*/
|
||||
int altmode_register_notifier(const char *amdev_name, struct notifier_block *nb)
|
||||
{
|
||||
struct probe_notify_node *notify_node;
|
||||
|
||||
if (!amdev_name || !nb)
|
||||
return -EINVAL;
|
||||
|
||||
notify_node = kzalloc(sizeof(*notify_node), GFP_KERNEL);
|
||||
if (!notify_node)
|
||||
return -ENOMEM;
|
||||
|
||||
notify_node->nb = nb;
|
||||
notify_node->amdev_name = kstrdup(amdev_name, GFP_KERNEL);
|
||||
if (!notify_node->amdev_name) {
|
||||
kfree(notify_node);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
mutex_lock(¬ify_lock);
|
||||
list_add(¬ify_node->node, &probe_notify_list);
|
||||
mutex_unlock(¬ify_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(altmode_register_notifier);
|
||||
|
||||
/**
|
||||
* altmode_deregister_notifier()
|
||||
* Deregister probe completion notifier.
|
||||
*
|
||||
* @client: The altmode client obtained during registration
|
||||
* @nb: Notifier block used for registration
|
||||
*
|
||||
* Returns: 0 upon success, negative upon errors.
|
||||
*/
|
||||
int altmode_deregister_notifier(struct altmode_client *client,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
struct altmode_dev *amdev;
|
||||
|
||||
if (!client || !nb)
|
||||
return -EINVAL;
|
||||
|
||||
amdev = client->amdev;
|
||||
if (!amdev)
|
||||
return -ENODEV;
|
||||
|
||||
return raw_notifier_chain_unregister(&amdev->probe_notifier, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(altmode_deregister_notifier);
|
||||
|
||||
/**
|
||||
* altmode_send_data()
|
||||
* Send data from altmode client to remote subsystem.
|
||||
*
|
||||
* @client: Parent altmode device of this client
|
||||
* @data: Data to be sent
|
||||
* @len: Length in bytes of the data to be sent
|
||||
*
|
||||
* Returns: 0 upon success, -EINVAL if len exceeds message buffer's
|
||||
* capacity, and other negative error codes as appropriate.
|
||||
*/
|
||||
int altmode_send_data(struct altmode_client *client, void *data,
|
||||
size_t len)
|
||||
{
|
||||
struct altmode_dev *amdev = client->amdev;
|
||||
|
||||
return __altmode_send_data(amdev, data, len);
|
||||
}
|
||||
EXPORT_SYMBOL(altmode_send_data);
|
||||
|
||||
/**
|
||||
* altmode_register_client()
|
||||
* Register with altmode to receive PMIC GLINK messages from remote
|
||||
* subsystem.
|
||||
*
|
||||
* @dev: Device of the parent altmode (platform) device
|
||||
* @client_data: Details identifying altmode client uniquely
|
||||
*
|
||||
* Returns: Valid altmode client pointer upon success, ERR_PTRs
|
||||
* upon errors.
|
||||
*
|
||||
* Notes: client_data should contain a unique SVID.
|
||||
*/
|
||||
struct altmode_client *altmode_register_client(struct device *dev,
|
||||
const struct altmode_client_data *client_data)
|
||||
{
|
||||
int rc;
|
||||
struct altmode_dev *amdev;
|
||||
struct altmode_client *amclient;
|
||||
|
||||
if (!dev || !dev->parent)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
if (!client_data->name || !client_data->priv || !client_data->callback
|
||||
|| !client_data->svid)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
amdev = get_amdev_from_dev(dev);
|
||||
if (!amdev) {
|
||||
pr_err("No alt mode device exists for %s\n", client_data->name);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
amclient = kzalloc(sizeof(*amclient), GFP_KERNEL);
|
||||
if (!amclient)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
amclient->amdev = amdev;
|
||||
amclient->port_index = U8_MAX; /* invalid */
|
||||
amclient->data.svid = client_data->svid;
|
||||
amclient->data.priv = client_data->priv;
|
||||
amclient->data.callback = client_data->callback;
|
||||
amclient->data.name = kstrdup(client_data->name, GFP_KERNEL);
|
||||
if (!amclient->data.name) {
|
||||
kfree(amclient);
|
||||
return ERR_PTR(-ENOMEM);
|
||||
}
|
||||
|
||||
mutex_lock(&amdev->client_lock);
|
||||
rc = idr_alloc(&amdev->client_idr, amclient, amclient->data.svid,
|
||||
amclient->data.svid + 1, GFP_KERNEL);
|
||||
if (rc < 0) {
|
||||
pr_err("Error in allocating idr for client %s: %d\n",
|
||||
client_data->name, rc);
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
kfree(amclient->data.name);
|
||||
kfree(amclient);
|
||||
return ERR_PTR(rc);
|
||||
}
|
||||
|
||||
list_add(&amclient->c_node, &amdev->client_list);
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
|
||||
if (!atomic_read(&amdev->pan_en_sent))
|
||||
schedule_delayed_work(&amdev->send_pan_en_work,
|
||||
msecs_to_jiffies(20));
|
||||
|
||||
return amclient;
|
||||
}
|
||||
EXPORT_SYMBOL(altmode_register_client);
|
||||
|
||||
/**
|
||||
* altmode_deregister_client()
|
||||
* Deregister altmode client to stop receiving PMIC GLINK messages
|
||||
* specific to its SVID from remote subsystem.
|
||||
*
|
||||
* @client: Client returned by altmode_register_client()
|
||||
*
|
||||
* Returns: 0 upon success, negative upon errors.
|
||||
*
|
||||
* Notes: This does not stop the transmission of the messages by
|
||||
* the remote subsystem - only the reception of them by
|
||||
* the client.
|
||||
*/
|
||||
int altmode_deregister_client(struct altmode_client *client)
|
||||
{
|
||||
struct altmode_dev *amdev;
|
||||
struct altmode_client *pos, *tmp;
|
||||
|
||||
if (!client || !client->amdev)
|
||||
return -ENODEV;
|
||||
|
||||
amdev = client->amdev;
|
||||
|
||||
mutex_lock(&amdev->client_lock);
|
||||
idr_remove(&amdev->client_idr, client->data.svid);
|
||||
|
||||
list_for_each_entry_safe(pos, tmp, &amdev->client_list, c_node) {
|
||||
if (pos == client)
|
||||
list_del(&pos->c_node);
|
||||
}
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
|
||||
kfree(client->data.name);
|
||||
kfree(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(altmode_deregister_client);
|
||||
|
||||
static void altmode_send_pan_en(struct work_struct *work)
|
||||
{
|
||||
int rc;
|
||||
u32 enable_msg = ALTMODE_PAN_EN;
|
||||
struct altmode_dev *amdev = container_of(work, struct altmode_dev,
|
||||
send_pan_en_work.work);
|
||||
|
||||
rc = __altmode_send_data(amdev, &enable_msg, sizeof(enable_msg));
|
||||
if (rc < 0) {
|
||||
pr_err("Failed to send PAN EN: %d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
atomic_set(&amdev->pan_en_sent, 1);
|
||||
pr_debug("Sent PAN EN\n");
|
||||
}
|
||||
|
||||
static int altmode_send_ack(struct altmode_dev *amdev, u8 port_index)
|
||||
{
|
||||
int rc;
|
||||
struct altmode_pan_ack_msg ack;
|
||||
|
||||
ack.cmd_type = ALTMODE_PAN_ACK;
|
||||
ack.port_index = port_index;
|
||||
|
||||
rc = __altmode_send_data(amdev, &ack, sizeof(ack));
|
||||
if (rc < 0) {
|
||||
pr_err("port %u: Failed to send PAN ACK\n", port_index);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pr_debug("port %d: Sent PAN ACK\n", port_index);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void altmode_state_cb(void *priv, enum pmic_glink_state state)
|
||||
{
|
||||
struct altmode_dev *amdev = priv;
|
||||
|
||||
pr_debug("state: %d\n", state);
|
||||
|
||||
switch (state) {
|
||||
case PMIC_GLINK_STATE_DOWN:
|
||||
/* As of now, nothing to do */
|
||||
break;
|
||||
case PMIC_GLINK_STATE_UP:
|
||||
mutex_lock(&amdev->client_lock);
|
||||
if (!list_empty(&amdev->client_list))
|
||||
schedule_delayed_work(&amdev->send_pan_en_work,
|
||||
msecs_to_jiffies(20));
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#define USBC_NOTIFY_IND_MASK GENMASK(7, 0)
|
||||
#define GET_OP(opcode) (opcode & USBC_NOTIFY_IND_MASK)
|
||||
#define GET_SVID(opcode) (opcode >> 16)
|
||||
|
||||
static int altmode_callback(void *priv, void *data, size_t len)
|
||||
{
|
||||
u16 svid, op;
|
||||
struct usbc_notify_ind_msg *notify_msg = data;
|
||||
struct pmic_glink_hdr *hdr = ¬ify_msg->hdr;
|
||||
struct altmode_dev *amdev = priv;
|
||||
struct altmode_client *amclient;
|
||||
u8 port_index;
|
||||
|
||||
pr_debug("len: %zu owner: %u type: %u opcode %04x\n", len, hdr->owner,
|
||||
hdr->type, hdr->opcode);
|
||||
|
||||
/*
|
||||
* For DisplayPort alt mode, the hdr->opcode is designed as follows:
|
||||
*
|
||||
* hdr->opcode = (svid << 16) | USBC_NOTIFY_IND
|
||||
*/
|
||||
op = GET_OP(hdr->opcode);
|
||||
svid = GET_SVID(hdr->opcode);
|
||||
port_index = notify_msg->payload[0];
|
||||
|
||||
mutex_lock(&amdev->client_lock);
|
||||
amclient = idr_find(&amdev->client_idr, svid);
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
|
||||
if (op == USBC_NOTIFY_IND) {
|
||||
if (!amclient) {
|
||||
pr_debug("No client associated with SVID %#x\n", svid);
|
||||
altmode_send_ack(amdev, port_index);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (amclient->port_index == U8_MAX)
|
||||
amclient->port_index = port_index;
|
||||
|
||||
pr_debug("Payload: %*ph\n", NOTIFY_PAYLOAD_SIZE,
|
||||
notify_msg->payload);
|
||||
amclient->data.callback(amclient->data.priv,
|
||||
notify_msg->payload, len);
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void altmode_gather_clients(struct altmode_dev *amdev)
|
||||
{
|
||||
struct probe_notify_node *pos, *tmp;
|
||||
|
||||
mutex_lock(¬ify_lock);
|
||||
list_for_each_entry_safe(pos, tmp, &probe_notify_list, node) {
|
||||
if (!strcmp(pos->amdev_name, amdev->name)) {
|
||||
raw_notifier_chain_register(&amdev->probe_notifier,
|
||||
pos->nb);
|
||||
/*
|
||||
* Client's nb has been added to amdev's notifier, so
|
||||
* it may be removed from the global list.
|
||||
*/
|
||||
list_del(&pos->node);
|
||||
kfree(pos->amdev_name);
|
||||
kfree(pos);
|
||||
}
|
||||
}
|
||||
mutex_unlock(¬ify_lock);
|
||||
}
|
||||
|
||||
static void altmode_notify_clients(struct altmode_dev *amdev,
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
altmode_gather_clients(amdev);
|
||||
raw_notifier_call_chain(&amdev->probe_notifier, 0, pdev);
|
||||
}
|
||||
|
||||
static void altmode_device_add(struct altmode_dev *amdev)
|
||||
{
|
||||
mutex_lock(&amdev_lock);
|
||||
list_add(&amdev->d_node, &amdev_list);
|
||||
mutex_unlock(&amdev_lock);
|
||||
}
|
||||
|
||||
static int altmode_probe(struct platform_device *pdev)
|
||||
{
|
||||
int rc;
|
||||
const char *str = NULL;
|
||||
struct altmode_dev *amdev;
|
||||
struct pmic_glink_client_data pgclient_data = { };
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
amdev = devm_kzalloc(&pdev->dev, sizeof(*amdev), GFP_KERNEL);
|
||||
if (!amdev)
|
||||
return -ENOMEM;
|
||||
|
||||
rc = of_property_read_string(dev->of_node, "qcom,altmode-name",
|
||||
&str);
|
||||
if (rc < 0) {
|
||||
dev_err(dev, "No altmode device name specified: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!str || (strlen(str) >= ALTMODE_NAME_MAX_LEN) ||
|
||||
!str_has_prefix(str, "altmode_")) {
|
||||
dev_err(dev, "Incorrect altmode device name format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
amdev->dev = dev;
|
||||
strlcpy(amdev->name, str, ALTMODE_NAME_MAX_LEN);
|
||||
|
||||
RAW_INIT_NOTIFIER_HEAD(&amdev->probe_notifier);
|
||||
|
||||
mutex_init(&amdev->client_lock);
|
||||
idr_init(&amdev->client_idr);
|
||||
INIT_DELAYED_WORK(&amdev->send_pan_en_work, altmode_send_pan_en);
|
||||
INIT_LIST_HEAD(&amdev->d_node);
|
||||
INIT_LIST_HEAD(&amdev->client_list);
|
||||
|
||||
pgclient_data.id = MSG_OWNER_USBC_PAN;
|
||||
pgclient_data.name = "altmode";
|
||||
pgclient_data.msg_cb = altmode_callback;
|
||||
pgclient_data.priv = amdev;
|
||||
pgclient_data.state_cb = altmode_state_cb;
|
||||
|
||||
amdev->pgclient = pmic_glink_register_client(amdev->dev,
|
||||
&pgclient_data);
|
||||
if (IS_ERR(amdev->pgclient)) {
|
||||
rc = PTR_ERR(amdev->pgclient);
|
||||
if (rc != -EPROBE_DEFER)
|
||||
dev_err(dev, "Error in pmic_glink registration: %d\n",
|
||||
rc);
|
||||
goto error_register;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, amdev);
|
||||
|
||||
altmode_device_add(amdev);
|
||||
altmode_notify_clients(amdev, pdev);
|
||||
|
||||
return 0;
|
||||
|
||||
error_register:
|
||||
idr_destroy(&amdev->client_idr);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static void altmode_device_remove(struct altmode_dev *amdev)
|
||||
{
|
||||
struct altmode_dev *pos, *tmp;
|
||||
|
||||
atomic_set(&amdev->pan_en_sent, 0);
|
||||
|
||||
mutex_lock(&amdev_lock);
|
||||
list_for_each_entry_safe(pos, tmp, &amdev_list, d_node) {
|
||||
if (pos == amdev)
|
||||
list_del(&pos->d_node);
|
||||
}
|
||||
mutex_unlock(&amdev_lock);
|
||||
}
|
||||
|
||||
static int altmode_remove(struct platform_device *pdev)
|
||||
{
|
||||
int rc;
|
||||
struct altmode_dev *amdev = platform_get_drvdata(pdev);
|
||||
struct altmode_client *client, *tmp;
|
||||
|
||||
cancel_delayed_work_sync(&amdev->send_pan_en_work);
|
||||
idr_destroy(&amdev->client_idr);
|
||||
|
||||
mutex_lock(&amdev->client_lock);
|
||||
list_for_each_entry_safe(client, tmp, &amdev->client_list, c_node)
|
||||
list_del(&client->c_node);
|
||||
mutex_unlock(&amdev->client_lock);
|
||||
|
||||
altmode_device_remove(amdev);
|
||||
|
||||
rc = pmic_glink_unregister_client(amdev->pgclient);
|
||||
if (rc < 0)
|
||||
dev_err(amdev->dev, "Error in pmic_glink de-registration: %d\n",
|
||||
rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static const struct of_device_id altmode_match_table[] = {
|
||||
{ .compatible = "qcom,altmode-glink" },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver altmode_driver = {
|
||||
.driver = {
|
||||
.name = "altmode-glink",
|
||||
.of_match_table = altmode_match_table,
|
||||
},
|
||||
.probe = altmode_probe,
|
||||
.remove = altmode_remove,
|
||||
};
|
||||
module_platform_driver(altmode_driver);
|
||||
|
||||
MODULE_DESCRIPTION("QTI Type-C Alt Mode over GLINK");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "BATTERY_DBG: %s: " fmt, __func__
|
||||
|
|
@ -14,17 +14,50 @@
|
|||
#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
|
||||
/* owner/type/opcodes for battery debug */
|
||||
#define MSG_OWNER_BD 32781
|
||||
#define MSG_TYPE_REQ_RESP 1
|
||||
#define BD_GET_AGGREGATOR_INFO_REQ 0x15
|
||||
#define BD_OVERWRITE_VOTABLE_REQ 0x16
|
||||
#define BD_GET_VOTABLE_REQ 0x17
|
||||
#define BD_QBG_DUMP_REQ 0x36
|
||||
|
||||
/* Generic definitions */
|
||||
#define MAX_BUF_LEN (560 * sizeof(u32))
|
||||
#define MAX_BUF_LEN SZ_4K
|
||||
#define BD_WAIT_TIME_MS 1000
|
||||
|
||||
#define MAX_NUM_VOTABLES 12
|
||||
#define MAX_NUM_VOTERS 32
|
||||
#define MAX_NAME_LEN 12
|
||||
|
||||
struct all_votables_data {
|
||||
u32 num_votables;
|
||||
u32 num_voters;
|
||||
char votables[MAX_NUM_VOTABLES][MAX_NAME_LEN];
|
||||
char voters[MAX_NUM_VOTERS][MAX_NAME_LEN];
|
||||
};
|
||||
|
||||
struct votable_data {
|
||||
u32 votable_id;
|
||||
u32 eff_val;
|
||||
u8 voter_ids[MAX_NUM_VOTERS]; /* unused */
|
||||
u32 votes[MAX_NUM_VOTERS];
|
||||
u32 active_voter_mask;
|
||||
u32 eff_voter;
|
||||
u32 override_voter;
|
||||
};
|
||||
|
||||
struct votable {
|
||||
char *name;
|
||||
u32 id;
|
||||
struct battery_dbg_dev *bd;
|
||||
struct votable_data data;
|
||||
u32 override_val;
|
||||
};
|
||||
|
||||
struct qbg_context_req_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
u32 battery_cell_id;
|
||||
};
|
||||
|
||||
struct qbg_context_resp_msg {
|
||||
|
|
@ -33,6 +66,31 @@ struct qbg_context_resp_msg {
|
|||
u8 buf[MAX_BUF_LEN];
|
||||
};
|
||||
|
||||
struct votables_list_req_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
};
|
||||
|
||||
struct votables_list_resp_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
struct all_votables_data all_data;
|
||||
};
|
||||
|
||||
struct votable_req_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
u32 votable_id;
|
||||
};
|
||||
|
||||
struct votable_resp_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
struct votable_data v_data;
|
||||
};
|
||||
|
||||
struct override_req_msg {
|
||||
struct pmic_glink_hdr hdr;
|
||||
u32 votable_id;
|
||||
u32 override_val;
|
||||
};
|
||||
|
||||
struct battery_dbg_dev {
|
||||
struct device *dev;
|
||||
struct pmic_glink_client *client;
|
||||
|
|
@ -41,6 +99,10 @@ struct battery_dbg_dev {
|
|||
struct qbg_context_resp_msg qbg_dump;
|
||||
struct dentry *debugfs_dir;
|
||||
struct debugfs_blob_wrapper qbg_blob;
|
||||
struct all_votables_data all_data;
|
||||
struct votable *votable;
|
||||
u8 override_voter_id;
|
||||
u32 battery_cell_id;
|
||||
};
|
||||
|
||||
static int battery_dbg_write(struct battery_dbg_dev *bd, void *data, size_t len)
|
||||
|
|
@ -91,6 +153,89 @@ static void handle_qbg_dump_message(struct battery_dbg_dev *bd,
|
|||
complete(&bd->ack);
|
||||
}
|
||||
|
||||
static void handle_override_message(struct battery_dbg_dev *bd, void *unused,
|
||||
size_t len)
|
||||
{
|
||||
pr_debug("override succeeded\n");
|
||||
complete(&bd->ack);
|
||||
}
|
||||
|
||||
static void handle_get_votable_message(struct battery_dbg_dev *bd,
|
||||
struct votable_resp_msg *resp_msg,
|
||||
size_t len)
|
||||
{
|
||||
u32 id = resp_msg->v_data.votable_id;
|
||||
|
||||
if (len != sizeof(*resp_msg)) {
|
||||
pr_err("Expected data length: %zu, received: %zu\n",
|
||||
sizeof(*resp_msg), len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (id >= MAX_NUM_VOTABLES) {
|
||||
pr_err("Votable id %u exceeds max %d\n", id, MAX_NUM_VOTABLES);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp_msg->v_data.active_voter_mask &&
|
||||
resp_msg->v_data.eff_voter >= MAX_NUM_VOTERS) {
|
||||
pr_err("Effective voter id %u exceeds max %d\n",
|
||||
resp_msg->v_data.eff_voter, MAX_NUM_VOTERS);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&bd->votable[id].data, &resp_msg->v_data,
|
||||
sizeof(resp_msg->v_data));
|
||||
|
||||
complete(&bd->ack);
|
||||
}
|
||||
|
||||
#define OVERRIDE_VOTER_NAME "glink"
|
||||
static void handle_get_votables_list_message(struct battery_dbg_dev *bd,
|
||||
struct votables_list_resp_msg *resp_msg,
|
||||
size_t len)
|
||||
{
|
||||
u8 i;
|
||||
|
||||
if (len != sizeof(*resp_msg)) {
|
||||
pr_err("Expected data length: %zu, received: %zu\n",
|
||||
sizeof(*resp_msg), len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp_msg->all_data.num_votables >= MAX_NUM_VOTABLES) {
|
||||
pr_err("Num votables %u exceeds max %d\n",
|
||||
resp_msg->all_data.num_votables, MAX_NUM_VOTABLES);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resp_msg->all_data.num_voters >= MAX_NUM_VOTERS) {
|
||||
pr_err("Num voters %u exceeds max %d\n",
|
||||
resp_msg->all_data.num_voters, MAX_NUM_VOTERS);
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(&bd->all_data, &resp_msg->all_data, sizeof(resp_msg->all_data));
|
||||
|
||||
for (i = 0; i < MAX_NUM_VOTABLES; i++)
|
||||
bd->all_data.votables[i][MAX_NAME_LEN - 1] = '\0';
|
||||
|
||||
for (i = 0; i < MAX_NUM_VOTERS; i++)
|
||||
bd->all_data.voters[i][MAX_NAME_LEN - 1] = '\0';
|
||||
|
||||
if (!bd->override_voter_id) {
|
||||
for (i = 0; i < MAX_NUM_VOTERS; i++) {
|
||||
if (!strcmp(bd->all_data.voters[i],
|
||||
OVERRIDE_VOTER_NAME)) {
|
||||
bd->override_voter_id = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
complete(&bd->ack);
|
||||
}
|
||||
|
||||
static int battery_dbg_callback(void *priv, void *data, size_t len)
|
||||
{
|
||||
struct pmic_glink_hdr *hdr = data;
|
||||
|
|
@ -103,6 +248,15 @@ static int battery_dbg_callback(void *priv, void *data, size_t len)
|
|||
case BD_QBG_DUMP_REQ:
|
||||
handle_qbg_dump_message(bd, data, len);
|
||||
break;
|
||||
case BD_GET_AGGREGATOR_INFO_REQ:
|
||||
handle_get_votables_list_message(bd, data, len);
|
||||
break;
|
||||
case BD_GET_VOTABLE_REQ:
|
||||
handle_get_votable_message(bd, data, len);
|
||||
break;
|
||||
case BD_OVERWRITE_VOTABLE_REQ:
|
||||
handle_override_message(bd, data, len);
|
||||
break;
|
||||
default:
|
||||
pr_err("Unknown opcode %u\n", hdr->opcode);
|
||||
break;
|
||||
|
|
@ -111,6 +265,389 @@ static int battery_dbg_callback(void *priv, void *data, size_t len)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int battery_dbg_request_read_votable(struct battery_dbg_dev *bd,
|
||||
u32 id)
|
||||
{
|
||||
struct votable_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_GET_VOTABLE_REQ;
|
||||
req_msg.votable_id = id;
|
||||
|
||||
return battery_dbg_write(bd, &req_msg, sizeof(req_msg));
|
||||
}
|
||||
|
||||
#ifdef CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG
|
||||
static int battery_dbg_request_override(struct battery_dbg_dev *bd, u32 id,
|
||||
u32 val)
|
||||
{
|
||||
struct override_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_OVERWRITE_VOTABLE_REQ;
|
||||
req_msg.votable_id = id;
|
||||
req_msg.override_val = val;
|
||||
|
||||
pr_debug("requesting override of %s with value %u\n",
|
||||
bd->votable[id].name, val);
|
||||
|
||||
return battery_dbg_write(bd, &req_msg, sizeof(req_msg));
|
||||
}
|
||||
#endif
|
||||
|
||||
static int active_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
seq_printf(s, "%#x\n", voter_mask);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(active);
|
||||
|
||||
static int winvote_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc, winvote;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
winvote = voter_mask ? v->data.eff_val : -EINVAL;
|
||||
|
||||
seq_printf(s, "%d\n", winvote);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(winvote);
|
||||
|
||||
static int winner_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc;
|
||||
char *winner;
|
||||
u32 eff_voter;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
if (voter_mask) {
|
||||
eff_voter = v->data.eff_voter;
|
||||
winner = bd->all_data.voters[eff_voter];
|
||||
} else {
|
||||
winner = "";
|
||||
}
|
||||
|
||||
seq_printf(s, "%s\n", winner);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(winner);
|
||||
|
||||
static int voters_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc, i;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS)
|
||||
seq_printf(s, "%s ", bd->all_data.voters[i]);
|
||||
seq_puts(s, "\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(voters);
|
||||
|
||||
static int votes_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc, i;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS)
|
||||
seq_printf(s, "%d ", v->data.votes[i]);
|
||||
seq_puts(s, "\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(votes);
|
||||
|
||||
static int status_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
int rc, i, winvote;
|
||||
char *winner;
|
||||
u32 eff_voter;
|
||||
unsigned long voter_mask;
|
||||
struct votable *v = s->private;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
if (!voter_mask) {
|
||||
seq_puts(s, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
winvote = v->data.eff_val;
|
||||
eff_voter = v->data.eff_voter;
|
||||
winner = bd->all_data.voters[eff_voter];
|
||||
|
||||
for_each_set_bit(i, &voter_mask, MAX_NUM_VOTERS)
|
||||
seq_printf(s, " %-*s: %-*s: %d\n", MAX_NAME_LEN,
|
||||
v->name, MAX_NAME_LEN, bd->all_data.voters[i],
|
||||
v->data.votes[i]);
|
||||
seq_printf(s, "EFFECTIVE: %-*s: %-*s: %d\n", MAX_NAME_LEN, v->name,
|
||||
MAX_NAME_LEN, winner, winvote);
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_SHOW_ATTRIBUTE(status);
|
||||
|
||||
#ifdef CONFIG_QTI_PMIC_GLINK_CLIENT_DEBUG
|
||||
static int override_get(void *data, u64 *val)
|
||||
{
|
||||
int rc;
|
||||
struct votable *v = data;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
unsigned long voter_mask;
|
||||
|
||||
rc = battery_dbg_request_read_votable(bd, v->id);
|
||||
if (rc) {
|
||||
pr_err("Failed to read %s votable: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
voter_mask = v->data.active_voter_mask;
|
||||
|
||||
/* Show override voter's vote only if override voter is active */
|
||||
if (test_bit(bd->override_voter_id, &voter_mask))
|
||||
*val = v->data.votes[bd->override_voter_id];
|
||||
else
|
||||
*val = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int override_set(void *data, u64 val)
|
||||
{
|
||||
int rc;
|
||||
struct votable *v = data;
|
||||
struct battery_dbg_dev *bd = v->bd;
|
||||
u32 set = val;
|
||||
|
||||
rc = battery_dbg_request_override(bd, v->id, set);
|
||||
if (rc) {
|
||||
pr_err("%s override request failed: %d\n", v->name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
DEFINE_DEBUGFS_ATTRIBUTE(override_fops, override_get, override_set, "%llu\n");
|
||||
|
||||
static int battery_dbg_create_override_file(struct votable *v,
|
||||
struct dentry *votable_dir)
|
||||
{
|
||||
return PTR_ERR_OR_ZERO(debugfs_create_file_unsafe("override", 0600,
|
||||
votable_dir, v, &override_fops));
|
||||
|
||||
}
|
||||
#else
|
||||
static int battery_dbg_create_override_file(struct votable *v,
|
||||
struct dentry *votable_dir)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int battery_dbg_create_votable(struct battery_dbg_dev *bd,
|
||||
struct dentry *votables_root_dir,
|
||||
u32 id)
|
||||
{
|
||||
int rc;
|
||||
char *v_name = bd->all_data.votables[id];
|
||||
struct dentry *votable_dir;
|
||||
|
||||
votable_dir = debugfs_create_dir(v_name, votables_root_dir);
|
||||
if (IS_ERR(votable_dir)) {
|
||||
pr_err("Failed to create %s debugfs directory\n", v_name);
|
||||
return PTR_ERR(votable_dir);
|
||||
}
|
||||
|
||||
bd->votable[id].name = v_name;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("active", 0400, votable_dir,
|
||||
&bd->votable[id], &active_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("winvote", 0400, votable_dir,
|
||||
&bd->votable[id], &winvote_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("winner", 0400, votable_dir,
|
||||
&bd->votable[id], &winner_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("voters", 0400, votable_dir,
|
||||
&bd->votable[id], &voters_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("votes", 0400, votable_dir,
|
||||
&bd->votable[id], &votes_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = PTR_ERR_OR_ZERO(debugfs_create_file("status", 0400, votable_dir,
|
||||
&bd->votable[id], &status_fops));
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
rc = battery_dbg_create_override_file(&bd->votable[id], votable_dir);
|
||||
if (rc)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
pr_err("Failed to create debugfs file: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int battery_dbg_get_votables_list(struct battery_dbg_dev *bd)
|
||||
{
|
||||
struct votable_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_GET_AGGREGATOR_INFO_REQ;
|
||||
|
||||
return battery_dbg_write(bd, &req_msg, sizeof(req_msg));
|
||||
}
|
||||
|
||||
static int battery_dbg_create_votables(struct battery_dbg_dev *bd,
|
||||
struct dentry *bd_root_dir)
|
||||
{
|
||||
int rc, id;
|
||||
u32 num_votables;
|
||||
struct dentry *votables_root_dir;
|
||||
|
||||
votables_root_dir = debugfs_create_dir("votables", bd_root_dir);
|
||||
if (IS_ERR(votables_root_dir)) {
|
||||
pr_err("Failed to create votables root directory\n");
|
||||
return PTR_ERR(votables_root_dir);
|
||||
}
|
||||
|
||||
rc = battery_dbg_get_votables_list(bd);
|
||||
if (rc) {
|
||||
pr_err("Failed to get votables list: %d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
num_votables = bd->all_data.num_votables;
|
||||
|
||||
bd->votable = devm_kcalloc(bd->dev, num_votables,
|
||||
sizeof(struct votable), GFP_KERNEL);
|
||||
if (!bd->votable)
|
||||
return -ENOMEM;
|
||||
|
||||
for (id = 0; id < num_votables; id++) {
|
||||
bd->votable[id].bd = bd;
|
||||
bd->votable[id].id = id;
|
||||
rc = battery_dbg_create_votable(bd, votables_root_dir, id);
|
||||
if (rc)
|
||||
return rc;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void battery_dbg_add_debugfs(struct battery_dbg_dev *bd)
|
||||
{
|
||||
int rc;
|
||||
struct dentry *bd_dir;
|
||||
|
||||
bd_dir = debugfs_create_dir("battery_debug", NULL);
|
||||
if (IS_ERR(bd_dir)) {
|
||||
rc = PTR_ERR(bd_dir);
|
||||
pr_err("Failed to create battery debugfs directory: %d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
rc = battery_dbg_create_votables(bd, bd_dir);
|
||||
if (rc) {
|
||||
pr_err("Failed to create votables: %d\n", rc);
|
||||
goto error;
|
||||
}
|
||||
|
||||
bd->debugfs_dir = bd_dir;
|
||||
|
||||
return;
|
||||
error:
|
||||
debugfs_remove_recursive(bd_dir);
|
||||
return;
|
||||
}
|
||||
#else
|
||||
static void battery_dbg_add_debugfs(struct battery_dbg_dev *bd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int get_qbg_context_write(void *data, u64 val)
|
||||
{
|
||||
struct battery_dbg_dev *bd = data;
|
||||
|
|
@ -119,44 +656,97 @@ static int get_qbg_context_write(void *data, u64 val)
|
|||
req_msg.hdr.owner = MSG_OWNER_BD;
|
||||
req_msg.hdr.type = MSG_TYPE_REQ_RESP;
|
||||
req_msg.hdr.opcode = BD_QBG_DUMP_REQ;
|
||||
req_msg.battery_cell_id = bd->battery_cell_id;
|
||||
|
||||
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)
|
||||
static ssize_t qbg_blob_write(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
struct dentry *bd_dir, *file;
|
||||
int rc;
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct battery_dbg_dev *bd = dev_get_drvdata(dev);
|
||||
|
||||
bd_dir = debugfs_create_dir("battery_debug", NULL);
|
||||
if (!bd_dir) {
|
||||
pr_err("Failed to create battery debugfs directory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
rc = get_qbg_context_write(bd, 0); /* second arg is ignored */
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
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;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t qbg_blob_read(struct file *filp, struct kobject *kobj,
|
||||
struct bin_attribute *attr, char *buf,
|
||||
loff_t pos, size_t count)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct battery_dbg_dev *bd = dev_get_drvdata(dev);
|
||||
|
||||
return memory_read_from_buffer(buf, count, &pos, bd->qbg_blob.data,
|
||||
bd->qbg_blob.size);
|
||||
}
|
||||
|
||||
static struct bin_attribute qbg_blob = {
|
||||
.attr = {
|
||||
.name = "qbg_context",
|
||||
.mode = 0600,
|
||||
},
|
||||
.read = qbg_blob_read,
|
||||
.write = qbg_blob_write,
|
||||
};
|
||||
|
||||
|
||||
static ssize_t battery_cell_id_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct battery_dbg_dev *bd = dev_get_drvdata(dev);
|
||||
|
||||
return scnprintf(buf, PAGE_SIZE, "%u\n", bd->battery_cell_id);
|
||||
}
|
||||
|
||||
static ssize_t battery_cell_id_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
struct battery_dbg_dev *bd = dev_get_drvdata(dev);
|
||||
|
||||
if (kstrtou32(buf, 0, &bd->battery_cell_id))
|
||||
return -EINVAL;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR_RW(battery_cell_id);
|
||||
|
||||
static struct attribute *battery_dbg_attrs[] = {
|
||||
&dev_attr_battery_cell_id.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct bin_attribute *battery_dbg_bin_attrs[] = {
|
||||
&qbg_blob,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group battery_dbg_group = {
|
||||
.attrs = battery_dbg_attrs,
|
||||
.bin_attrs = battery_dbg_bin_attrs,
|
||||
};
|
||||
|
||||
static int battery_dbg_add_dev_attr(struct battery_dbg_dev *bd)
|
||||
{
|
||||
int rc;
|
||||
|
||||
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;
|
||||
rc = sysfs_create_group(&bd->dev->kobj, &battery_dbg_group);
|
||||
if (rc)
|
||||
dev_err(bd->dev, "Failed to create sysfs files for qbg_context: %d\n",
|
||||
rc);
|
||||
|
||||
return 0;
|
||||
error:
|
||||
debugfs_remove_recursive(bd_dir);
|
||||
return -ENOMEM;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int battery_dbg_probe(struct platform_device *pdev)
|
||||
|
|
@ -188,10 +778,12 @@ static int battery_dbg_probe(struct platform_device *pdev)
|
|||
init_completion(&bd->ack);
|
||||
platform_set_drvdata(pdev, bd);
|
||||
|
||||
rc = battery_dbg_add_debugfs(bd);
|
||||
rc = battery_dbg_add_dev_attr(bd);
|
||||
if (rc < 0)
|
||||
goto out;
|
||||
|
||||
battery_dbg_add_debugfs(bd);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
pmic_glink_unregister_client(bd->client);
|
||||
|
|
@ -203,6 +795,7 @@ static int battery_dbg_remove(struct platform_device *pdev)
|
|||
struct battery_dbg_dev *bd = platform_get_drvdata(pdev);
|
||||
int rc;
|
||||
|
||||
sysfs_remove_group(&bd->dev->kobj, &battery_dbg_group);
|
||||
debugfs_remove_recursive(bd->debugfs_dir);
|
||||
rc = pmic_glink_unregister_client(bd->client);
|
||||
if (rc < 0) {
|
||||
|
|
|
|||
86
include/linux/soc/qcom/altmode-glink.h
Normal file
86
include/linux/soc/qcom/altmode-glink.h
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#ifndef __ALTMODE_H__
|
||||
#define __ALTMODE_H__
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
/**
|
||||
* struct altmode_client_data
|
||||
* Uniquely define altmode client while registering with altmode framework.
|
||||
*
|
||||
* @svid: Unique ID for Type-C Altmode client devices
|
||||
* @name: Short descriptive name to identify client
|
||||
* @priv: Pointer to client driver's internal top level structure
|
||||
* @callback: Callback function to receive PMIC GLINK message data
|
||||
*/
|
||||
struct altmode_client_data {
|
||||
u16 svid;
|
||||
const char *name;
|
||||
void *priv;
|
||||
int (*callback)(void *priv, void *data, size_t len);
|
||||
};
|
||||
|
||||
struct altmode_client;
|
||||
|
||||
enum altmode_send_msg_type {
|
||||
ALTMODE_PAN_EN = 0x10,
|
||||
ALTMODE_PAN_ACK,
|
||||
};
|
||||
|
||||
struct altmode_pan_ack_msg {
|
||||
u32 cmd_type;
|
||||
u8 port_index;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_QTI_ALTMODE_GLINK)
|
||||
|
||||
struct notifier_block;
|
||||
struct device;
|
||||
|
||||
int altmode_register_notifier(const char *amdev_name,
|
||||
struct notifier_block *nb);
|
||||
int altmode_deregister_notifier(struct altmode_client *client,
|
||||
struct notifier_block *nb);
|
||||
struct altmode_client *altmode_register_client(struct device *dev,
|
||||
const struct altmode_client_data *client_data);
|
||||
int altmode_deregister_client(struct altmode_client *client);
|
||||
int altmode_send_data(struct altmode_client *client, void *data, size_t len);
|
||||
|
||||
#else
|
||||
|
||||
static inline int altmode_register_notifier(const char *amdev_name,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int altmode_deregister_notifier(struct altmode_client *client,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline struct altmode_client *altmode_register_client(struct device *dev,
|
||||
const struct altmode_client_data *client_data)
|
||||
{
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static inline int altmode_deregister_client(struct altmode_client *client)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
static inline int altmode_send_data(struct altmode_client *client, void *data,
|
||||
size_t len)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user