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

Commit 76b8e47e authored by Taniya Das's avatar Taniya Das Committed by Deepak Katragadda
Browse files

clk: qcom: Add support for AOP clock controller



The Always On Processor(AOP) supports enabling,disabling and scaling few
shared clocks using the QMP mailbox client. Add support for the same to
register one such QDSS clock with AOP.

Change-Id: I31dd297acf639640a410494ad4dd96caa76a873f
Signed-off-by: default avatarTaniya Das <tdas@codeaurora.org>
parent 30684a42
Loading
Loading
Loading
Loading
+17 −0
Original line number Diff line number Diff line
Qualcomm Technologies, Inc. Always On Processor Clock controller Binding
------------------------------------------------------------------------

Required properties :
- compatible : must be "qcom,aop-qmp-clk"
- #clock-cells : must contain 1
- mboxes : list of QMP mailbox phandle and channel identifier tuples.
- mbox-names: List of identifier strings for each mailbox channel.
		Must contain "qdss_clk".

Example :
	clock_qdss: qcom,aopclk {
		compatible = "qcom,aop-qmp-clk";
		#clock-cells = <1>;
		mboxes = <&qmp_aop 0>;
		mbox-names = "qdss_clk";
	};
+9 −0
Original line number Diff line number Diff line
@@ -225,4 +225,13 @@ config MSM_GPUCC_SDM845
	  sdm845 devices.
	  Say Y if you want to support graphics controller devices.

config MSM_CLK_AOP_QMP
	tristate "AOP QMP Clock Driver"
	depends on COMMON_CLK_QCOM && MSM_QMP
	help
	Always On Processor manages few shared clocks on some Qualcomm
	Technologies, Inc. SoCs. It accepts requests from other hardware
	subsystems via QMP mailboxes.
	Say Y to support the clocks managed by AOP on platforms such as sdm845.

source "drivers/clk/qcom/mdss/Kconfig"
+1 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o
obj-$(CONFIG_MDM_GCC_9615) += gcc-mdm9615.o
obj-$(CONFIG_MDM_LCC_9615) += lcc-mdm9615.o
obj-$(CONFIG_MSM_CAMCC_SDM845) += camcc-sdm845.o
obj-$(CONFIG_MSM_CLK_AOP_QMP) += clk-aop-qmp.o
obj-$(CONFIG_MSM_CLK_RPMH) += clk-rpmh.o
obj-$(CONFIG_MSM_DISPCC_SDM845) += dispcc-sdm845.o
obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o
+322 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/mailbox_client.h>
#include <dt-bindings/clock/qcom,aop-qmp.h>

#define MAX_LEN			        96
#define MBOX_TOUT_MS			1000

struct qmp_pkt {
	u32 size;
	void *data;
};

#define DEFINE_CLK_AOP_QMP(_name, _class, _res, _estate, _dstate)	\
	static struct clk_aop_qmp _name = {				\
		.msg.class = #_class,					\
		.msg.res = #_res,					\
		.enable_state = _estate,				\
		.disable_state = _dstate,				\
		.hw.init = &(struct clk_init_data){			\
			.ops = &aop_qmp_clk_ops,			\
			.name = #_name,					\
			.num_parents = 0,				\
			.flags = CLK_ENABLE_HAND_OFF,			\
		},							\
	}

#define to_aop_qmp_clk(hw) container_of(hw, struct clk_aop_qmp, hw)

/*
 * struct qmp_mbox_msg -  mailbox data to QMP
 * @class:	identifies the class.
 * @res:	identifies the resource in the class
 * @level:	identifies the level for the resource.
 */
struct qmp_mbox_msg {
	char class[MAX_LEN];
	char res[MAX_LEN];
	int level;
};

/*
 * struct clk_aop_qmp -  AOP clock
 * @dev:		The device that corresponds to this clock.
 * @hw:			The clock hardware for this clock.
 * @cl:			The client mailbox for this clock.
 * @mbox:		The mbox controller for this clock.
 * @level:		The clock level for this clock.
 * @enable_state:	The clock state when this clock is prepared.
 * @disable_state:	The clock state when this clock is unprepared.
 * @msg:		QMP data associated with this clock.
 * @enabled:		Status of the clock enable.
 */
