Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1d4c3f6e authored by qctecmdr's avatar qctecmdr Committed by Gerrit - the friendly Code Review server
Browse files

Merge "defconfig: lahaina-gki: enable the glink debug spmi controller driver"

parents ccc64327 a8916a4c
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -11,6 +11,7 @@ CONFIG_SND_USB_AUDIO_QMI=m
CONFIG_EXTCON=m
CONFIG_EXTCON=m
CONFIG_SPMI_MSM_PMIC_ARB=m
CONFIG_SPMI_MSM_PMIC_ARB=m
CONFIG_SPMI_MSM_PMIC_ARB_DEBUG=m
CONFIG_SPMI_MSM_PMIC_ARB_DEBUG=m
CONFIG_SPMI_QTI_GLINK_DEBUG=m
CONFIG_MFD_I2C_PMIC=m
CONFIG_MFD_I2C_PMIC=m
CONFIG_PINCTRL_QCOM_SPMI_PMIC=m
CONFIG_PINCTRL_QCOM_SPMI_PMIC=m
CONFIG_NVMEM_SPMI_SDAM=m
CONFIG_NVMEM_SPMI_SDAM=m
+10 −0
Original line number Original line Diff line number Diff line
@@ -35,4 +35,14 @@ config SPMI_MSM_PMIC_ARB_DEBUG
	  Inc. (QTI) MSM family processors.  This feature is available on chips
	  Inc. (QTI) MSM family processors.  This feature is available on chips
	  with PMIC arbiter version 5 and above.
	  with PMIC arbiter version 5 and above.


config SPMI_QTI_GLINK_DEBUG
	tristate "QTI Glink Debug SPMI Controller"
	depends on QTI_PMIC_GLINK
	help
	  The Qualcomm Technologies, Inc. Glink debug SPMI controller driver
	  provides an interface to read and write PMIC registers over PMIC
	  Glink using a remote subsytem (e.g. DSP).  This allows for debugging
	  PMIC peripherals that would typically only be accessible to the
	  charger and fuel gauging firmware running on the remote subsystem.

endif
endif
+1 −0
Original line number Original line Diff line number Diff line
@@ -6,3 +6,4 @@ obj-$(CONFIG_SPMI) += spmi.o


obj-$(CONFIG_SPMI_MSM_PMIC_ARB)	+= spmi-pmic-arb.o
obj-$(CONFIG_SPMI_MSM_PMIC_ARB)	+= spmi-pmic-arb.o
obj-$(CONFIG_SPMI_MSM_PMIC_ARB_DEBUG)	+= spmi-pmic-arb-debug.o
obj-$(CONFIG_SPMI_MSM_PMIC_ARB_DEBUG)	+= spmi-pmic-arb-debug.o
obj-$(CONFIG_SPMI_QTI_GLINK_DEBUG)	+= spmi-glink-debug.o
+397 −0
Original line number Original line Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */

#include <linux/bitops.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/spmi.h>
#include <linux/string.h>

#include <linux/soc/qcom/pmic_glink.h>

#define MSG_OWNER_REG_DUMP		32783
#define MSG_TYPE_REQ_RESP		1
#define REG_DUMP_REG_READ_REQ		0x37
#define REG_DUMP_REG_WRITE_REQ		0x38

#define REG_DUMP_WAIT_TIME_MS		1000

#define SPMI_GLINK_MAX_READ_BYTES	256
#define SPMI_GLINK_MAX_WRITE_BYTES	1

#define PERIPH_MASK			GENMASK(7, 0)

struct reg_dump_read_req_msg {
	struct pmic_glink_hdr		hdr;
	u32				spmi_bus_id;
	u32				pmic_sid;
	u32				address;
	u32				byte_count;
};

struct reg_dump_read_resp_msg {
	struct pmic_glink_hdr		hdr;
	u32				spmi_bus_id;
	u32				pmic_sid;
	u32				address;
	u32				byte_count;
	u8				data[SPMI_GLINK_MAX_READ_BYTES];
};

struct reg_dump_write_req_msg {
	struct pmic_glink_hdr		hdr;
	u32				spmi_bus_id;
	u32				pmic_sid;
	u32				address;
	u32				data;
};

struct reg_dump_write_resp_msg {
	struct pmic_glink_hdr		hdr;
	u32				return_status;
};

struct spmi_glink_ctrl;

struct spmi_glink_dev {
	struct pmic_glink_client	*client;
	struct device			*dev;
	struct mutex			lock;
	struct completion		ack;
	struct reg_dump_read_resp_msg	read_msg;
	struct spmi_glink_ctrl		**gctrl;
	int				bus_count;
};

struct spmi_glink_ctrl {
	struct spmi_glink_dev		*gd;
	struct spmi_controller		*ctrl;
	u32				bus_id;
};

