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

Commit 78f6d637 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "clk: qcom: Add support for AOP clock controller" into msm-next

parents 98964442 79e90c02
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
@@ -196,3 +196,12 @@ config MSM_MMCC_8996
	  Support for the multimedia clock controller on msm8996 devices.
	  Say Y if you want to support multimedia devices such as display,
	  graphics, video encode/decode, camera, etc.

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.
+1 −0
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ obj-$(CONFIG_IPQ_GCC_8074) += gcc-ipq8074.o
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_CLK_AOP_QMP) += clk-aop-qmp.o
obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o
obj-$(CONFIG_MSM_GCC_8916) += gcc-msm8916.o
obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o
+327 −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, _flags) \
	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 = _flags,				\
		},							\
	}

#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;
	}

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

	return ret < 0 ? ret : 0;
}

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;
	}

	/* 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 < 0 ? ret : 0;
}

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, CLK_ENABLE_HAND_OFF);
DEFINE_CLK_AOP_QMP(qdss_ao_qmp_clk, clock, qdss_ao, QDSS_CLK_LEVEL_DYNAMIC,
			QDSS_CLK_LEVEL_OFF, 0);

static struct clk_hw *aop_qmp_clk_hws[] = {
	[QDSS_CLK] = &qdss_qmp_clk.hw,
	[QDSS_AO_CLK] = &qdss_ao_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 = NULL;
	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++) {
		if (!aop_qmp_clk_hws[i])
			continue;
		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;
		}
	}

	/*
	 * Proxy vote on the QDSS clock. This is needed to avoid issues with
	 * excessive requests on the QMP layer during the QDSS driver probe.
	 */
	ret = clk_aop_qmp_prepare(&qdss_qmp_clk.hw);
	if (ret < 0)
		goto fail;

	for (i = 0; i < num_clks; i++) {
		if (!aop_qmp_clk_hws[i])
			continue;
		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);
+1 −1
Original line number Diff line number Diff line
@@ -25,5 +25,5 @@

/* clocks id */
#define QDSS_CLK			0

#define QDSS_AO_CLK			1
#endif