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

Commit 8bb162b0 authored by Vikash Garodia's avatar Vikash Garodia
Browse files

soc: qcom: Update Cx ipeak driver for multimedia clients



Redesign the driver by introducing target specific ops.
Based on the target, the register programming can be
handled in the correspond target ops.
In v2, individual client has a dedicated register to
update about the operating threshold.

Change-Id: I2476a9118b4f30994065233cefba2472569ca340
Signed-off-by: default avatarVikash Garodia <vgarodia@codeaurora.org>
parent a487c3ab
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
* Cx iPeak driver to handle requests from various multimedia clients.

Cx ipeak HW module is used to limit the current drawn by various subsystem
blocks on Cx power rail. Each client needs to set their
bit in tcsr register if it is going to cross its own threshold. If all
clients are going to cross their thresholds then Cx ipeak hw module will raise
an interrupt to cDSP block to throttle cDSP's fmax.
blocks on Cx power rail. Each client needs to set their bit in tcsr register
if it is going to cross its own threshold. If all clients are going to cross
their thresholds then Cx ipeak hw module will raise an interrupt to cDSP block
to throttle cDSP's fmax.

Required properties:

- compatible : name of the component used for driver matching, should be one
	       "qcom,cx-ipeak-sdm660", "qcom,cx-ipeak-sm6150"
	       "qcom,cx-ipeak-v1", "qcom,cx-ipeak-v2"

- reg : physical base address and length of the register set(s), SRAM and XPU
	of the component.
@@ -17,6 +17,6 @@ Required properties:
Example:

	cx_ipeak_lm: cx_ipeak@1fe5040 {
		compatible = "qcom,cx-ipeak-sdm660";
		compatible = "qcom,cx-ipeak-v1";
		reg = <0x01fe5040 0x28>;
	};
+167 −44
Original line number Diff line number Diff line
@@ -23,28 +23,43 @@

#include <soc/qcom/cx_ipeak.h>

#define TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET		0x10

/* v1 register set */
#define TCSR_CXIP_LM_VOTE_BYPASS_OFFSET			0x4
#define TCSR_CXIP_LM_VOTE_CLEAR_OFFSET			0x8
#define TCSR_CXIP_LM_VOTE_SET_OFFSET			0xC
#define TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET         0x10
#define TCSR_CXIP_LM_TRS_OFFSET				0x24

/* v2 register set */
#define TCSR_CXIP_LM_VOTE_CLIENTx_BYPASS_OFFSET		0x4
#define TCSR_CXIP_LM_DANGER_OFFSET			0x24

#define CXIP_CLIENT_OFFSET				0x1000
#define CXIP_POLL_TIMEOUT_US (50 * 1000)

struct cx_ipeak_client;

struct cx_ipeak_core_ops {
	int (*update)(struct cx_ipeak_client *client, bool vote);
	struct cx_ipeak_client* (*register_client)(int client_id);
};

static struct cx_ipeak_device {
	spinlock_t vote_lock;
	void __iomem *tcsr_vptr;
	struct cx_ipeak_core_ops *core_ops;
} device_ipeak;

struct cx_ipeak_client {
	int vote_count;
	unsigned int vote_mask;
	unsigned int offset;
	struct cx_ipeak_device *dev;
};