struct clk_aop_qmp {
	struct device *dev;
	struct clk_hw hw;
	struct mbox_client cl;
	struct mbox_chan *mbox;
	int level;
	int enable_state;
	int disable_state;
	struct qmp_mbox_msg msg;
	bool enabled;
};

static DEFINE_MUTEX(clk_aop_lock);

static unsigned long clk_aop_qmp_recalc_rate(struct clk_hw *hw,
						unsigned long parent_rate)
{
	struct clk_aop_qmp *clk = to_aop_qmp_clk(hw);

	return clk->level;
}

static long clk_aop_qmp_round_rate(struct clk_hw *hw, unsigned long rate,
						unsigned long *parent_rate)
{
	return rate;
}

static int clk_aop_qmp_set_rate(struct clk_hw *hw, unsigned long rate,
						unsigned long parent_rate)
{
	char mbox_msg[MAX_LEN];
	struct qmp_pkt pkt;
	struct clk_aop_qmp *clk = to_aop_qmp_clk(hw);
	int ret = 0;

	mutex_lock(&clk_aop_lock);

	snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}",
					clk->msg.class, clk->msg.res, rate);
	pkt.size = MAX_LEN;
	pkt.data = mbox_msg;

	ret = mbox_send_message(clk->mbox, &pkt);
	if (ret < 0) {
		pr_err("Failed to send set rate request of %lu for %s, ret %d\n",
					rate, clk_hw_get_name(hw), ret);
		goto err;
	} else
		/* Success: update the return value */
		ret = 0;

	/* update the current clock level once the mailbox message is sent */
	clk->level = rate;
err:
	mutex_unlock(&clk_aop_lock);

	return ret;
}

static int clk_aop_qmp_prepare(struct clk_hw *hw)
{
	char mbox_msg[MAX_LEN];
	unsigned long rate;
	int ret = 0;
	struct qmp_pkt pkt;
	struct clk_aop_qmp *clk = to_aop_qmp_clk(hw);

	mutex_lock(&clk_aop_lock);

	if (clk->level)
		rate = clk->level;
	else
		rate = clk->enable_state;

	snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}",
				clk->msg.class, clk->msg.res, rate);
	pkt.size = MAX_LEN;
	pkt.data = mbox_msg;

	ret = mbox_send_message(clk->mbox, &pkt);
	if (ret < 0) {
		pr_err("Failed to send clk prepare request for %s, ret %d\n",
					clk_hw_get_name(hw), ret);
		goto err;
	} else
		/* Success: update the return value */
		ret = 0;

	/* update the current clock level once the mailbox message is sent */
	clk->level = rate;

	clk->enabled = true;
err:
	mutex_unlock(&clk_aop_lock);

	return ret;
}

static void clk_aop_qmp_unprepare(struct clk_hw *hw)
{
	char mbox_msg[MAX_LEN];
	unsigned long rate;
	int ret = 0;
	struct qmp_pkt pkt;
	struct clk_aop_qmp *clk = to_aop_qmp_clk(hw);

	mutex_lock(&clk_aop_lock);

	rate = clk->disable_state;

	snprintf(mbox_msg, MAX_LEN, "{class: %s, res: %s, val: %ld}",
				clk->msg.class, clk->msg.res, rate);
	pkt.size = MAX_LEN;
	pkt.data = mbox_msg;

	ret = mbox_send_message(clk->mbox, &pkt);
	if (ret < 0) {
		pr_err("Failed to send clk unprepare request for %s, ret %d\n",
					clk_hw_get_name(hw), ret);
		goto err;
	}

	clk->enabled = false;
err:
	mutex_unlock(&clk_aop_lock);
}

static int clk_aop_qmp_is_enabled(struct clk_hw *hw)
{
	struct clk_aop_qmp *clk = to_aop_qmp_clk(hw);

	return clk->enabled;
}

static const struct clk_ops aop_qmp_clk_ops = {
	.prepare	= clk_aop_qmp_prepare,
	.unprepare	= clk_aop_qmp_unprepare,
	.recalc_rate	= clk_aop_qmp_recalc_rate,
	.set_rate	= clk_aop_qmp_set_rate,
	.round_rate	= clk_aop_qmp_round_rate,
	.is_enabled	= clk_aop_qmp_is_enabled,
};

