[PATCH v4 5/7] clk: Introduce davinci clocks
David Lechner
david at lechnology.com
Tue Jan 2 13:31:10 PST 2018
Forgot to cc linux-clk, so doing that now...
On 12/31/2017 05:39 PM, David Lechner wrote:
> 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 */
>
More information about the linux-arm-kernel
mailing list