[PATCH 2/8] clk: keystone: Add gate control clock driver

Santosh Shilimkar santosh.shilimkar at ti.com
Mon Aug 5 12:12:21 EDT 2013


Add the driver for the clock gate control which uses PSC (Power Sleep
Controller) IP on Keystone 2 based SOCs. It is responsible for enabling and
disabling of the clocks for different IPs present in the SoC.

Cc: Mike Turquette <mturquette at linaro.org>

Signed-off-by: Santosh Shilimkar <santosh.shilimkar at ti.com>
---
 .../devicetree/bindings/clock/keystone-gate.txt    |   30 ++
 drivers/clk/keystone/gate.c                        |  306 ++++++++++++++++++++
 include/linux/clk/keystone.h                       |    1 +
 3 files changed, 337 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/clock/keystone-gate.txt
 create mode 100644 drivers/clk/keystone/gate.c

diff --git a/Documentation/devicetree/bindings/clock/keystone-gate.txt b/Documentation/devicetree/bindings/clock/keystone-gate.txt
new file mode 100644
index 0000000..40afef5
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/keystone-gate.txt
@@ -0,0 +1,30 @@
+Binding for Keystone gate control driver which uses PSC controller IP.
+
+This binding uses the common clock binding[1].
+
+[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
+
+Required properties:
+- compatible : shall be "keystone,psc-clk".
+- #clock-cells : from common clock binding; shall be set to 0.
+- clocks : parent clock phandle
+- reg :	psc address address space
+
+Optional properties:
+- clock-output-names : From common clock binding to override the
+			default output clock name
+- status : "enabled" if clock is always enabled
+- lpsc : lpsc module id, if not set defaults to zero
+- pd : power domain number, if not set defaults to zero (always ON)
+- gpsc : gpsc number, if not set defaults to zero
+
+Example:
+	clock {
+		#clock-cells = <0>;
+		compatible = "keystone,psc-clk";
+		clocks = <&chipclk3>;
+		clock-output-names = "debugss_trc";
+		reg = <0x02350000 4096>;
+		lpsc = <5>;
+		pd = <1>;
+	};
diff --git a/drivers/clk/keystone/gate.c b/drivers/clk/keystone/gate.c
new file mode 100644
index 0000000..72ac478
--- /dev/null
+++ b/drivers/clk/keystone/gate.c
@@ -0,0 +1,306 @@
+/*
+ * Clock driver for Keystone 2 based devices
+ *
+ * Copyright (C) 2013 Texas Instruments.
+ *	Murali Karicheri <m-karicheri2 at ti.com>
+ *	Santosh Shilimkar <santosh.shilimkar at ti.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/of_address.h>
+#include <linux/of.h>
+#include <linux/module.h>
+
+/* PSC register offsets */
+#define PTCMD			0x120
+#define PTSTAT			0x128
+#define PDSTAT			0x200
+#define PDCTL			0x300
+#define MDSTAT			0x800
+#define MDCTL			0xa00
+
+/* PSC module states */
+#define PSC_STATE_SWRSTDISABLE	0
+#define PSC_STATE_SYNCRST	1
+#define PSC_STATE_DISABLE	2
+#define PSC_STATE_ENABLE	3
+
+#define MDSTAT_STATE_MASK	0x3f
+#define MDSTAT_MCKOUT		BIT(12)
+#define PDSTAT_STATE_MASK	0x1f
+#define MDCTL_FORCE		BIT(31)
+#define MDCTL_LRESET		BIT(8)
+#define PDCTL_NEXT		BIT(0)
+
+/* PSC flags. Disable state is SwRstDisable */
+#define PSC_SWRSTDISABLE	BIT(0)
+/* Force module state transtition */
+#define PSC_FORCE		BIT(1)
+/* Keep module in local reset */
+#define PSC_LRESET		BIT(2)
+#define NUM_GPSC		2
+#define REG_OFFSET		4
+
+/**
+ * struct clk_psc_data - PSC data
+ * @base: Base address for a PSC
+ * @flags: framework flags
+ * @lpsc: Is it local PSC
+ * @gpsc: Is it global PSC
+ * @domain: PSC domain
+ *
+ */
+struct clk_psc_data {
+	void __iomem *base;
+	u32	flags;
+	u32	psc_flags;
+	u8	lpsc;
+	u8	gpsc;
+	u8	domain;
+};
+
+/**
+ * struct clk_psc - PSC clock structure
+ * @hw: clk_hw for the psc
+ * @psc_data: PSC driver specific data
+ * @lock: Spinlock used by the driver
+ */
+struct clk_psc {
+	struct clk_hw hw;
+	struct clk_psc_data *psc_data;
+	spinlock_t *lock;
+};
+
+struct reg_psc {
+	u32 phy_base;
+	u32 size;
+	void __iomem *io_base;
+};
+
+static struct reg_psc psc_addr[NUM_GPSC];
+static DEFINE_SPINLOCK(psc_lock);
+
+#define to_clk_psc(_hw) container_of(_hw, struct clk_psc, hw)
+
+static void psc_config(void __iomem *psc_base, unsigned int domain,
+		unsigned int id, bool enable, u32 flags)
+{
+	u32 ptcmd, pdstat, pdctl, mdstat, mdctl, next_state;
+
+	if (enable)
+		next_state = PSC_STATE_ENABLE;
+	else if (flags & PSC_SWRSTDISABLE)
+		next_state = PSC_STATE_SWRSTDISABLE;
+	else
+		next_state = PSC_STATE_DISABLE;
+
+	mdctl = readl(psc_base + MDCTL + REG_OFFSET * id);
+	mdctl &= ~MDSTAT_STATE_MASK;
+	mdctl |= next_state;
+	if (flags & PSC_FORCE)
+		mdctl |= MDCTL_FORCE;
+	if (flags & PSC_LRESET)
+		mdctl &= ~MDCTL_LRESET;
+	writel(mdctl, psc_base + MDCTL + REG_OFFSET * id);
+
+	pdstat = readl(psc_base + PDSTAT + REG_OFFSET * domain);
+	if (!(pdstat & PDSTAT_STATE_MASK)) {
+		pdctl = readl(psc_base + PDCTL + REG_OFFSET * domain);
+		pdctl |= PDCTL_NEXT;
+		writel(pdctl, psc_base + PDCTL + REG_OFFSET * domain);
+	}
+
+	ptcmd = 1 << domain;
+	writel(ptcmd, psc_base + PTCMD);
+	while ((readl(psc_base + PTSTAT) >> domain) & 1)
+		;
+
+	do {
+		mdstat = readl(psc_base + MDSTAT + REG_OFFSET * id);
+	} while (!((mdstat & MDSTAT_STATE_MASK) == next_state));
+}
+
+static int keystone_clk_is_enabled(struct clk_hw *hw)
+{
+	struct clk_psc *psc = to_clk_psc(hw);
+	struct clk_psc_data *data = psc->psc_data;
+	u32 mdstat = readl(data->base + MDSTAT + REG_OFFSET * data->lpsc);
+
+	return (mdstat & MDSTAT_MCKOUT) ? 1 : 0;
+}
+
+static int keystone_clk_enable(struct clk_hw *hw)
+{
+	struct clk_psc *psc = to_clk_psc(hw);
+	struct clk_psc_data *data = psc->psc_data;
+	unsigned long flags = 0;
+
+	if (psc->lock)
+		spin_lock_irqsave(psc->lock, flags);
+
+	psc_config(data->base, data->domain, data->lpsc, 1, data->psc_flags);
+
+	if (psc->lock)
+		spin_unlock_irqrestore(psc->lock, flags);
+
+	return 0;
+}
+
+static void keystone_clk_disable(struct clk_hw *hw)
+{
+	struct clk_psc *psc = to_clk_psc(hw);
+	struct clk_psc_data *data = psc->psc_data;
+	unsigned long flags = 0;
+
+	if (psc->lock)
+		spin_lock_irqsave(psc->lock, flags);
+
+	psc_config(data->base, data->domain, data->lpsc, 0, data->psc_flags);
+
+	if (psc->lock)
+		spin_unlock_irqrestore(psc->lock, flags);
+}
+
+static const struct clk_ops clk_psc_ops = {
+	.enable = keystone_clk_enable,
+	.disable = keystone_clk_disable,
+	.is_enabled = keystone_clk_is_enabled,
+};
+
+/**
+ * clk_register_psc - register psc clock
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_name: name of clock's parent
+ * @psc_data: platform data to configure this clock
+ * @lock: spinlock used by this clock
+ */
+static struct clk *clk_register_psc(struct device *dev,
+			const char *name,
+			const char *parent_name,
+			struct clk_psc_data *psc_data,
+			spinlock_t *lock)
+{
+	struct clk_init_data init;
+	struct clk_psc *psc;
+	struct clk *clk;
+
+	psc = kzalloc(sizeof(*psc), GFP_KERNEL);
+	if (!psc)
+		return ERR_PTR(-ENOMEM);
+
+	init.name = name;
+	init.ops = &clk_psc_ops;
+	init.flags = psc_data->flags;
+	init.parent_names = (parent_name ? &parent_name : NULL);
+	init.num_parents = (parent_name ? 1 : 0);
+
+	psc->psc_data = psc_data;
+	psc->lock = lock;
+	psc->hw.init = &init;
+
+	clk = clk_register(NULL, &psc->hw);
+	if (IS_ERR(clk))
+		kfree(psc);
+
+	return clk;
+}
+
+/**
+ * of_psc_clk_init - initialize psc clock through DT
+ * @node: device tree node for this clock
+ * @lock: spinlock used by this clock
+ */
+static void __init of_psc_clk_init(struct device_node *node, spinlock_t *lock)
+{
+	const char *parent_name, *status = NULL, *base_flags = NULL;
+	struct clk_psc_data *data;
+	const char *clk_name = node->name;
+	u32 gpsc = 0, lpsc = 0, pd = 0;
+	struct resource res;
+	struct clk *clk;
+	int rc;
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	WARN_ON(!data);
+
+	if (of_address_to_resource(node, 0, &res)) {
+		pr_err("psc_clk_init - no reg property defined\n");
+		goto out;
+	}
+
+	of_property_read_u32(node, "gpsc", &gpsc);
+	of_property_read_u32(node, "lpsc", &lpsc);
+	of_property_read_u32(node, "pd", &pd);
+
+	if (gpsc >= NUM_GPSC) {
+		pr_err("psc_clk_init - no reg property defined\n");
+		goto out;
+	}
+
+	of_property_read_string(node,
+			"clock-output-names", &clk_name);
+	parent_name = of_clk_get_parent_name(node, 0);
+	WARN_ON(!parent_name);
+
+	/* Expected that same phy_base is used for all psc clocks of
+	 * a give gpsc. So ioremap is done only once.
+	 */
+	if (psc_addr[gpsc].phy_base) {
+		if (psc_addr[gpsc].phy_base != res.start) {
+			pr_err("Different psc base for same GPSC\n");
+			goto out;
+		}
+	} else {
+		psc_addr[gpsc].phy_base = res.start;
+		psc_addr[gpsc].io_base =
+			ioremap(res.start, resource_size(&res));
+	}
+
+	WARN_ON(!psc_addr[gpsc].io_base);
+	data->base = psc_addr[gpsc].io_base;
+	data->lpsc = lpsc;
+	data->gpsc = gpsc;
+	data->domain = pd;
+
+	if (of_property_read_bool(node, "ti,psc-lreset"))
+		data->psc_flags |= PSC_LRESET;
+	if (of_property_read_bool(node, "ti,psc-force"))
+		data->psc_flags |= PSC_FORCE;
+
+	clk = clk_register_psc(NULL, clk_name, parent_name,
+				data, lock);
+
+	if (clk) {
+		of_clk_add_provider(node, of_clk_src_simple_get, clk);
+
+		rc = of_property_read_string(node, "status", &status);
+		if (status && !strcmp(status, "enabled"))
+			clk_prepare_enable(clk);
+		return;
+	}
+	pr_err("psc_clk_init - error registering psc clk %s\n", node->name);
+out:
+	kfree(data);
+	return;
+}
+
+/**
+ * of_keystone_psc_clk_init - initialize psc clock through DT
+ * @node: device tree node for this clock
+ */
+void __init of_keystone_psc_clk_init(struct device_node *node)
+{
+	of_psc_clk_init(node, &psc_lock);
+}
+EXPORT_SYMBOL_GPL(of_keystone_psc_clk_init);
+CLK_OF_DECLARE(keystone_gate_clk, "keystone,psc-clk", of_keystone_psc_clk_init);
diff --git a/include/linux/clk/keystone.h b/include/linux/clk/keystone.h
index 1ade95d..7b3e154 100644
--- a/include/linux/clk/keystone.h
+++ b/include/linux/clk/keystone.h
@@ -14,5 +14,6 @@
 #define __LINUX_CLK_KEYSTONE_H_
 
 extern void of_keystone_pll_clk_init(struct device_node *node);
+extern void of_keystone_psc_clk_init(struct device_node *node);
 
 #endif /* __LINUX_CLK_KEYSTONE_H_ */
-- 
1.7.9.5




More information about the linux-arm-kernel mailing list