/**
 * cx_ipeak_register() - allocate client structure and fill device private and
 *			bit details.
 *			offset details.
 * @dev_node: device node of the client
 * @client_name: property name of the client
 *
@@ -55,8 +70,7 @@ struct cx_ipeak_client *cx_ipeak_register(struct device_node *dev_node,
		const char *client_name)
{
	struct of_phandle_args cx_spec;
	struct cx_ipeak_client *client;
	unsigned int reg_enable, reg_bypass;
	struct cx_ipeak_client *client = NULL;
	int ret;

	ret = of_parse_phandle_with_fixed_args(dev_node, client_name,
@@ -73,37 +87,65 @@ struct cx_ipeak_client *cx_ipeak_register(struct device_node *dev_node,
	if (cx_spec.args[0] > 31)
		return ERR_PTR(-EINVAL);

	if (device_ipeak.core_ops)
		client =  device_ipeak.core_ops->register_client
						(cx_spec.args[0]);
	return client;
}
EXPORT_SYMBOL(cx_ipeak_register);

static struct cx_ipeak_client *cx_ipeak_register_v1(int client_id)
{
	struct cx_ipeak_client *client;
	unsigned int reg_enable, reg_bypass;
	void __iomem *vptr = device_ipeak.tcsr_vptr;

	reg_enable = readl_relaxed(device_ipeak.tcsr_vptr +
			TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET);
	reg_bypass = readl_relaxed(device_ipeak.tcsr_vptr +
	reg_bypass = readl_relaxed(vptr +
			TCSR_CXIP_LM_VOTE_BYPASS_OFFSET) &
			BIT(cx_spec.args[0]);

			BIT(client_id);
	if (!reg_enable || reg_bypass)
		return NULL;

	client = kzalloc(sizeof(struct cx_ipeak_client), GFP_KERNEL);
	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client)
		return ERR_PTR(-ENOMEM);

	client->vote_mask = BIT(cx_spec.args[0]);
	client->offset = BIT(client_id);
	client->dev = &device_ipeak;

	return client;
}
EXPORT_SYMBOL(cx_ipeak_register);

/**
 * cx_ipeak_unregister() - unregister client
 * @client: client address to free
 *
 * Free the client memory
 */
void cx_ipeak_unregister(struct cx_ipeak_client *client)
static struct cx_ipeak_client *cx_ipeak_register_v2(int client_id)
{
	kfree(client);
	unsigned int reg_bypass, reg_enable;
	struct cx_ipeak_client *client;
	unsigned int client_offset = 0;
	void __iomem *vptr = device_ipeak.tcsr_vptr;

	for (int i = 0; i <= client_id; i++)
		client_offset += CXIP_CLIENT_OFFSET;

	reg_enable = readl_relaxed(device_ipeak.tcsr_vptr +
			TCSR_CXIP_LM_VOTE_FEATURE_ENABLE_OFFSET);
	reg_bypass = readl_relaxed(vptr + client_offset +
			TCSR_CXIP_LM_VOTE_CLIENTx_BYPASS_OFFSET) &
			BIT(0);

	if (!reg_enable || reg_bypass)
		return NULL;

	client = kzalloc(sizeof(*client), GFP_KERNEL);
	if (!client)
		return ERR_PTR(-ENOMEM);

	client->offset = client_offset;
	client->dev = &device_ipeak;

	return client;
}
EXPORT_SYMBOL(cx_ipeak_unregister);

/*
 * cx_ipeak_update() - Set/Clear client vote for Cx iPeak limit
@@ -115,22 +157,30 @@ EXPORT_SYMBOL(cx_ipeak_unregister);
 * This function is NOP for the targets which does not support TCSR Cx iPeak.
 */
int cx_ipeak_update(struct cx_ipeak_client *client, bool vote)
{
	/* Check for client and device availability and proceed */
	if (!client)
		return 0;

	if (!client->dev || !client->dev->core_ops || !client->dev->tcsr_vptr)
		return -EINVAL;

	return client->dev->core_ops->update(client, vote);
}
EXPORT_SYMBOL(cx_ipeak_update);