static int spmi_glink_write(struct spmi_glink_ctrl *gctrl, void *data,
				size_t len)
{
	struct spmi_glink_dev *gd = gctrl->gd;
	int ret;

	reinit_completion(&gd->ack);
	ret = pmic_glink_write(gd->client, data, len);
	if (ret)
		return ret;

	ret = wait_for_completion_timeout(&gd->ack,
				msecs_to_jiffies(REG_DUMP_WAIT_TIME_MS));
	if (!ret) {
		dev_err(&gctrl->ctrl->dev, "Error, timed out sending message\n");
		return -ETIMEDOUT;
	}

	return 0;
}

/* Non-data SPMI command */
static int spmi_glink_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid)
{
	return -EOPNOTSUPP;
}

static int spmi_glink_read_reg(struct spmi_glink_ctrl *gctrl, u8 sid, u16 addr,
				u8 *buf, size_t len)
{
	struct spmi_glink_dev *gd = gctrl->gd;
	struct reg_dump_read_req_msg msg = {{0}};
	int ret;

	if (len > SPMI_GLINK_MAX_READ_BYTES)
		return -EINVAL;

	msg.hdr.owner = MSG_OWNER_REG_DUMP;
	msg.hdr.type = MSG_TYPE_REQ_RESP;
	msg.hdr.opcode = REG_DUMP_REG_READ_REQ;

	msg.spmi_bus_id = gctrl->bus_id;
	msg.pmic_sid = sid;
	msg.address = addr;
	msg.byte_count = len;

	ret = spmi_glink_write(gctrl, &msg, sizeof(msg));
	if (ret)
		return ret;

	if (gd->read_msg.byte_count != len)
		return -EINVAL;

	memcpy(buf, gd->read_msg.data, len);

	return 0;
}

static int spmi_glink_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
			     u16 addr, u8 *buf, size_t len)
{
	struct spmi_glink_ctrl *gctrl = spmi_controller_get_drvdata(ctrl);
	struct spmi_glink_dev *gd = gctrl->gd;
	int ret, count;

	mutex_lock(&gd->lock);
	do {
		count = min_t(size_t, len, SPMI_GLINK_MAX_READ_BYTES);
		/* Ensure transactions are divided across peripherals */
		if ((addr & PERIPH_MASK) + count > PERIPH_MASK + 1)
			count = PERIPH_MASK + 1 - (addr & PERIPH_MASK);

		ret = spmi_glink_read_reg(gctrl, sid, addr, buf, count);
		if (ret)
			goto done;

		/* Handle a transaction split across SIDs */
		if ((u16)(addr + count) < addr)
			sid++;
		addr += count;
		buf += count;
		len -= count;
	} while (len > 0);

done:
	mutex_unlock(&gd->lock);
	return ret;
}

static int spmi_glink_write_reg(struct spmi_glink_ctrl *gctrl, u8 sid, u16 addr,
				u8 val)
{
	struct reg_dump_write_req_msg msg = {{0}};

	msg.hdr.owner = MSG_OWNER_REG_DUMP;
	msg.hdr.type = MSG_TYPE_REQ_RESP;
	msg.hdr.opcode = REG_DUMP_REG_WRITE_REQ;

	msg.spmi_bus_id = gctrl->bus_id;
	msg.pmic_sid = sid;
	msg.address = addr;
	msg.data = val;

	return spmi_glink_write(gctrl, &msg, sizeof(msg));
}

static int spmi_glink_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid,
			     u16 addr, const u8 *buf, size_t len)
{
	struct spmi_glink_ctrl *gctrl = spmi_controller_get_drvdata(ctrl);
	struct spmi_glink_dev *gd = gctrl->gd;
	int ret, count;

	mutex_lock(&gd->lock);
	do {
		count = min_t(size_t, len, SPMI_GLINK_MAX_WRITE_BYTES);
		/* Ensure transactions are divided across peripherals */
		if ((addr & PERIPH_MASK) + count > PERIPH_MASK + 1)
			count = PERIPH_MASK + 1 - (addr & PERIPH_MASK);

		ret = spmi_glink_write_reg(gctrl, sid, addr, *buf);
		if (ret)
			goto done;

		/* Handle a transaction split across SIDs */
		if ((u16)(addr + count) < addr)
			sid++;
		addr += count;
		buf += count;
		len -= count;
	} while (len > 0);

done:
	mutex_unlock(&gd->lock);
	return ret;
}

static void spmi_glink_handle_read_resp(struct spmi_glink_dev *gd,
			struct reg_dump_read_resp_msg *read_resp, size_t len)
{
	if (len != sizeof(*read_resp)) {
		dev_err(gd->dev, "Invalid read response, glink packet size=%zu\n",
			len);
		return;
	}

	if ((int)read_resp->byte_count < 0) {
		dev_err(gd->dev, "glink read failed, ret=%d\n",
			(int)read_resp->byte_count);
		return;
	}

	memcpy(&gd->read_msg, read_resp, sizeof(gd->read_msg));

	complete(&gd->ack);
}

static void spmi_glink_handle_write_resp(struct spmi_glink_dev *gd,
			struct reg_dump_write_resp_msg *write_resp, size_t len)
{
	if (len != sizeof(*write_resp)) {
		dev_err(gd->dev, "Invalid write response, glink packet size=%zu\n",
			len);
		return;
	}