DEFINE_CLK_AOP_QMP(qdss_qmp_clk, clock, qdss,
		QDSS_CLK_LEVEL_DYNAMIC, QDSS_CLK_LEVEL_OFF);

static struct clk_hw *aop_qmp_clk_hws[] = {
	[QDSS_CLK] = &qdss_qmp_clk.hw,
};

static int qmp_update_client(struct clk_hw *hw, struct device *dev,
		struct mbox_chan *mbox)
{
	struct clk_aop_qmp *clk_aop = to_aop_qmp_clk(hw);

	/* Use mailbox client with blocking mode */
	clk_aop->cl.dev = dev;
	clk_aop->cl.tx_block = true;
	clk_aop->cl.tx_tout = MBOX_TOUT_MS;
	clk_aop->cl.knows_txdone = false;

	if (mbox) {
		clk_aop->mbox = mbox;
		return 0;
	}

	/* Allocate mailbox channel */
	mbox = clk_aop->mbox = mbox_request_channel(&clk_aop->cl, 0);
	if (IS_ERR(clk_aop->mbox) && PTR_ERR(clk_aop->mbox) != -EPROBE_DEFER) {
		dev_err(dev, "Failed to get mailbox channel %pK %ld\n",
						mbox, PTR_ERR(mbox));
		return PTR_ERR(clk_aop->mbox);
	}

	return 0;
}

static int aop_qmp_clk_probe(struct platform_device *pdev)
{
	struct clk *clk;
	struct device_node *np = pdev->dev.of_node;
	struct mbox_chan *mbox = NULL;
	int num_clks = ARRAY_SIZE(aop_qmp_clk_hws);
	int ret = 0, i = 0;

	/*
	 * Allocate mbox channel for the first clock client. The same channel
	 * would be used for the rest of the clock clients.
	 */
	ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox);
	if (ret < 0)
		return ret;

	for (i = 1; i < num_clks; i++) {
		ret = qmp_update_client(aop_qmp_clk_hws[i], &pdev->dev, mbox);
		if (ret < 0) {
			dev_err(&pdev->dev, "Failed to update QMP client %d\n",
							ret);
			goto fail;
		}
	}

	for (i = 0; i < num_clks; i++) {
		ret = clk_aop_qmp_prepare(aop_qmp_clk_hws[i]);
		if (ret < 0)
			goto fail;
	}

	for (i = 0; i < num_clks; i++) {
		clk = devm_clk_register(&pdev->dev, aop_qmp_clk_hws[i]);
		if (IS_ERR(clk)) {
			ret = PTR_ERR(clk);
			goto fail;
		}
	}

	ret = of_clk_add_provider(np, of_clk_src_simple_get, clk);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register clock provider\n");
		goto fail;
	}

	dev_info(&pdev->dev, "Registered clocks with AOP\n");

	return ret;
fail:
	mbox_free_channel(mbox);

	return ret;
}

static const struct of_device_id aop_qmp_clk_of_match[] = {
	{ .compatible = "qcom,aop-qmp-clk", },
	{}
};

static struct platform_driver aop_qmp_clk_driver = {
	.driver = {
		.name = "qmp-aop-clk",
		.of_match_table = aop_qmp_clk_of_match,
	},
	.probe = aop_qmp_clk_probe,
};

static int __init aop_qmp_clk_init(void)
{
	return platform_driver_register(&aop_qmp_clk_driver);
}
subsys_initcall(aop_qmp_clk_init);
+29 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#ifndef _DT_BINDINGS_CLK_QCOM_AOP_QMP_H
#define _DT_BINDINGS_CLK_QCOM_AOP_QMP_H

#define QDSS_CLK_LEVEL_OFF		0
#define QDSS_CLK_LEVEL_DYNAMIC		1
#define QDSS_CLK_LEVEL_TURBO		2
#define QDSS_CLK_LEVEL_NOMINAL		3
#define QDSS_CLK_LEVEL_SVS_L1		4
#define QDSS_CLK_LEVEL_SVS		5
#define QDSS_CLK_LEVEL_LOW_SVS		6
#define QDSS_CLK_LEVEL_MIN_SVS		7

/* clocks id */
#define QDSS_CLK			0

#endif