[PATCH v4 5/7] clk: Introduce davinci clocks
David Lechner
david at lechnology.com
Sun Dec 31 15:39:46 PST 2017
This introduces new drivers for arch/arm/mach-davinci. The code is based
on the clock drivers from there and adapted to use the common clock
framework.
Signed-off-by: David Lechner <david at lechnology.com>
---
drivers/clk/Makefile | 1 +
drivers/clk/davinci/Makefile | 3 +
drivers/clk/davinci/da8xx-cfgchip-clk.c | 380 ++++++++++++++++++++++++++++++
drivers/clk/davinci/pll.c | 333 ++++++++++++++++++++++++++
drivers/clk/davinci/psc.c | 217 +++++++++++++++++
include/linux/clk/davinci.h | 46 ++++
include/linux/platform_data/davinci_clk.h | 25 ++
7 files changed, 1005 insertions(+)
create mode 100644 drivers/clk/davinci/Makefile
create mode 100644 drivers/clk/davinci/da8xx-cfgchip-clk.c
create mode 100644 drivers/clk/davinci/pll.c
create mode 100644 drivers/clk/davinci/psc.c
create mode 100644 include/linux/clk/davinci.h
create mode 100644 include/linux/platform_data/davinci_clk.h
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f7f761b..c865fd0 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_ARCH_ARTPEC) += axis/
obj-$(CONFIG_ARC_PLAT_AXS10X) += axs10x/
obj-y += bcm/
obj-$(CONFIG_ARCH_BERLIN) += berlin/
+obj-$(CONFIG_ARCH_DAVINCI) += davinci/
obj-$(CONFIG_H8300) += h8300/
obj-$(CONFIG_ARCH_HISI) += hisilicon/
obj-y += imgtec/
diff --git a/drivers/clk/davinci/Makefile b/drivers/clk/davinci/Makefile
new file mode 100644
index 0000000..5efbdcd
--- /dev/null
+++ b/drivers/clk/davinci/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_PHY_DA8XX_USB) += da8xx-cfgchip-clk.o
+obj-y += pll.o
+obj-y += psc.o
\ No newline at end of file
diff --git a/drivers/clk/davinci/da8xx-cfgchip-clk.c b/drivers/clk/davinci/da8xx-cfgchip-clk.c
new file mode 100644
index 0000000..780bb25
--- /dev/null
+++ b/drivers/clk/davinci/da8xx-cfgchip-clk.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * da8xx-cfgchip-clk - TI DaVinci DA8xx CFGCHIP clocks driver
+ *
+ * Copyright (C) 2017 David Lechner <david at lechnology.com>
+ *
+ * This driver exposes the USB PHY clocks on DA8xx/AM18xx/OMAP-L13x SoCs.
+ * The clocks consist of two muxes and a PLL. The USB 2.0 PHY mux and PLL are
+ * combined into a single clock in Linux. The USB 1.0 PHY clock just consists
+ * of a mux. These clocks are controlled through CFGCHIP2, which is accessed
+ * as a syscon regmap since it is shared with other devices.
+ */
+
+#include <linux/clk.h>
+#include <linux/clkdev.h>
+#include <linux/clk-provider.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/mfd/da8xx-cfgchip.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_data/davinci_clk.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/**
+ * da8xx_cfgchip_clk
+ * @usb0_hw: The USB 2.0 PHY clock (mux + PLL)
+ * @usb1_hw: The USB 1.1 PHY clock (mux)
+ * @usb0_clk: The USB 2.0 subsystem PSC clock
+ * @regmap: The CFGCHIP syscon regmap
+ */
+struct da8xx_cfgchip_clk {
+ struct clk_hw usb0_hw;
+ struct clk_hw usb1_hw;
+ struct clk *usb0_clk;
+ struct regmap *regmap;
+};
+
+/* The USB 2.0 PHY can use either USB_REFCLKIN or AUXCLK */
+enum usb0_phy_clk_parent {
+ USB20_PHY_CLK_PARENT_USB_REFCLKIN,
+ USB20_PHY_CLK_PARENT_PLL0_AUX,
+};
+
+/* The USB 1.1 PHY can use either the PLL output from the USB 2.0 PHY or
+ * USB_REFCLKIN
+ */
+enum usb1_phy_clk_parent {
+ USB1_PHY_CLK_PARENT_USB_REFCLKIN,
+ USB1_PHY_CLK_PARENT_USB0_PHY_PLL,
+};
+
+/* --- USB 2.0 PHY clock --- */
+
+static int usb0_phy_clk_prepare(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+
+ /* The USB 2.0 PSC clock is only needed temporarily during the USB 2.0
+ * PHY clock enable, but since clk_prepare() can't be called in an
+ * atomic context (i.e. in clk_enable()), we have to prepare it here.
+ */
+ return clk_prepare(clk->usb0_clk);
+}
+
+static void usb0_phy_clk_unprepare(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+
+ clk_unprepare(clk->usb0_clk);
+}
+
+static int usb0_phy_clk_enable(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+ unsigned int mask, val;
+ int ret;
+
+ /* Locking the USB 2.O PLL requires that the USB 2.O PSC is enabled
+ * temporaily. It can be turned back off once the PLL is locked.
+ */
+ clk_enable(clk->usb0_clk);
+
+ /* Turn on the USB 2.0 PHY, but just the PLL, and not OTG. The USB 1.1
+ * PHY may use the USB 2.0 PLL clock without USB 2.0 OTG being used.
+ */
+ mask = CFGCHIP2_RESET | CFGCHIP2_PHYPWRDN | CFGCHIP2_PHY_PLLON;
+ val = CFGCHIP2_PHY_PLLON;
+
+ regmap_write_bits(clk->regmap, CFGCHIP(2), mask, val);
+ ret = regmap_read_poll_timeout(clk->regmap, CFGCHIP(2), val,
+ val & CFGCHIP2_PHYCLKGD, 0, 500000);
+
+ clk_disable(clk->usb0_clk);
+
+ return ret;
+}
+
+static void usb0_phy_clk_disable(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+ unsigned int val;
+
+ val = CFGCHIP2_PHYPWRDN;
+ regmap_write_bits(clk->regmap, CFGCHIP(2), val, val);
+}
+
+static unsigned long usb0_phy_clk_recalc_rate(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+ unsigned int mask, val;
+
+ /* The parent clock rate must be one of the following */
+ mask = CFGCHIP2_REFFREQ_MASK;
+ switch (parent_rate) {
+ case 12000000:
+ val = CFGCHIP2_REFFREQ_12MHZ;
+ break;
+ case 13000000:
+ val = CFGCHIP2_REFFREQ_13MHZ;
+ break;
+ case 19200000:
+ val = CFGCHIP2_REFFREQ_19_2MHZ;
+ break;
+ case 20000000:
+ val = CFGCHIP2_REFFREQ_20MHZ;
+ break;
+ case 24000000:
+ val = CFGCHIP2_REFFREQ_24MHZ;
+ break;
+ case 26000000:
+ val = CFGCHIP2_REFFREQ_26MHZ;
+ break;
+ case 38400000:
+ val = CFGCHIP2_REFFREQ_38_4MHZ;
+ break;
+ case 40000000:
+ val = CFGCHIP2_REFFREQ_40MHZ;
+ break;
+ case 48000000:
+ val = CFGCHIP2_REFFREQ_48MHZ;
+ break;
+ default:
+ return 0;
+ }
+
+ regmap_write_bits(clk->regmap, CFGCHIP(2), mask, val);
+
+ /* USB 2.0 PLL always supplies 48MHz */
+ return 48000000;
+}
+
+static long usb0_phy_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+ unsigned long *parent_rate)
+{
+ return 48000000;
+}
+
+static int usb0_phy_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+ unsigned int mask, val;
+
+ /* Set the mux depending on the parent clock. */
+ mask = CFGCHIP2_USB2PHYCLKMUX;
+ switch (index) {
+ case USB20_PHY_CLK_PARENT_USB_REFCLKIN:
+ val = 0;
+ break;
+ case USB20_PHY_CLK_PARENT_PLL0_AUX:
+ val = CFGCHIP2_USB2PHYCLKMUX;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write_bits(clk->regmap, CFGCHIP(2), mask, val);
+
+ return 0;
+}
+
+static u8 usb0_phy_clk_get_parent(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb0_hw);
+ unsigned int val;
+
+ regmap_read(clk->regmap, CFGCHIP(2), &val);
+
+ if (val & CFGCHIP2_USB2PHYCLKMUX)
+ return USB20_PHY_CLK_PARENT_PLL0_AUX;
+
+ return USB20_PHY_CLK_PARENT_USB_REFCLKIN;
+}
+
+static const struct clk_ops usb0_phy_clk_ops = {
+ .prepare = usb0_phy_clk_prepare,
+ .unprepare = usb0_phy_clk_unprepare,
+ .enable = usb0_phy_clk_enable,
+ .disable = usb0_phy_clk_disable,
+ .recalc_rate = usb0_phy_clk_recalc_rate,
+ .round_rate = usb0_phy_clk_round_rate,
+ .set_parent = usb0_phy_clk_set_parent,
+ .get_parent = usb0_phy_clk_get_parent,
+};
+
+static const char * const usb0_phy_clk_parent_names[] = {
+ [USB20_PHY_CLK_PARENT_USB_REFCLKIN] = "usb_refclkin",
+ [USB20_PHY_CLK_PARENT_PLL0_AUX] = "pll0_aux_clk",
+};
+
+static const struct clk_init_data usb0_phy_clk_init_data = {
+ .name = "usb0_phy_clk",
+ .ops = &usb0_phy_clk_ops,
+ .parent_names = usb0_phy_clk_parent_names,
+ .num_parents = ARRAY_SIZE(usb0_phy_clk_parent_names),
+};
+
+/* --- USB 1.1 PHY clock --- */
+
+static int usb1_phy_clk_set_parent(struct clk_hw *hw, u8 index)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb1_hw);
+ unsigned int mask, val;
+
+ /* Set the USB 1.1 PHY clock mux based on the parent clock. */
+ mask = CFGCHIP2_USB1PHYCLKMUX;
+ switch (index) {
+ case USB1_PHY_CLK_PARENT_USB_REFCLKIN:
+ val = CFGCHIP2_USB1PHYCLKMUX;
+ break;
+ case USB1_PHY_CLK_PARENT_USB0_PHY_PLL:
+ val = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_write_bits(clk->regmap, CFGCHIP(2), mask, val);
+
+ return 0;
+}
+
+static u8 usb1_phy_clk_get_parent(struct clk_hw *hw)
+{
+ struct da8xx_cfgchip_clk *clk =
+ container_of(hw, struct da8xx_cfgchip_clk, usb1_hw);
+ unsigned int val;
+
+ regmap_read(clk->regmap, CFGCHIP(2), &val);
+
+ if (val & CFGCHIP2_USB1PHYCLKMUX)
+ return USB1_PHY_CLK_PARENT_USB_REFCLKIN;
+
+ return USB1_PHY_CLK_PARENT_USB0_PHY_PLL;
+}
+
+static const struct clk_ops usb1_phy_clk_ops = {
+ .set_parent = usb1_phy_clk_set_parent,
+ .get_parent = usb1_phy_clk_get_parent,
+};
+
+static const char * const usb1_phy_clk_parent_names[] = {
+ [USB1_PHY_CLK_PARENT_USB_REFCLKIN] = "usb_refclkin",
+ [USB1_PHY_CLK_PARENT_USB0_PHY_PLL] = "usb0_phy_clk",
+};
+
+static struct clk_init_data usb1_phy_clk_init_data = {
+ .name = "usb1_phy_clk",
+ .ops = &usb1_phy_clk_ops,
+ .parent_names = usb1_phy_clk_parent_names,
+ .num_parents = ARRAY_SIZE(usb1_phy_clk_parent_names),
+};
+
+/* --- platform driver --- */
+
+static int da8xx_cfgchip_clk_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da8xx_cfgchip_clk_data *pdata = dev->platform_data;
+ struct da8xx_cfgchip_clk *phy_clk;
+ const char *parent_name;
+ struct clk *parent;
+ int ret;
+
+ if (!pdata)
+ return -EINVAL;
+
+ phy_clk = devm_kzalloc(dev, sizeof(*phy_clk), GFP_KERNEL);
+ if (!phy_clk)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, phy_clk);
+
+ phy_clk->regmap = syscon_regmap_lookup_by_pdevname("syscon");
+ if (IS_ERR(phy_clk->regmap)) {
+ dev_err(dev, "Failed to get syscon\n");
+ return PTR_ERR(phy_clk->regmap);
+ }
+
+ /* USB 2.0 subsystem PSC clock - needed to lock PLL */
+ phy_clk->usb0_clk = clk_get(dev, "usb20");
+ if (IS_ERR(phy_clk->usb0_clk)) {
+ dev_err(dev, "Failed to get usb20 clock\n");
+ return PTR_ERR(phy_clk->usb0_clk);
+ }
+
+ phy_clk->usb0_hw.init = &usb0_phy_clk_init_data;
+ ret = devm_clk_hw_register(dev, &phy_clk->usb0_hw);
+ if (ret) {
+ dev_err(dev, "Failed to register usb0_phy_clk\n");
+ return ret;
+ }
+
+ phy_clk->usb1_hw.init = &usb1_phy_clk_init_data;
+ ret = devm_clk_hw_register(dev, &phy_clk->usb1_hw);
+ if (ret) {
+ dev_err(dev, "Failed to register usb1_phy_clk\n");
+ return ret;
+ }
+
+ parent_name = pdata->usb0_use_refclkin ? "usb_refclkin" : "pll0_aux";
+ parent = devm_clk_get(dev, parent_name);
+ if (IS_ERR(parent)) {
+ dev_err(dev, "Failed to get usb0 parent clock %s\n",
+ parent_name);
+ return PTR_ERR(parent);
+ }
+
+ ret = clk_set_parent(phy_clk->usb0_hw.clk, parent);
+ if (ret) {
+ dev_err(dev, "Failed to set usb0 parent clock to %s\n",
+ parent_name);
+ return ret;
+ }
+
+ clk_hw_register_clkdev(&phy_clk->usb0_hw, NULL, "da8xx-cfgchip-clk");
+
+ parent_name = pdata->usb1_use_refclkin ? "usb_refclkin" : "usb0_phy_clk";
+ parent = devm_clk_get(dev, parent_name);
+ if (IS_ERR(parent)) {
+ dev_err(dev, "Failed to get usb1 parent clock %s\n",
+ parent_name);
+ return PTR_ERR(parent);
+ }
+
+ ret = clk_set_parent(phy_clk->usb1_hw.clk, parent);
+ if (ret) {
+ dev_err(dev, "Failed to set usb1 parent clock to %s\n",
+ parent_name);
+ return ret;
+ }
+
+ clk_hw_register_clkdev(&phy_clk->usb0_hw, "usb20_phy", "da8xx-usb-phy");
+ clk_hw_register_clkdev(&phy_clk->usb1_hw, "usb11_phy", "da8xx-usb-phy");
+
+ return 0;
+}
+
+static struct platform_driver da8xx_cfgchip_clk_driver = {
+ .probe = da8xx_cfgchip_clk_probe,
+ .driver = {
+ .name = "da8xx-cfgchip-clk",
+ },
+};
+module_platform_driver(da8xx_cfgchip_clk_driver);
+
+MODULE_ALIAS("platform:da8xx-cfgchip-clk");
+MODULE_AUTHOR("David Lechner <david at lechnology.com>");
+MODULE_DESCRIPTION("TI DA8xx CFGCHIP clock driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/davinci/pll.c b/drivers/clk/davinci/pll.c
new file mode 100644
index 0000000..035cd91
--- /dev/null
+++ b/drivers/clk/davinci/pll.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * PLL clock driver for Davinci devices
+ *
+ * Copyright (C) 2017 David Lechner <david at lechnology.com>
+ *
+ * Based on drivers/clk/keystone/pll.c
+ * Copyright (C) 2013 Texas Instruments Inc.
+ * Murali Karicheri <m-karicheri2 at ti.com>
+ * Santosh Shilimkar <santosh.shilimkar at ti.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+#define REVID 0x000
+#define PLLCTL 0x100
+#define OCSEL 0x104
+#define PLLSECCTL 0x108
+#define PLLM 0x110
+#define PREDIV 0x114
+#define PLLDIV1 0x118
+#define PLLDIV2 0x11c
+#define PLLDIV3 0x120
+#define OSCDIV 0x124
+#define POSTDIV 0x128
+#define BPDIV 0x12c
+#define PLLCMD 0x138
+#define PLLSTAT 0x13c
+#define ALNCTL 0x140
+#define DCHANGE 0x144
+#define CKEN 0x148
+#define CKSTAT 0x14c
+#define SYSTAT 0x150
+#define PLLDIV4 0x160
+#define PLLDIV5 0x164
+#define PLLDIV6 0x168
+#define PLLDIV7 0x16c
+#define PLLDIV8 0x170
+#define PLLDIV9 0x174
+
+#define PLLM_MASK 0x1f
+#define PREDIV_RATIO_MASK 0x1f
+#define PLLDIV_RATIO_WIDTH 5
+#define PLLDIV_ENABLE_SHIFT 15
+#define OSCDIV_RATIO_WIDTH 5
+#define POSTDIV_RATIO_MASK 0x1f
+#define BPDIV_RATIO_SHIFT 0
+#define BPDIV_RATIO_WIDTH 5
+#define CKEN_OBSCLK_SHIFT 1
+#define CKEN_AUXEN_SHIFT 0
+
+/**
+ * struct davinci_pll_clk - Main PLL clock
+ * @hw: clk_hw for the pll
+ * @base: Base memory address
+ * @parent_rate: Saved parent rate used by some child clocks
+ */
+struct davinci_pll_clk {
+ struct clk_hw hw;
+ void __iomem *base;
+};
+
+#define to_davinci_pll_clk(_hw) container_of((_hw), struct davinci_pll_clk, hw)
+
+static unsigned long davinci_pll_clk_recalc(struct clk_hw *hw,
+ unsigned long parent_rate)
+{
+ struct davinci_pll_clk *pll = to_davinci_pll_clk(hw);
+ unsigned long rate = parent_rate;
+ u32 prediv, mult, postdiv;
+
+ prediv = readl(pll->base + PREDIV) & PREDIV_RATIO_MASK;
+ mult = readl(pll->base + PLLM) & PLLM_MASK;
+ postdiv = readl(pll->base + POSTDIV) & POSTDIV_RATIO_MASK;
+
+ rate /= prediv + 1;
+ rate *= mult + 1;
+ rate /= postdiv + 1;
+
+ return rate;
+}
+
+#ifdef CONFIG_DEBUG_FS
+#include <linux/debugfs.h>
+
+#define DEBUG_REG(n) \
+{ \
+ .name = #n, \
+ .offset = n, \
+}
+
+static const struct debugfs_reg32 davinci_pll_regs[] = {
+ DEBUG_REG(REVID),
+ DEBUG_REG(PLLCTL),
+ DEBUG_REG(OCSEL),
+ DEBUG_REG(PLLSECCTL),
+ DEBUG_REG(PLLM),
+ DEBUG_REG(PREDIV),
+ DEBUG_REG(PLLDIV1),
+ DEBUG_REG(PLLDIV2),
+ DEBUG_REG(PLLDIV3),
+ DEBUG_REG(OSCDIV),
+ DEBUG_REG(POSTDIV),
+ DEBUG_REG(BPDIV),
+ DEBUG_REG(PLLCMD),
+ DEBUG_REG(PLLSTAT),
+ DEBUG_REG(ALNCTL),
+ DEBUG_REG(DCHANGE),
+ DEBUG_REG(CKEN),
+ DEBUG_REG(CKSTAT),
+ DEBUG_REG(SYSTAT),
+ DEBUG_REG(PLLDIV4),
+ DEBUG_REG(PLLDIV5),
+ DEBUG_REG(PLLDIV6),
+ DEBUG_REG(PLLDIV7),
+ DEBUG_REG(PLLDIV8),
+ DEBUG_REG(PLLDIV9),
+};
+
+static int davinci_pll_debug_init(struct clk_hw *hw, struct dentry *dentry)
+{
+ struct davinci_pll_clk *pll = to_davinci_pll_clk(hw);
+ struct debugfs_regset32 *regset;
+ struct dentry *d;
+
+ regset = kzalloc(sizeof(regset), GFP_KERNEL);
+ if (!regset)
+ return -ENOMEM;
+
+ regset->regs = davinci_pll_regs;
+ regset->nregs = ARRAY_SIZE(davinci_pll_regs);
+ regset->base = pll->base;
+
+ d = debugfs_create_regset32("registers", 0400, dentry, regset);
+ if (IS_ERR(d)) {
+ kfree(regset);
+ return PTR_ERR(d);
+ }
+
+ return 0;
+}
+#else
+#define davinci_pll_debug_init NULL
+#endif
+
+static const struct clk_ops davinci_pll_clk_ops = {
+ .recalc_rate = davinci_pll_clk_recalc,
+ .debug_init = davinci_pll_debug_init,
+};
+
+/**
+ * davinci_pll_clk_register - Register a PLL clock
+ * @name: The clock name
+ * @parent_name: The parent clock name
+ * @base: The PLL's memory region
+ */
+struct clk *davinci_pll_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base)
+{
+ struct clk_init_data init;
+ struct davinci_pll_clk *pll;
+ struct clk *clk;
+
+ pll = kzalloc(sizeof(*pll), GFP_KERNEL);
+ if (!pll)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &davinci_pll_clk_ops;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ pll->base = base;
+ pll->hw.init = &init;
+
+ clk = clk_register(NULL, &pll->hw);
+ if (IS_ERR(clk))
+ kfree(pll);
+
+ return clk;
+}
+
+struct davinci_pll_aux_clk {
+ struct clk_hw hw;
+ struct davinci_pll_clk *pll;
+};
+
+/**
+ * davinci_pll_aux_clk_register - Register bypass clock (AUXCLK)
+ * @name: The clock name
+ * @parent_name: The parent clock name (usually "ref_clk" since this bypasses
+ * the PLL)
+ * @base: The PLL memory region
+ */
+struct clk *davinci_pll_aux_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base)
+{
+ return clk_register_gate(NULL, name, parent_name, 0, base + CKEN,
+ CKEN_AUXEN_SHIFT, 0, NULL);
+}
+
+/**
+ * davinci_pll_bpdiv_clk_register - Register bypass divider clock (SYSCLKBP)
+ * @name: The clock name
+ * @parent_name: The parent clock name (usually "ref_clk" since this bypasses
+ * the PLL)
+ * @base: The PLL memory region
+ */
+struct clk *davinci_pll_bpdiv_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base)
+{
+ return clk_register_divider(NULL, name, parent_name, 0, base + BPDIV,
+ BPDIV_RATIO_SHIFT, BPDIV_RATIO_WIDTH,
+ CLK_DIVIDER_READ_ONLY, NULL);
+}
+
+/**
+ * davinci_pll_obs_clk_register - Register oscillator divider clock (OBSCLK)
+ * @name: The clock name
+ * @parent_names: The parent clock names
+ * @num_parents: The number of paren clocks
+ * @base: The PLL memory region
+ * @table: A table of values cooresponding to the parent clocks (see OCSEL
+ * register in SRM for values)
+ */
+struct clk *davinci_pll_obs_clk_register(const char *name,
+ const char * const *parent_names,
+ u8 num_parents,
+ void __iomem *base,
+ u32 *table)
+{
+ struct clk_mux *mux;
+ struct clk_gate *gate;
+ struct clk_divider *divider;
+ struct clk *clk;
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ mux->reg = base + OCSEL;
+ mux->table = table;
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate) {
+ kfree(mux);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ gate->reg = base + CKEN;
+ gate->bit_idx = CKEN_OBSCLK_SHIFT;
+
+ divider = kzalloc(sizeof(*divider), GFP_KERNEL);
+ if (!divider) {
+ kfree(gate);
+ kfree(mux);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ divider->reg = base + OSCDIV;
+ divider->width = OSCDIV_RATIO_WIDTH;
+
+ clk = clk_register_composite(NULL, name, parent_names, num_parents,
+ &mux->hw, &clk_mux_ops,
+ ÷r->hw, &clk_divider_ops,
+ &gate->hw, &clk_gate_ops, 0);
+ if (IS_ERR(clk)) {
+ kfree(divider);
+ kfree(gate);
+ kfree(mux);
+ }
+
+ return clk;
+}
+
+/**
+ * davinci_pll_div_clk_register - Register a PLLDIV (SYSCLK) clock
+ * @name: The clock name
+ * @parent_name: The parent clock name
+ * @base: The PLL memory region
+ * @id: The id of the divider (n in PLLDIVn)
+ */
+struct clk *davinci_pll_div_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base,
+ u32 id)
+{
+ const char * const *parent_names = (parent_name ? &parent_name : NULL);
+ int num_parents = (parent_name ? 1 : 0);
+ struct clk_gate *gate;
+ struct clk_divider *divider;
+ struct clk *clk;
+ u32 reg;
+
+ /* PLLDIVn registers are not entirely consecutive */
+ if (id < 4)
+ reg = PLLDIV1 + 4 * (id - 1);
+ else
+ reg = PLLDIV4 + 4 * (id - 4);
+
+ gate = kzalloc(sizeof(*gate), GFP_KERNEL);
+ if (!gate)
+ return ERR_PTR(-ENOMEM);
+
+ gate->reg = base + reg;
+ gate->bit_idx = PLLDIV_ENABLE_SHIFT;
+
+ divider = kzalloc(sizeof(*divider), GFP_KERNEL);
+ if (!divider) {
+ kfree(gate);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ divider->reg = base + reg;
+ divider->width = PLLDIV_RATIO_WIDTH;
+ divider->flags = CLK_DIVIDER_READ_ONLY;
+
+ clk = clk_register_composite(NULL, name, parent_names, num_parents,
+ NULL, NULL, ÷r->hw, &clk_divider_ops,
+ &gate->hw, &clk_gate_ops, 0);
+ if (IS_ERR(clk)) {
+ kfree(divider);
+ kfree(gate);
+ }
+
+ return clk;
+}
diff --git a/drivers/clk/davinci/psc.c b/drivers/clk/davinci/psc.c
new file mode 100644
index 0000000..8ae85ee
--- /dev/null
+++ b/drivers/clk/davinci/psc.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Clock driver for DA8xx/AM17xx/AM18xx/OMAP-L13x PSC controllers
+ *
+ * Copyright (C) 2017 David Lechner <david at lechnology.com>
+ *
+ * Based on: drivers/clk/keystone/gate.c
+ * Copyright (C) 2013 Texas Instruments.
+ * Murali Karicheri <m-karicheri2 at ti.com>
+ * Santosh Shilimkar <santosh.shilimkar at ti.com>
+ *
+ * And: arch/arm/mach-davinci/psc.c
+ * Copyright (C) 2006 Texas Instruments.
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+
+/* PSC register offsets */
+#define EPCPR 0x070
+#define PTCMD 0x120
+#define PTSTAT 0x128
+#define PDSTAT 0x200
+#define PDCTL 0x300
+#define MDSTAT 0x800
+#define MDCTL 0xa00
+
+/* PSC module states */
+enum davinci_psc_state {
+ PSC_STATE_SWRSTDISABLE = 0,
+ PSC_STATE_SYNCRST = 1,
+ PSC_STATE_DISABLE = 2,
+ 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_EPCGOOD BIT(8)
+#define PDCTL_NEXT BIT(0)
+
+/**
+ * struct davinci_psc_clk - PSC clock structure
+ * @hw: clk_hw for the psc
+ * @psc_data: PSC driver specific data
+ * @lpsc: Local PSC number (module id)
+ * @pd: Power domain
+ */
+struct davinci_psc_clk {
+ struct clk_hw hw;
+ void __iomem *base;
+ u32 lpsc;
+ u32 pd;
+};
+
+#define to_davinci_psc_clk(_hw) container_of(_hw, struct davinci_psc_clk, hw)
+
+static void psc_config(struct davinci_psc_clk *psc,
+ enum davinci_psc_state next_state)
+{
+ u32 epcpr, ptcmd, pdstat, pdctl, mdstat, mdctl, ptstat;
+
+ mdctl = readl(psc->base + MDCTL + 4 * psc->lpsc);
+ mdctl &= ~MDSTAT_STATE_MASK;
+ mdctl |= next_state;
+ /* TODO: old davinci clocks for da850 set MDCTL_FORCE bit for sata and
+ * dsp here. Is this really needed?
+ */
+ writel(mdctl, psc->base + MDCTL + 4 * psc->lpsc);
+
+ pdstat = readl(psc->base + PDSTAT + 4 * psc->pd);
+ if ((pdstat & PDSTAT_STATE_MASK) == 0) {
+ pdctl = readl(psc->base + PDSTAT + 4 * psc->pd);
+ pdctl |= PDCTL_NEXT;
+ writel(pdctl, psc->base + PDSTAT + 4 * psc->pd);
+
+ ptcmd = BIT(psc->pd);
+ writel(ptcmd, psc->base + PTCMD);
+
+ do {
+ epcpr = __raw_readl(psc->base + EPCPR);
+ } while (!(epcpr & BIT(psc->pd)));
+
+ pdctl = __raw_readl(psc->base + PDCTL + 4 * psc->pd);
+ pdctl |= PDCTL_EPCGOOD;
+ __raw_writel(pdctl, psc->base + PDCTL + 4 * psc->pd);
+ } else {
+ ptcmd = BIT(psc->pd);
+ writel(ptcmd, psc->base + PTCMD);
+ }
+
+ do {
+ ptstat = readl(psc->base + PTSTAT);
+ } while (ptstat & BIT(psc->pd));
+
+ do {
+ mdstat = readl(psc->base + MDSTAT + 4 * psc->lpsc);
+ } while (!((mdstat & MDSTAT_STATE_MASK) == next_state));
+}
+
+static int davinci_psc_clk_enable(struct clk_hw *hw)
+{
+ struct davinci_psc_clk *psc = to_davinci_psc_clk(hw);
+
+ psc_config(psc, PSC_STATE_ENABLE);
+
+ return 0;
+}
+
+static void davinci_psc_clk_disable(struct clk_hw *hw)
+{
+ struct davinci_psc_clk *psc = to_davinci_psc_clk(hw);
+
+ psc_config(psc, PSC_STATE_DISABLE);
+}
+
+static int davinci_psc_clk_is_enabled(struct clk_hw *hw)
+{
+ struct davinci_psc_clk *psc = to_davinci_psc_clk(hw);
+ u32 mdstat;
+
+ mdstat = readl(psc->base + MDSTAT + 4 * psc->lpsc);
+
+ return (mdstat & MDSTAT_MCKOUT) ? 1 : 0;
+}
+
+static const struct clk_ops davinci_psc_clk_ops = {
+ .enable = davinci_psc_clk_enable,
+ .disable = davinci_psc_clk_disable,
+ .is_enabled = davinci_psc_clk_is_enabled,
+};
+
+/**
+ * davinci_psc_clk_register - register psc clock
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_name: name of clock's parent
+ * @base: memory mapped register for the PSC
+ * @lpsc: local PSC number
+ * @pd: power domain
+ */
+struct clk *davinci_psc_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base,
+ u32 lpsc, u32 pd)
+{
+ struct clk_init_data init;
+ struct davinci_psc_clk *psc;
+ struct clk *clk;
+
+ psc = kzalloc(sizeof(*psc), GFP_KERNEL);
+ if (!psc)
+ return ERR_PTR(-ENOMEM);
+
+ init.name = name;
+ init.ops = &davinci_psc_clk_ops;
+ init.flags = 0;
+ init.parent_names = (parent_name ? &parent_name : NULL);
+ init.num_parents = (parent_name ? 1 : 0);
+
+ psc->base = base;
+ psc->hw.init = &init;
+ psc->lpsc = lpsc;
+ psc->pd = pd;
+
+ clk = clk_register(NULL, &psc->hw);
+ if (IS_ERR(clk))
+ kfree(psc);
+
+ return clk;
+}
+
+/* FIXME: This needs to be converted to a reset controller. But, the reset
+ * framework is currently device tree only.
+ */
+
+DEFINE_SPINLOCK(davinci_psc_reset_lock);
+
+static int davinci_psc_clk_reset(struct davinci_psc_clk *psc, bool reset)
+{
+ unsigned long flags;
+ u32 mdctl;
+
+ if (IS_ERR_OR_NULL(psc))
+ return -EINVAL;
+
+ spin_lock_irqsave(&davinci_psc_reset_lock, flags);
+ mdctl = readl(psc->base + MDCTL + 4 * psc->lpsc);
+ if (reset)
+ mdctl &= ~MDCTL_LRESET;
+ else
+ mdctl |= MDCTL_LRESET;
+ writel(mdctl, psc->base + MDCTL + 4 * psc->lpsc);
+ spin_unlock_irqrestore(&davinci_psc_reset_lock, flags);
+
+ return 0;
+}
+
+int davinci_clk_reset_assert(struct clk *clk)
+{
+ struct davinci_psc_clk *psc = to_davinci_psc_clk(__clk_get_hw(clk));
+
+ return davinci_psc_clk_reset(psc, true);
+}
+EXPORT_SYMBOL(davinci_clk_reset_assert);
+
+int davinci_clk_reset_deassert(struct clk *clk)
+{
+ struct davinci_psc_clk *psc = to_davinci_psc_clk(__clk_get_hw(clk));
+
+ return davinci_psc_clk_reset(psc, false);
+}
+EXPORT_SYMBOL(davinci_clk_reset_deassert);
diff --git a/include/linux/clk/davinci.h b/include/linux/clk/davinci.h
new file mode 100644
index 0000000..c5d2181
--- /dev/null
+++ b/include/linux/clk/davinci.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI Davinci clocks
+ *
+ * Copyright (C) 2017 David Lechner <david at lechnology.com>
+ */
+#ifndef __LINUX_CLK_DAVINCI_H__
+#define __LINUX_CLK_DAVINCI_H__
+
+#include <linux/clk-provider.h>
+#include <linux/types.h>
+
+struct clk *davinci_pll_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base);
+struct clk *davinci_pll_aux_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base);
+struct clk *davinci_pll_bpdiv_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base);
+struct clk *davinci_pll_obs_clk_register(const char *name,
+ const char * const *parent_names,
+ u8 num_parents,
+ void __iomem *base,
+ u32 *table);
+struct clk *davinci_pll_div_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base,
+ u32 id);
+struct clk *davinci_psc_clk_register(const char *name,
+ const char *parent_name,
+ void __iomem *base,
+ u32 lpsc, u32 pd);
+
+/* convience macros for board declaration files */
+#define EXT_CLK(n, r) clk_register_fixed_rate(NULL, (n), NULL, 0, (r))
+#define FIX_CLK(n, p) clk_register_fixed_factor(NULL, (n), (p), 0, 1, 1)
+#define PLL_CLK davinci_pll_clk_register
+#define PLL_DIV_CLK davinci_pll_div_clk_register
+#define PLL_AUX_CLK davinci_pll_aux_clk_register
+#define PLL_BP_CLK davinci_pll_bpdiv_clk_register
+#define PLL_OBS_CLK davinci_pll_obs_clk_register
+#define PSC_CLK davinci_psc_clk_register
+
+#endif /* __LINUX_CLK_DAVINCI_H__ */
diff --git a/include/linux/platform_data/davinci_clk.h b/include/linux/platform_data/davinci_clk.h
new file mode 100644
index 0000000..7576ace
--- /dev/null
+++ b/include/linux/platform_data/davinci_clk.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * TI DaVinci Clock support
+ *
+ * Copyright (C) 2017 David Lechner <david at lechnology.com>
+ */
+
+#ifndef __PLATFORM_DATA_DAVINCI_CLK_H
+#define __PLATFORM_DATA_DAVINCI_CLK_H
+
+#include <linux/types.h>
+
+/**
+ * da8xx_cfgchip_clk_data - DA8xx CFGCHIP clock platform data
+ * @usb0_use_refclkin: when true, use USB_REFCLKIN, otherwise use AUXCLK for
+ * USB 2.0 PHY clock
+ * @usb1_use_refclkin: when true, use USB_REFCLKIN, otherwise use USB 2.0 PHY
+ * PLL for USB 1.1 PHY clock
+ */
+struct da8xx_cfgchip_clk_data {
+ bool usb0_use_refclkin;
+ bool usb1_use_refclkin;
+};
+
+#endif /* __PLATFORM_DATA_DAVINCI_CLK_H */
--
2.7.4
More information about the linux-arm-kernel
mailing list