static int cx_ipeak_update_v1(struct cx_ipeak_client *client, bool vote)
{
	unsigned int reg_val;
	int ret = 0;

	/* Check for client and device availability and proceed */
	if (client == NULL || client->dev->tcsr_vptr == NULL)
		return ret;

	spin_lock(&client->dev->vote_lock);

	if (vote) {
		if (client->vote_count == 0) {
			writel_relaxed(client->vote_mask,
			writel_relaxed(client->offset,
				       client->dev->tcsr_vptr +
				       TCSR_CXIP_LM_VOTE_SET_OFFSET);

			/*
			 * Do a dummy read to give enough time for TRS register
			 * to become 1 when the last client votes.
@@ -139,10 +189,11 @@ int cx_ipeak_update(struct cx_ipeak_client *client, bool vote)
				      TCSR_CXIP_LM_TRS_OFFSET);

			ret = readl_poll_timeout(client->dev->tcsr_vptr +
				TCSR_CXIP_LM_TRS_OFFSET, reg_val, !reg_val,
				0, CXIP_POLL_TIMEOUT_US);
						 TCSR_CXIP_LM_TRS_OFFSET,
						 reg_val, !reg_val, 0,
						 CXIP_POLL_TIMEOUT_US);
			if (ret) {
				writel_relaxed(client->vote_mask,
				writel_relaxed(client->offset,
					       client->dev->tcsr_vptr +
					       TCSR_CXIP_LM_VOTE_CLEAR_OFFSET);
				goto done;
@@ -153,7 +204,7 @@ int cx_ipeak_update(struct cx_ipeak_client *client, bool vote)
		if (client->vote_count > 0) {
			client->vote_count--;
			if (client->vote_count == 0) {
				writel_relaxed(client->vote_mask,
				writel_relaxed(client->offset,
					       client->dev->tcsr_vptr +
					       TCSR_CXIP_LM_VOTE_CLEAR_OFFSET);
			}
@@ -165,7 +216,71 @@ int cx_ipeak_update(struct cx_ipeak_client *client, bool vote)
	spin_unlock(&client->dev->vote_lock);
	return ret;
}
EXPORT_SYMBOL(cx_ipeak_update);

static int cx_ipeak_update_v2(struct cx_ipeak_client *client, bool vote)
{
	unsigned int reg_val;
	int ret = 0;

	spin_lock(&client->dev->vote_lock);

	if (vote) {
		if (client->vote_count == 0) {
			writel_relaxed(BIT(0),
				       client->dev->tcsr_vptr +
				       client->offset);

			ret = readl_poll_timeout(client->dev->tcsr_vptr +
						 TCSR_CXIP_LM_DANGER_OFFSET,
						 reg_val, !reg_val, 0,
						 CXIP_POLL_TIMEOUT_US);
			if (ret) {
				writel_relaxed(0,
					       client->dev->tcsr_vptr +
					       client->offset);
				goto done;
			}
		}
		client->vote_count++;
	} else {
		if (client->vote_count > 0) {
			client->vote_count--;
			if (client->vote_count == 0) {
				writel_relaxed(0,
					       client->dev->tcsr_vptr +
					       client->offset);
			}
		} else {
			ret = -EINVAL;
		}
	}

done:
	spin_unlock(&client->dev->vote_lock);
	return ret;
}

/**
 * cx_ipeak_unregister() - unregister client
 * @client: client address to free
 *
 * Free the client memory
 */
void cx_ipeak_unregister(struct cx_ipeak_client *client)
{
	kfree(client);
}
EXPORT_SYMBOL(cx_ipeak_unregister);

struct cx_ipeak_core_ops core_ops_v1 = {
	.update = cx_ipeak_update_v1,
	.register_client = cx_ipeak_register_v1,
};

struct cx_ipeak_core_ops core_ops_v2 = {
	.update = cx_ipeak_update_v2,
	.register_client = cx_ipeak_register_v2,
};

static int cx_ipeak_probe(struct platform_device *pdev)
{
@@ -176,13 +291,21 @@ static int cx_ipeak_probe(struct platform_device *pdev)
	if (IS_ERR(device_ipeak.tcsr_vptr))
		return PTR_ERR(device_ipeak.tcsr_vptr);

	if (of_device_is_compatible(pdev->dev.of_node, "qcom,cx-ipeak-v1"))
		device_ipeak.core_ops = &core_ops_v1;
	else if (of_device_is_compatible(pdev->dev.of_node,
					 "qcom,cx-ipeak-v2"))
		device_ipeak.core_ops = &core_ops_v2;
	else
		device_ipeak.core_ops = NULL;

	spin_lock_init(&device_ipeak.vote_lock);
	return 0;
}

static const struct of_device_id cx_ipeak_match_table[] = {
	{ .compatible = "qcom,cx-ipeak-sdm660"},
	{ .compatible = "qcom,cx-ipeak-sm6150"},
	{ .compatible = "qcom,cx-ipeak-v1"},
	{ .compatible = "qcom,cx-ipeak-v2"},
	{}
};