	if (write_resp->return_status) {
		dev_err(gd->dev, "glink write failed, ret=%d\n",
			(int)write_resp->return_status);
		return;
	}

	complete(&gd->ack);
}

static int spmi_glink_callback(void *priv, void *data, size_t len)
{
	struct spmi_glink_dev *gd = priv;
	struct pmic_glink_hdr *hdr = data;

	dev_dbg(gd->dev, "owner: %u type: %u opcode: %#x len: %zu\n",
		hdr->owner, hdr->type, hdr->opcode, len);

	switch (hdr->opcode) {
	case REG_DUMP_REG_READ_REQ:
		spmi_glink_handle_read_resp(gd, data, len);
		break;
	case REG_DUMP_REG_WRITE_REQ:
		spmi_glink_handle_write_resp(gd, data, len);
		break;
	default:
		dev_err(gd->dev, "Unknown opcode %u\n", hdr->opcode);
		break;
	}

	return 0;
}

static int spmi_glink_remove(struct platform_device *pdev)
{
	struct spmi_glink_dev *gd = platform_get_drvdata(pdev);
	int i;

	for (i = 0; i < gd->bus_count; i++) {
		if (gd->gctrl[i]) {
			spmi_controller_remove(gd->gctrl[i]->ctrl);
			spmi_controller_put(gd->gctrl[i]->ctrl);
		}
	}

	pmic_glink_unregister_client(gd->client);

	return 0;
}

static int spmi_glink_probe(struct platform_device *pdev)
{
	struct spmi_glink_dev *gd;
	struct spmi_controller *ctrl;
	struct pmic_glink_client_data client_data = { };
	struct spmi_glink_ctrl *gctrl;
	struct device_node *node;
	int ret, i;

	gd = devm_kzalloc(&pdev->dev, sizeof(*gd), GFP_KERNEL);
	if (!gd)
		return -ENOMEM;

	gd->dev = &pdev->dev;
	mutex_init(&gd->lock);
	init_completion(&gd->ack);
	platform_set_drvdata(pdev, gd);

	for_each_available_child_of_node(pdev->dev.of_node, node)
		gd->bus_count++;
	if (!gd->bus_count) {
		dev_err(&pdev->dev, "SPMI bus child nodes missing\n");
		return -ENODEV;
	}

	gd->gctrl = devm_kcalloc(&pdev->dev, gd->bus_count, sizeof(*gd->gctrl),
				GFP_KERNEL);
	if (!gd->gctrl)
		return -ENOMEM;

	client_data.id = MSG_OWNER_REG_DUMP;
	client_data.name = "spmi_register_debug";
	client_data.callback = spmi_glink_callback;
	client_data.priv = gd;

	gd->client = pmic_glink_register_client(&pdev->dev, &client_data);
	if (IS_ERR(gd->client)) {
		ret = PTR_ERR(gd->client);
		if (ret != -EPROBE_DEFER)
			dev_err(&pdev->dev, "Error registering with pmic_glink, ret=%d\n",
				ret);
		return ret;
	}

	i = 0;
	for_each_available_child_of_node(pdev->dev.of_node, node) {
		ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*gctrl));
		if (!ctrl) {
			ret = -ENOMEM;
			of_node_put(node);
			goto err_remove_ctrl;
		}

		gctrl = spmi_controller_get_drvdata(ctrl);
		gctrl->ctrl = ctrl;
		gctrl->gd = gd;
		ret = of_property_read_u32(node, "reg", &gctrl->bus_id);
		if (ret) {
			dev_err(&pdev->dev, "Could not find reg property, ret=%d\n",
				ret);
			spmi_controller_put(ctrl);
			of_node_put(node);
			goto err_remove_ctrl;
		}

		ctrl->cmd = spmi_glink_cmd;
		ctrl->read_cmd = spmi_glink_read_cmd;
		ctrl->write_cmd = spmi_glink_write_cmd;
		ctrl->dev.of_node = node;

		ret = spmi_controller_add(ctrl);
		if (ret) {
			spmi_controller_put(ctrl);
			of_node_put(node);
			goto err_remove_ctrl;
		}

		gd->gctrl[i++] = gctrl;
	}

	return 0;

err_remove_ctrl:
	spmi_glink_remove(pdev);

	return ret;
}

static const struct of_device_id spmi_glink_match_table[] = {
	{ .compatible = "qcom,spmi-glink-debug", },
	{},
};
MODULE_DEVICE_TABLE(of, spmi_glink_match_table);

static struct platform_driver spmi_glink_driver = {
	.driver = {
		.name = "spmi_glink",
		.of_match_table = spmi_glink_match_table,
	},
	.probe = spmi_glink_probe,
	.remove = spmi_glink_remove,
};
module_platform_driver(spmi_glink_driver);

MODULE_DESCRIPTION("SPMI Glink Debug Driver");
MODULE_LICENSE("GPL v2");